본문 바로가기

IT Book Summary/ModernJavaInAction

Chapter 9 리팩터링, 테스팅, 디버깅

 이번장에서는 기존 코드를 이용해 새로운 프로젝트를 시작하는 상황을 가정한다.

기존 코드를 람다 표현식을 사용해 어떻게 리팩터링해야 하는지 설명할 것이다.

람다 표현식으로 전략, 템플릿 메서드, 옵저버, 의무체인, 팩토리 등의 객체지향 디자인 패턴을 간소화 시킬지 살펴보자.

 


9.1 가독성과 유연성을 개선하는 리팩터링

 

 

 1 - 코드 가독성 개선

코드 가독성이 좋다는것 : 어떤 코드를 다른 사람도 쉽게 이해할 수 있음.

코드의 문서화를 잘하고, 표준 코딩 규칙을 준수하는 등 노력

 

2 - 익명 클래스를 람다 표현식으로 리팩터링

하나의 추상 메서드를 구현하는 익명 클래스는 람다 표현식으로 리팩터링

 

ex) Runnable 객체를 만드는 익명 클래스와 이에 대응하는 람다 표현식

Runnable r1 = new Runnable() { // 익명클래스
    public void run() {
        System.out.println("Hello");
    }
};

// 람다표현식의 코드
Runnable r2 = () -> System.out.println("Hello");

 

하지만 모든 익명클래스를 변환할 수 있는것은 아님.

익명 클래스의 this, super는 람다 표현식에서 다음 의미.

- 익명클래스에서 this 는 익명클래스 자신을 가리키지만, 람다에서 this 는 람다를 감싸는 클래스를 가리킴.

- 익명클래스는 감싸고있는 클래스의 변수를 가릴수 있음(섀도 변수), 람다에서는 변수를 가릴수 없음.

 

int a = 10;

Runnable r1 = new Runnable() {
    public void run() {
        int a = 2; // 잘 동작.
        System.out.println(a);
    }
};

Runnable r2 = () -> {
    int a = 2; // 컴파일 에러.
    System.out.println(a);
}

 

- 익명클래스를 람다 표현식으로 바꾸면 콘텍스트 오버로딩에 따른 모호함 초래

ex) Task 라는 Runnable 과 같은 시그니처를 갖는 함수형 인터페이스

 

interface Task {
    public void execute();
}
public static void doSomething(Runnable r){ r.run(); }
public static void doSomething(Task a){ r.execute(); }

// Task를 구현하는 익명클래스 전달가능.
doSomething(new Task() {
    public void execute() {
        System.out.println("Danger danger!!");
    }
});

 

하지만 익명클래스를 람다로 바꾸면 메서드 호출시 Runnable 과 Task 모두 대상형식이 되므로 모호함 발생.

 

doSomething(() -> System.out.println("Danger danger!!"));

//명시적 형변환으로 모호한 제거 가능.
doSomething((Task)() -> System.out.println("Danger danger!!"));

하지만 대부분의 IDE 에서 제공하는 리팩터링 기능을 이용해 처리 가능함.

 

3- 람다 표현식을 메서드 참조로 리팩터링

메서드 참조의 메서드 명으로 코드의 의도를 명확하게 알릴수 있음.

ex) 6장의 칼로리 수준으로 요리 그룹화하는 코드

Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = menu.stream()
    .collect(
            groupingBy(dish -> {
                if(dish.getCalories() <= 400) return CaloricLevel.DIET;
                else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
                else return CaloricLevel.FAT;
            }));
            
// 람다표현식을 별도의 메서드로 추출한 후 groupingBy에 인수로 전달.
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel = 
    menu.stream().collect(groupingBy(Dish::getCaloricLevel));
    
public class Dish {
    ...
    public CaloricLevel getCaloricLevel() {
        if(dish.getCalories() <= 400) return CaloricLevel.DIET;
        else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;
        else return CaloricLevel.FAT;
    }
}
    

 

 

또한 comparing 과 maxBy 같은 정적 헬퍼 메서드를 활용하는 것도 좋다.

// 비교구현에 신경써야함.
inventory.sort(
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

// 코드가 문제 자체를 설명.
inventory.sort(comparing(Apple::getWeight));

 

 

sum, maximum 등 자주 사용하는 리듀싱 연산은 메서드 참조와 함 께 사용가능한 내장 헬퍼 메서드 제공

// 저수준 리듀싱 연산
int totalCalories = 
    menu.stream().map(Dish::getCalories)
                 .reduce(0, (c1, c2) -> c1 + c2);
                 
// 내장 컬렉터 이용
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

 

 

4 - 명령형 데이터 처리를 스트림으로 리펙터링

이론적으로 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API로 바꿔야 함.

- 데이터 처리 파이프라인의 의도를 더 명확하게 보여주기 때문.

- 쇼트서킷과 게으름 이라는 강력한 최적화

- 멀티코어 아키텍처를 활용할 수 있음.

 

// 필터링과 추출로 엉킨코드.
List<String> dishNames = new ArrayList<>();
for(Dish dish: menu) {
    if(dish.getCalories() > 300) {
        dishNames.add(dish.getName());
    }
}

// 스트림을 이용해 직접적으로 기술하고 쉽게 병렬화
menu.parallelStream()
    .filter(d -> d.getCalories() > 300)
    .map(Dish::getName)
    .collect(toList());

 

 

 

5 - 코드 유연성 개선

2,3장 동작 파라미터화를 쉽게 구현

변화하는 요구사항에 대응할 수 있는 코드 구현

ex) 프레디케이트로 다양한 필터링, 비교자로 다양한 비교기능 등.

 

함수형 인터페이스 적용

람다 표현식을 이용하려면 함수형 인터페이스가 필요

 

조건부 연기 실행

실제 작업을 처리하는 코드 내부에 제어 흐름문이 복잡하게 얽힌 코드를 종종 보임.

 

if(logger.isLoggable(Log.FINER)) {
    logger.finer("Problem: " + generateDiagnostic());
}

- logger 상태가 isLoggable이라는 메서드에 의해 클라이언트 코드로 노출

- 메시지를 로깅할 때마나 logger객체 상태를 매번 확인해야 하므로 코드를 어지럽힘.

 

메시지를 로깅하기전에 logger객체가 적절한 수준으로 설정되었는지 내부적으로 확인하는 log메서드를 사용

logger.log(Level.FINER, "Problem: " + generateDiagnostic());

하지만 logger가 활성화 되지 않더라도 항상 로깅 메시지를 평가하게 되므로 람다를 이용해 문제를 해결.

특정조건 에서만 메시지가 생성될 수 있도록 메시지 생성과정을 연기 할수 있게함.

자바 8에서는 Supplier를 인수로 갖는 오버로드된 log 메서드 제공

 

// 새로 추가된 log 메서드의 시그니처
public void log(Level level, Supplier<String> msgSupplier)

// 다음과 같이 호출


// log 메서드는 logger의 수준이 적절하게 설정되어 있을때만 인수로 넘겨진 람다를 내부적으로 실행
public void log(Level level, Supplier<String> msgSupplier) {
    if(logger.isLoggable(level)) {
        log(level, msgSupplier.get());
    }
}

 

만일 클라이언트 코드에서 객체 상태를 자주 확인(logger 상태)하거나

객체 일부 메서드를 호출하는 상황(메시지 로깅)이라면 

내부적으로 객체의 상태를 확인한 다음 메서드를 호출(람다나 메서드 참조를 인수로 사용)하도록

새로운 메서드를 구현하는것이 좋다.

코드의 가독성이 좋아질 뿐 아니라 캡슐화도 강화 (객체 상태가 클라이언트 코드로 노출되지 않음.)

 

실행 어라운드

3장의 실행 어라운드 패턴.

매번 같은 준비, 종료과정을 반복적으로 수행한다면 이를 람다로 변환. 로직 재사용.

 

ex) 3장의 파일처리 코드

 

String oneLine = 
    processFile((BufferedReader b) -> b.readLine());
String twoLines = 
    processFile((BufferedReader b) -> b.readLine() + b.readLine);
public static String processFile(BufferedReaderProcessor p) throws IOException {
    try(BufferedReader br = 
        new BufferedReader(new FileReader("ModernJavaInAction.txt"))) {
        return p.process(br);
    }
}
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

 

람다로 BufferedReader 객체의 동작을 결정할 수 있는것은 함수형 인터페이스 BufferedReaderProcessor때문

 


9.2 람다로 객체지향 디자인 패턴 리팩터링하기

 

디자인 패턴은 공통적인 소프트웨어 문제를 설계할 때 재사용할 수 있는 검증된 청사진을 제공.

예를 들어 구조체와 동작하는 알고리즘을 서로 분리하고 싶을때 방문자 디바인 패턴을 사용.

싱글턴 패턴을 이용해서 클래스 인스턴스화를 하나의 객체로 제한 등.

 

람다식을 이용해 이전의 디자인 패턴으로 해결하던 문제를 더 쉽게 해결할수도 있고,

람다식으로 기존의 객체지향 디자인 패턴을 제거하거나 간결하게 재구현할수도 있음.

 

1 - 전략

: 한 유형의 알고리즘을 보유한 상태에서 런타임에 적절한 알고리즘을 선택하는 기법.

 

다양하나 기준을 갖는 입력값을 검증하거나, 다양한 파싱방법을 사용하거나, 입력 형식을 설정하는 등, 

다양한 시나리오에 전략패턴 사용.

세부분으로 구성됨.

  • 알고리즘을 나타내는 인터페이스(Strategy 인터페이스)
  • 다양한 알고리즘을 나타내는 한개 이상의 인터페이스 구현(ConcreatStrategyA, ConcreatStrategyB 구체적 구현클래스)
  • 전략 객체를 사용하는 한개 이상의 클라이언트

ex) 텍스트 입력이 다양한 조건에 맞게 포맷되있는지 검증하는 예제

 

public interface ValidationStrategy {
    boolean execute(String s);
}

//구현 클래스 하나이상.
public class IsAllowerCase implements ValidationStrategy {
    public boolean execute(String s) {
        return s.matches("[a-z]+");
    }
}
public class IsNumeric implements ValidationStrategy {
    public boolean execute(String s) {
        return s.matches("\\d+");
    }
}

//구현 클래스를 다양한 검증 전략으로 활용.
public class Validator {
    private final ValidationStrategy strategy;
    public Validator(ValidationStrategy v) {
        this.strategy = v;
    }
    public boolean validate(String s) {
        return strategy.execute(s);
    }
}

Validator numericValidator = new Validator(new IsNumeric);
boolean b1 = numericValicator.validate("aaaa"); // false
Validaror lowerCaseValidator = new Validator(new IsAllLowerCase());
boolean b2 = lowerCaseValidator.validate("bbbb"); // true

 

ValidationStrategy는 함수형 인터페이스이고 Predicate<String> 과 같은 함수 디스크립터를 갖고있음.

따라서 새로운 클래스 구현없이, 직접 람다 표현식을 전달 가능.

 

Validator numericValidator = 
    new Validator((String s) -> s.matches("[a-z]+"));
boolean b1 = nemericValidator.validate("aaaa");
Validator lowerCaseValidator = 
    new Validator((String s) -> s.matches("\\d+"));
boolean b1 = lowerCaseValidator.validate("aaaa");

 

람다표현식은 코드전략을 캡슐화함.

 

2 - 템플릿메서드

템플릿메서드는 '이 알고리즘을 사용하고 싶은데 그대로는 안되고 조금 고쳐야 하는' 상황에 적합.

 

ex) 간단한 온라인 뱅킹 애플리케이션을 구현하는 예제

- 사용자가 고객 ID를 애플리케이션에 입력하면 은행 데이터베이스에서 고객 정보를 가져오고 고객이 원하는 서비스를 제공

- 고객 계좌에 보너스를 입금한다고 가정

 

// 온라인 뱅킹 애플리케이션 동작을 정의하는 추상 클래스
abstract class OnlineBanking {
    public void processCustomer(int id) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy(c);
    }
    abstract void makeCustomerHappy(Customer c);
}

 

각각의 지점은 OnlineBanking 클래스를 상속받아 makeCustomerhappy메서드가 원하는 동작을 수행하도록 구현

 

람다나 메서드 참조로 알고리즘에 추가할 다양한 컴포넌트 구현가능.

이전에 정의한 makeCustomerhappy의 메서드 시그니처와 일치하도록

Consumer<Customer> 형식을 갖는 두번째 인수를 processCustomer에 추가.

 

public void processCustomer(int id, Cunsumer<Customer> makeCustomerHappy) {
    Customer c = Database.getCustomerWithId(id);
    makeCustomerHappy.accept(c);
}

//onlineBanking 클래스를 상속받지 않고 직접 람다를 전달해서 다양한 동작 추가 가능.
new OnlineBankingLamda().processCustomer(1337, (Customer c) -> 
    System.out.println("Hello "+ c.getName()));

 

 

3 - 옵저버

어떤 이벤트가 발생했을때 한 객체(subject)가 다른 객체 리스트(observer)에 

자동으로 알림을 보내야 하는 상황에서 옵저버 디자인 패턴을 사용.

GUI 애플리케이션에서 옵저버 패턴이 자주등장.

 

ex) 트위터같은 커스터마이즈된 알림 시스템을 설계하고 구현.

 

// 다양한 옵저버를 그룹화할 인터페이스 필요.
// 새로운 트윗이 있을때 주제Feed가 호출할 수있도록 notify메서드 제공
interface Observer {
    void notify (String tweet);
}

// 트윗에 포함된 다양한 키워드에 다른 동작을 수행할 수 있는 여러 옵저버 정의.
class NYTimes implements Observer {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("money")){
            System.out.println("Breaking news in NY! " +tweet);
        }
    }
}
class Gurdian implements Obserber {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("queen")) {
            System.out.println("Yet more news from London... " +tweet);
        }
    }
}
class LeMonde implements Obserber {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("wine")) {
            System.out.println("Today cheese, wine and news! " +tweet);
        }
    }
}

// 주제 구현.
interface Subject {
    void registerObserver(Observer o);
    void notifyObservers(String tweet);
}

// 주제는 registerObserver 메서드로 새로운 옵저버를 등록한 다음 
// notifyObserver 메서드로 트윗의 옵저버에 이를 알림
class Feed implements Subject {
    private final List<Observer> observers = new ArrayList<>();
    public void registerObserver(Observer o) {
        this.observers.add(o);
    }
    public void notifyObservers(String tweet) {
        observers.forEach(o -> o.notify(tweet));
    }
}

 

구현은 간단함.

Feed 는 트윗을 받았을때 알림을 보낼 옵저버 리스트를 유지.

이제 주제와 옵저버를 연결하는 데모 애플리케이션을 만들수 있음.

 

Feed f = new Feed();
f.registerObserer(new NYTimes());
f.registerObserer(new Guardian());
f.registerObserer(new LeMonde());
f.registerObserer("The queen said her favourite book is Harry Porter");

 

람다는 불필요한 감싸는 코드 제거 전문가다.

세개의 옵저버를 명시적으로 인스턴스화 하지않고 람다표현식을 직접 전달해서 실행할 동작 지정.

 

f.registerObserver((String tweet) -> {
    if(tweet !=null && tweet.contains("money")) {
        System.out.println("Breaking news in NY! "+tweet);
    }
})

 

옵저버가 사아태를 가지며 여러 메서드를 정의하는 등 복잡한 구조를 가지고 있으면

람다보다 기존의 클래스 구현방식을 고수하는것이 나을수 있음.

 

4 - 의무 체인

작업처리 객체의 체인(동작 체인 등)을 만들때는 의무 체인 패턴을 사용.

- 일반적으로 다음으로 처리할 객체 정보를 유지하는 필드를 포함하는

작업처리 추상클래스로 의무체인 패턴을 구성.

- 작업처리 객체가 자신의 작업을 끝냈으면 다음 작업처리 객체로 결과를 전달.

 

public abstract class ProcessingObject<T> {
    protected ProcessingObject<T> successor;
    public void setSuccessor(ProcessingObject<T> successor) {
        this.successor = successor;
    }
    public T handle(T input) {
        T r = handleWork(input);
        if(successor != null) {
            return successor.handle(r);
        }
        return r;
    }
    abstract protected T handleWork(T input);
}

 

ProcessingObject 클래스를 상속받아 handleWork 메서드를 구현하여 다양한 종류의 작업처리 객체를 만들수 있음.

ex) 텍스트를 처리하는 예제

 

public class HeaderTextProcessing extends ProcessingObject<String> {
    public String handleWork(String text) {
        return "From Raoul, Mario and Alan: "+text;
    }
}

public class SpellCheckerProcessing extends ProcessingObject<String> {
    public String handleWork(String text) {
        return text.replaceAll("labda", "lambda");
    }
}

// 두 작업처리 객체를 연결해서 작업 체인
ProcessingObject<String> p1 = new HeaderTextProcessing();
ProcessingObject<String> p2 = new SpellCheckerProcessing();
p1.setSuccessor(p2);
String result = p1.handle("Aren't labdas really sexy?!!");
System.out.println(result);

 

이 패턴은 함수체인과 비슷하자.

3장에서 람다 표현식 조합방법과 같이 작업처리 객체를 Function<String, String>

UnaryOperator<String> 형식의 인스턴스로 표현 가능.

andThen 메서드로 이들 함수를 조합해서 체인 만들기.

 

UnaryOperator<String> headerProcessing = 
    (String text) -> "From Raoul, Mario and Alan: "+text;
UnaryOperator<String> spellCheckerProcessing = 
    (String text) -> text.replaceAll("labda", "lambda");
Function<String, String> pipeline = 
    headerProcessing.andThen(spellCheckerProcessing);
String result = pipeline.apply("Aren't labda really sexy?!!");

 

5 - 팩토리

인스턴스화 로직을 클라이언트에 노출하지 않고 객체를 만들때 팩토리 디자인 패턴을 사용.

 

ex) 은행에서 취급하는 대출, 채권, 주식 등 다양한 상품을 만드는 예제

 

public class ProductFactory {
    public static Product createProduct(String name) {
        switch(name) { // 모두 Product의 서브 형식.
            case "loan": return new Loan();
            case "stock": return new Stock();
            case "bond": return new Bond();
            default: throw new RuntimeException("No such product "+ name);
        }
    }
}
// 생성자와 설정을 외부로 노출하지 않음.
// 클라이언트가 단순하게 상품을 생산.
Product p = ProductFactory.createProduct("loan");

 

생성자도 메서드 참조처럼 접근할 수 있다.

 

Supplier<Product> loanSupplier = Loan::new; 
Loan loan = loanSupplier.get();

// 상품명을 생성자로 연결하는 Map을 만들어 재구현
final static Map<String, Supplier<Product>> map = new HashMap<>();
static {
    map.put("loan", Loan::new);
    map.put("stock", Stock::new);
    map.put("bond", Bond::new);
}

// Map을 이용해 팩토리디자인패턴 에서 한것처럼 다양한 상품 인스턴스화
public static Product createProduct(String name) {
    Supplier<Product> p = map.get(name);
    if(p != null) return p.get();
    throw new IllegalArgumentException("No such product "+ name);
}

 

단, createProduct 상품 생성자로 여러 인수를 전달하는 상황에서는 특별한 함수형 인터페이스를 만들어야 함.


9.3 람다 테스팅

 

일반적으로 프로그램이 의도대로 동작하는지 확인하는 단위 테스팅을 진행함.

예상된 결과를 도출할 것이라 단언하는 테스트 케이스 구현.

 

// 그래픽 애플리케이션 일부 Point 클래스
public class Point {
    private final int x;
    private final int y;
    
    private Point(int x, int y) {
        this.x = x;
        thix.y = y;
    }
    public int getX(){return x;}
    public int getY(){return y;}
    public Point moveRightBy(int a) {
        return new Point(this.x + a, this.y);
    }
}

// 단위테스트
@Test
public void testMoveRightBy() throws Exception {
    Point p1 = new Point(5,5);
    Point p2 = p1.moveRightBy(10);
    assertEqual(15, p2.getX());
    assertEqual(5, p2.getY());
}

 

 

1 - 보이는 람다 표현식의 동작 테스팅

테스트 케이스 내부에서 Point클래스 코드를 테스트할 수 있으나

람다는 익명함수이므로 테스트 코드 이름을 호출할 수 없음.

 

그래서 필요하다면 람다를 필드에 저장해서 재사용할 수 있으며 람다의 로직을 테스트 할 수 있음.

 

Point 클래스에 compareByXAndThenY라는 정적 필드를 추가하자.

람다 표현식은 함수형 인터페이스의 인스턴스를 생성함.

따라서 생성된 인스턴스의 동작으로 람다 표현식을 테스트할 수있음.

 

public class Point {
    public final static Comparator<Point> compareByXAndThenY = 
        comparing(Point::getX).thenComparing(Point::getY);
    ...    
}

// Comparator 객체 compareByXAndThenY에 다양한 인수로 compare 메서드 호출
@Test
public void testComparingTwoPoints() throws Exception {
    Point p1 = new Point(10, 15);
    Point p2 = new Point(10, 20);
    int result = Point.compareByXAndThenY.compare(p1, p2);
    assertTrue(result < 0);
}

 

 

2 - 람다를 사용하는 메서드의 동작에 집중하라

람다의 목표는 정해진 동작을 다른메서드에서 사용할 수 있도록

하나의 조각으로 캡슐화하는 것.

람다 표현식을 사용하는 메서드의 동작을 테스트 함으로써 람다를 공개하지 않으면서도 람다 표현식을 검증할 수 있음.

 

public static List<Point> moveAllPointsRightBy(List<Point> points, int a) {
    return points.stream()
                 .map(p -> new Point(p.getX() + a, p.getY()))
                 .collect(toList());
}

// moveAllPointsRightBy 메서드 동작 확인
@Test
public void testMoveAllPointsRightBy() throws Exception {
    List<Point> points = 
        Arrays.asList(new Point(5, 5), new Point(10,5));
    List<Point> expectedPoints = 
        Arrays.asList(new Point(15, 5), new Point(20,5));
    List<Point> newPoints = Point.moveAllPointsRightBy(points, 10);
    assertEquals(expectedPoints, newPoints);
}

 

3 - 복잡한 람다를 개별 메서드로 분할하기

 

테스트 코드에서 람다 표현식을 참조할 수 없는데 

복잡한 람다 표현식을 어떻게 테스트 할것인가?

-> 9.1.3 절에서 처럼 람다 표현식을 메서드 참조로 바꾸는 것이다.

그러면 일반 메서드를 테스트하듯 람다표현식을 테스트 할 수있음.

 

 

4 -  고차원 함수 테스팅

 

함수를 인수로 받거나 다른함수를 반환하는 메서드는 사용하기 어려움.

메서드가 람다를 인수로 받는다면 다른 람다로 메서드 동작 테스트 가능

 

ex) 2장의 프레디케이트로 만든 filter 메서드 테스트

 

@Test
public void testFilter() throws Exception {
    List<Integer> numbers = Arrays.asList(1,2,3,4);
    List<Integer> even = filter(number, i -> i % 2 == 0);
    List<Integer> smallerThanThree = filter(number, i-> i<3);
    assertEquals(Arrays.asList(2,4), even);
    assertEquals(Arrays.asList(1,2), smallerThanThree);
}

 

테스트할 메서드가 다른 함수를 반환한다면? - 함수형 인터페이스의 인스턴스로 간주하고 함수의 동작을 테스트.


9.4 디버깅

 

람다 표현식과 스트림은 기존의 디버깅 기법을 무력화 한다.

 

1 - 스택 트레이스 확인

 

예외발생시 어디에서 멈췄고 어떻게 멈추게 됬는지 살펴봐야 하는데 

바로 스택 프레임 stack frame 에서 이 정보를 얻을수 있음.

프로그램이 메서드를 호출할 때마나 호출위치, 인수값 등의 호출정보가 생성되며 스택프레임에 저장됨.

 

어떻게 멈추게 되었는지 프레임별로 보여주는 스택 트레이스 stack trace를 얻을수 있음. 

 

람다와 스택 트레이스 

 

- 람다 표현식은 이름이 없으므로 컴파일러가 람다를 참조하는 이름을 만들어냄.

- 메서드 참조를 사용해도 스택 트레이스에는 메서드 명이 나타나지 않음.

 

- 메서드 참조를 사용하는 클래스와 같은곳에 선언되어 있는 메서드를 참조할때는 

스택트레이스에 메서드 참조 이름이 나타남.

 

2 - 정보 로깅

 

스트림 파이프라인 연산을 어떻게 디버깅 할 수 있을까?

forEach 로 스트림결과를 출력하거나 로깅할수있다.

하지만 forEach를 호출하는 순간 전체 스트림이 소비됨.

 

스트림 파이프라인에 적용된 각각의 연산(map, filter, limit)이 어떤 결과를 도출하는지 확인하고 싶다.

-> peek 스트림 연산을 활용

 

peek은 실제로 스트림의 요소를 소비하지 않고

자신이 확인한 요소를 파이프라인의 다음연산으로 그대로 전달함

 

 


  • 람다 표현식으로 가독성 좋고 유연한 코드
  • 익명클래스는 람다 표현식으로 바꾸는 것이 좋다. 이때 this, 변수섀도 등 주의.
  • 메서드 참조로 람다 표현식보다 더 가독성 좋은 코드 구현
  • 반복적으로 컬렉션을 처리하는 루틴은 스트림 API 로 대체가능한지 고려
  • 람다 표현식으로 전략, 템플릿메서드, 옵저버, 의무 체인, 팩토리 등 객체지향 디자인 패턴에서 발생하는 불필요한 코드 제거
  • 람다 표현식으로 단위테스트 수행. 람다 표현식 자체를 테스트하는것보다 사용되는 메서드의 동작을 테스트.
  • 복잡한 람다 표현식은 일반 메서드로 재구현
  • 람다 표현식을 사용하면 스택 트레이스를 이해하기 어려움
  • 스트림 파이프라인에서 요소를 처리할때 peek메서드로 중간값 확인 가능.