Post

🐹 chap9. λ¦¬νŒ©ν„°λ§, ν…ŒμŠ€νŒ…, 디버깅

9.1 가독성과 μœ μ—°μ„±μ„ κ°œμ„ ν•˜λŠ” λ¦¬νŒ©ν„°λ§

9.1.1 μ½”λ“œ 가독성 κ°œμ„ 

μ½”λ“œ 가독성이 μ’‹λ‹€ β†’ μ–΄λ–€ μ½”λ“œλ₯Ό λ‹€λ₯Έ μ‚¬λžŒλ„ μ‰½κ²Œ 이해할 수 μžˆλ‹€.

μ½”λ“œ 가독성을 κ°œμ„ ν•œλ‹€λŠ” 것은 μš°λ¦¬κ°€ κ΅¬ν˜„ν•œ μ½”λ“œλ₯Ό λ‹€λ₯Έ μ‚¬λžŒμ΄ μ‰½κ²Œ μ΄ν•΄ν•˜κ³  μœ μ§€λ³΄μˆ˜ν•  수 있게 λ§Œλ“œλŠ” 것을 μ˜λ―Έν•˜λ‹€. μ½”λ“œ 가독성을 높이렀면 μ½”λ“œμ˜ λ¬Έμ„œν™”λ₯Ό μž˜ν•˜κ³ , ν‘œμ€€ μ½”λ”© κ·œμΉ™μ„ μ€€μˆ˜ν•˜λŠ” λ“±μ˜ λ…Έλ ₯을 κΈ°μšΈμ—¬μ•Ό ν•œλ‹€.

[ μ½”λ“œ 가독성을 κ°œμ„ ν•  수 μžˆλŠ” μ˜ˆμ‹œ ]

  • 읡λͺ… 클래슀λ₯Ό λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ¦¬νŒ©ν„°λ§
  • λžŒλ‹€ ν‘œν˜„μ‹μ„ λ©”μ„œλ“œ 참쑰둜 λ¦¬νŒ©ν„°λ§
  • λͺ…λ Ήν˜• 데이터 처리λ₯Ό 슀트림으둜 λ¦¬νŒ©ν„°λ§

9.1.2 읡λͺ… 클래슀λ₯Ό λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ¦¬νŒ©ν„°λ§ν•˜κΈ°

ν•˜λ‚˜μ˜ 좔상 λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” 읡λͺ… ν΄λž˜μŠ€λŠ” λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ¦¬νŒ©ν„°λ§ν•  수 μžˆλ‹€.

  • Runnable 객체λ₯Ό λ§Œλ“œλŠ” 읡λͺ… ν΄λž˜μŠ€μ™€ 이에 λŒ€μ‘ν•˜λŠ” λžŒλ‹€ ν‘œν˜„μ‹
1
2
3
4
5
6
7
8
// 읡λͺ… 클래슀 
Runnable r1 = new Runnable() {
    public void run() {
        System.out.println("Hello");
    }
};
// λžŒλ‹€ ν‘œν˜„μ‹
Runnable r2 = () -> System.out.println("Hello");

ν•˜μ§€λ§Œ λͺ¨λ“  읡λͺ… 클래슀λ₯Ό λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ³€ν™˜ν•  수 μžˆλŠ” 것은 μ•„λ‹ˆλ‹€.

  1. 읡λͺ… ν΄λž˜μŠ€μ—μ„œ μ‚¬μš©ν•œ this와 superλŠ” λžŒλ‹€ ν‘œν˜„μ‹μ—μ„œ λ‹€λ₯Έ 의미λ₯Ό κ°–λŠ”λ‹€.
    • 읡λͺ… 클래슀 this : 읡λͺ… 클래슀 μžμ‹ 
    • λžŒλ‹€ this : λžŒλ‹€λ₯Ό κ°μ‹ΈλŠ” 클래슀λ₯Ό 가리킨닀
  2. 읡λͺ… ν΄λž˜μŠ€λŠ” 감싸고 μžˆλŠ” 클래슀의 λ³€μˆ˜λ₯Ό 가릴 수 μžˆλ‹€. (섀도 λ³€μˆ˜)

    ν•˜μ§€λ§Œ λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œλŠ” λ³€μˆ˜λ₯Ό 가릴 수 μ—†λ‹€.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
     int a = 10;
     Runnable r1 = () -> {
         int a = 2; // μ»΄νŒŒμΌμ—λŸ¬
         System.out.println(a);
     };
        
     Runnable r2 = new Runnable() {
         public void run() {
             int a = 2;
             System.out.println(a);
         }
     };
    
  3. 읡λͺ… ν΄λž˜μŠ€λŠ” λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ°”κΎΈλ©΄ μ½˜ν…μŠ€νŠΈ μ˜€λ²„λ‘œλ”©μ— λ”°λ₯Έ λͺ¨ν˜Έν•¨μ΄ 초래될 수 μžˆλ‹€.

    읡λͺ… ν΄λž˜μŠ€λŠ” μΈμŠ€ν„΄μŠ€ν™”ν•  λ•Œ λͺ…μ‹œμ μœΌλ‘œ ν˜•μ‹μ΄ μ •ν•΄μ§€λŠ” 반면 λžŒλ‹€μ˜ ν˜•μ‹μ€ μ½˜ν…μŠ€νŠΈμ— 따라 달라지기 λ•Œλ¬Έμ΄λ‹€.

    1
    2
    3
    4
    5
    
     interface Task {
         public void execute();
     }
     public static void doSomething(Runnable r){ r.run(); }
     public static void doSomething(Task a){ a.execute(); }
    

    Taskλ₯Ό κ΅¬ν˜„ν•˜λŠ” 읡λͺ… 클래슀λ₯Ό 전달할 수 μžˆλ‹€.

    1
    2
    3
    4
    5
    6
    
     // Task 지정
     doSomething(new Task() {
         public void execute() {
             System.out.println("Danger danger!!");
         }
     });
    

    ν•˜μ§€λ§Œ 읡λͺ… 클래슀λ₯Ό λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ λ°”κΎΈλ©΄ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œ Runnableκ³Ό Task λͺ¨λ‘ λŒ€μƒ ν˜•μ‹μ΄ 될 수 μžˆμœΌλ―€λ‘œ λ¬Έμ œκ°€ 생긴닀.

    1
    2
    
     // Task인지 Runnable인지 λͺ…ν™•νžˆ μ•Œ 수 μ—†μŒ 
     doSomething(() -> System.out.println("Danger danger!!");
    

    즉, doSomething(Runnable)κ³Ό doSomething(Task) 쀑 μ–΄λŠ 것을 κ°€λ¦¬ν‚€λŠ”μ§€ μ•Œ 수 μ—†λŠ” λͺ¨ν˜Έν•¨μ΄ λ°œμƒν•œλ‹€.

    λͺ…μ‹œμ  ν˜•λ³€ν™˜ (Task)λ₯Ό μ΄μš©ν•΄ λͺ¨ν˜Έν•¨μ„ μ œκ±°ν•  수 μžˆλ‹€.

    1
    2
    
     // λͺ…μ‹œμ  ν˜•λ³€ν™˜ 
     doSomething((Task)() -> System.out.println("Danger danger!!"));
    

    ν•˜μ§€λ§Œ λ„·λΉˆμ¦ˆμ™€ IntelliJ 등을 ν¬ν•¨ν•œ λŒ€λΆ€λΆ„μ˜ 톡합 개발 ν™˜κ²½(IDE)μ—μ„œ μ œκ³΅ν•˜λŠ” λ¦¬νŒ©ν„°λ§ κΈ°λŠ₯을 μ΄μš©ν•˜λ©΄ μœ„ 같은 λ¬Έμ œκ°€ μžλ™μœΌλ‘œ ν•΄κ²°λœλ‹€.

9.1.3 λžŒλ‹€ ν‘œν˜„μ‹μ„ λ©”μ„œλ“œ 참쑰둜 λ¦¬νŒ©ν„°λ§ν•˜κΈ°

λžŒλ‹€ ν‘œν˜„μ‹ λŒ€μ‹  λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό μ΄μš©ν•˜λ©΄ 가독성을 높일 수 μžˆλ‹€.

λ©”μ„œλ“œ 참쑰의 λ©”μ„œλ“œλͺ…μœΌλ‘œ μ½”λ“œμ˜ μ˜λ„λ₯Ό λͺ…ν™•ν•˜κ²Œ μ•Œλ¦΄ 수 있기 λ•Œλ¬Έμ΄λ‹€.

1
2
3
4
5
6
7
8
9
// 칼둜리 μˆ˜μ€€μœΌλ‘œ μš”λ¦¬ κ·Έλ£Ήν™” 
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에 인수둜 전달할 수 μžˆλ‹€. λ‹€μŒμ²˜λŸΌ μ½”λ“œκ°€ κ°„κ²°ν•˜κ³  μ˜λ„λ„ λͺ…확해진닀.

1
2
3
// λžŒλ‹€ ν‘œν˜„μ‹μ„ λ©”μ„œλ“œλ‘œ μΆ”μΆœ
Map<CaloricLevel, List<Dish>> dishesByCaloricLevel =
    menu.stream().collect(groupingBy(Dish::getCaloricLevel));

이제 Dish ν΄λž˜μŠ€μ— getCaloricLevel λ©”μ„œλ“œλ₯Ό μΆ”κ°€ν•œλ‹€.

1
2
3
4
5
6
7
8
public class Dish {
    ...
    public CaloricLevel getCaloricLevel() {
        if (this.getCalories() <= 400) return CaloricLevel.DIET;
        else if (this.getCalories() <= 700) return CaloricLevel.NORMAL;
        else return CaloricLevel.FAT;
    }
}

λ˜ν•œ comparingκ³Ό maxBy 같은 정적 헬퍼 λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜λŠ” 것도 μ’‹λ‹€. 이듀은 λ©”μ„œλ“œ 참쑰와 μ‘°ν™”λ₯Ό 이루도둝 μ„€κ³„λ˜μ—ˆλ‹€.

1
2
3
4
5
// 비ꡐ κ΅¬ν˜„μ— μ‹ κ²½ 써야 ν•œλ‹€. 
inventory.sort(
    (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
// μ½”λ“œκ°€ 문제 자체λ₯Ό μ„€λͺ…ν•œλ‹€. 
inventory.sort(comparing(Apple::getWeight));

sum, maximum λ“± 자주 μ‚¬μš©ν•˜λŠ” 리듀싱 연산은 λ©”μ„œλ“œ 참쑰와 ν•¨κ»˜ μ‚¬μš©ν•  수 μžˆλŠ” λ‚΄μž₯ 헬퍼 λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.

μ΅œλŒ“κ°’μ΄λ‚˜ 합계λ₯Ό 계산할 λ•Œ λžŒλ‹€ ν‘œν˜„μ‹κ³Ό μ €μˆ˜μ€€ 리듀싱 연산을 μ‘°ν•©ν•˜λŠ” 것보닀 Collectors APIλ₯Ό μ‚¬μš©ν•˜λ©΄ μ½”λ“œμ˜ μ˜λ„κ°€ 더 λͺ…확해진닀.

1
2
3
4
// μ €μˆ˜μ€€ 리듀싱 연산을 μ‘°ν•©ν•œ μ½”λ“œ
int totalCalories =
    menu.stream().map(Dish::getCalories)
        .reduce(0, (c1, c2) -> c1 + c2);

λ‚΄μž₯ 컬렉터λ₯Ό μ΄μš©ν•˜λ©΄ μ½”λ“œ 자체둜 문제λ₯Ό 더 λͺ…ν™•ν•˜κ²Œ μ„€λͺ…ν•  수 μžˆλ‹€.

1
2
// 컬렉터 summingIntλ₯Ό μ‚¬μš©(μžμ‹ μ΄ μ–΄λ–€ λ™μž‘μ„ μˆ˜ν–‰ν•˜λŠ”μ§€ λ©”μ„œλ“œ μ΄λ¦„μœΌλ‘œ μ„€λͺ…)
int totalCalories = menu.stream().collect(summingInt(Dish::getCalories));

9.1.4 λͺ…λ Ήν˜• 데이터 처리λ₯Ό 슀트림으둜 λ¦¬νŒ©ν„°λ§ν•˜κΈ°

슀트림 APIλŠ” 데이터 처리 νŒŒμ΄ν”„λΌμΈμ˜ μ˜λ„λ₯Ό 더 λͺ…ν™•νžˆ 보여주기 λ•Œλ¬Έμ— 반볡자λ₯Ό μ΄μš©ν•œ 기쑴의 λͺ¨λ“  μ»¬λ ‰μ…˜ 처리 μ½”λ“œλ₯Ό 슀트림 API둜 λ°”κΏ”μ•Ό ν•œλ‹€.

μŠ€νŠΈλ¦Όμ€ μ‡ΌνŠΈμ„œν‚·κ³Ό κ²ŒμœΌλ¦„μ΄λΌλŠ” κ°•λ ₯ν•œ μ΅œμ ν™”λΏ μ•„λ‹ˆλΌ λ©€ν‹°μ½”μ–΄ μ•„ν‚€ν…μ²˜λ₯Ό ν™œμš©ν•  수 μžˆλŠ” 지름길을 μ œκ³΅ν•œλ‹€.

λ‹€μŒ λͺ…λ Ήν˜• μ½”λ“œλŠ” 필터링 + μΆ”μΆœλ‘œ 엉킨 μ½”λ“œλ‹€.

ν•΄λ‹Ή μ½”λ“œλŠ” μ˜λ„λ₯Ό νŒŒμ•…ν•˜κΈ° μ–΄λ ΅κ³  λ³‘λ ¬λ‘œ μ‹€ν–‰μ‹œν‚€λŠ” 것이 맀우 μ–΄λ ΅λ‹€.

1
2
3
4
5
6
List<String> dishNames = new ArrayList<>();
for(Dish dish: menu) {
    if(dish.getCalories() > 300) {
        dishNames.add(dish.getNames());
    }
}

슀트림 APIλ₯Ό μ΄μš©ν•˜λ©΄ 문제λ₯Ό 더 μ§μ ‘μ μœΌλ‘œ κΈ°μˆ ν•  수 μžˆμ„ 뿐 μ•„λ‹ˆλΌ μ‰½κ²Œ 병렬화할 수 μžˆλ‹€.

1
2
3
4
menu.parallelStream()
    .filter(d -> d.getCalories() > 300)
    .map(Dish::getName)
    .collect(toList());

λͺ…λ Ήν˜• μ½”λ“œμ˜ break, continue, return λ“±μ˜ μ œμ–΄ 흐름문을 λͺ¨λ‘ λΆ„μ„ν•΄μ„œ 같은 κΈ°λŠ₯을 μˆ˜ν–‰ν•˜λŠ” 슀트림 μ—°μ‚°μœΌλ‘œ μœ μΆ”ν•΄μ•Ό ν•˜λ―€λ‘œ λͺ…λ Ήν˜• μ½”λ“œλ₯Ό 슀트림 API둜 λ°”κΎΈλŠ” 것은 μ‰¬μš΄ 일이 μ•„λ‹ˆλ‹€.

9.1.5 μ½”λ“œ μœ μ—°μ„± κ°œμ„ 

λžŒλ‹€ ν‘œν˜„μ‹μ„ μ΄μš©ν•˜λ©΄ λ™μž‘ νŒŒλΌλ―Έν„°ν™”λ₯Ό μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆλ‹€. 즉, λ‹€μ–‘ν•œ λžŒλ‹€λ₯Ό μ „λ‹¬ν•΄μ„œ λ‹€μ–‘ν•œ λ™μž‘μ„ ν‘œν˜„ν•  수 μžˆλ‹€. λ”°λΌμ„œ λ³€ν™”ν•˜λŠ” μš”κ΅¬μ‚¬ν•­μ— λŒ€μ‘ν•˜λŠ” μ½”λ“œλ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€.

ex) ν”„λ ˆλ””μΌ€μ΄νŠΈλ‘œ λ‹€μ–‘ν•œ 필터링 κΈ°λŠ₯ κ΅¬ν˜„, λΉ„κ΅μžλ‘œ λ‹€μ–‘ν•œ 비ꡐ κΈ°λŠ₯ λ§Œλ“€κΈ°

ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ 적용

λžŒλ‹€ ν‘œν˜„μ‹μ„ μ΄μš©ν•˜λ €λ©΄ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€κ°€ ν•„μš”ν•˜λ‹€. λ”°λΌμ„œ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ½”λ“œμ— μΆ”κ°€ν•΄μ•Ό ν•œλ‹€.

쑰건뢀 μ—°κΈ° μ‹€ν–‰(conditional deferred execution)κ³Ό μ‹€ν–‰ μ–΄λΌμš΄λ“œ(execute around) 두 가지 자주 μ‚¬μš©ν•˜λŠ” νŒ¨ν„΄μœΌλ‘œ λžŒλ‹€ ν‘œν˜„μ‹ λ¦¬νŒ©ν„°λ§μ„ μ§„ν–‰ν•œλ‹€.

쑰건뢀 μ—°κΈ° μ‹€ν–‰

μ‹€μ œ μž‘μ—…μ„ μ²˜λ¦¬ν•˜λŠ” μ½”λ“œ 내뢀에 μ œμ–΄ 흐름문이 λ³΅μž‘ν•˜κ²Œ μ–½νžŒ μ½”λ“œλ₯Ό ν”νžˆ λ³Ό 수 μžˆλ‹€.

1
2
3
4
// λ‚΄μž₯ μžλ°” Logger 클래슀λ₯Ό μ‚¬μš©ν•˜λŠ” 예제
if (logger.isLoggable(Log.FINER)) {
    logger.final("Problem: " + generateDiagnostic());
}

μœ„ μ½”λ“œλŠ” λ‹€μŒκ³Ό 같은 λ¬Έμ œκ°€ μžˆλ‹€.

  • logger의 μƒνƒœκ°€ isLoggableμ΄λΌλŠ” λ©”μ„œλ“œμ— μ˜ν•΄ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλ‘œ λ…ΈμΆœλœλ‹€.
  • λ©”μ‹œμ§€λ₯Ό λ‘œκΉ…ν•  λ•Œλ§ˆλ‹€ logger 객체의 μƒνƒœλ₯Ό 맀번 ν™•μΈν•˜λŠ” 것은 μ½”λ“œλ₯Ό μ–΄μ§€λŸ½νžˆλŠ” 것 뿐이닀.

메세지λ₯Ό λ‘œκΉ…ν•˜κΈ° 전에 logger 객체가 μ μ ˆν•œ μˆ˜μ€€μœΌλ‘œ μ„€μ •λ˜μ—ˆλŠ”μ§€ λ‚΄λΆ€μ μœΌλ‘œ ν™•μΈν•˜λŠ” log λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜λŠ” 것이 λ°”λžŒμ§ν•˜λ‹€.

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

덕뢄에 λΆˆν•„μš”ν•œ if문을 μ œκ±°ν•  수 있으며 logger의 μƒνƒœλ₯Ό λ…ΈμΆœν•  ν•„μš”κ°€ μ—†λ‹€.

ν•˜μ§€λ§Œ 인수둜 μ „λ‹¬λœ 메세지 μˆ˜μ€€μ—μ„œ loggerκ°€ ν™œμ„±ν™”λ˜μ–΄ μžˆμ§€ μ•Šλ”λΌλ„ 항상 λ‘œκΉ… λ©”μ‹œμ§€λ₯Ό ν‰κ°€ν•˜κ²Œ λœλ‹€.

λžŒλ‹€λ₯Ό μ΄μš©ν•΄ ν•΄λ‹Ή 문제λ₯Ό ν•΄κ²°ν•œλ‹€. νŠΉμ •μ‘°κ±΄(logger μˆ˜μ€€μ„ FINER둜 μ„€μ •)μ—μ„œλ§Œ λ©”μ‹œμ§€κ°€ 생성될 수 μžˆλ„λ‘ λ©”μ‹œμ§€ 생성 과정을 μ—°κΈ°ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€.

μžλ°” 8 APIμ—μ„œλŠ” logger 문제λ₯Ό ν•΄κ²°ν•  수 μžˆλ„λ‘ Supplierλ₯Ό 인수둜 κ°–λŠ” μ˜€λ²„λ‘œλ“œλœ logλ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.

1
2
// μƒˆλ‘œ μΆ”κ°€λœ log λ©”μ„œλ“œμ˜ μ‹œκ·Έλ‹ˆμ²˜
public void log(Level level, Supplier<String> msgSupplier)

λ‹€μŒμ²˜λŸΌ log λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  수 μžˆλ‹€.

1
2
// λžŒλ‹€ μ‚¬μš©
logger.log(Level.FINER, () -> "Problem: " + generateDiagnostic());

log λ©”μ„œλ“œλŠ” logger의 μˆ˜μ€€μ΄ μ μ ˆν•˜κ²Œ μ„€μ •λ˜μ–΄ μžˆμ„ λ•Œλ§Œ 인수둜 λ„˜κ²¨μ§„ λžŒλ‹€λ₯Ό λ‚΄λΆ€μ μœΌλ‘œ μ‹€ν–‰ν•œλ‹€. λ‹€μŒμ€ log λ©”μ„œλ“œμ˜ λ‚΄λΆ€ κ΅¬ν˜„ μ½”λ“œλ‹€.

1
2
3
4
5
public void log(Level level, Supplier<String> msgSupplier) {
    if (logger.isLoggable(level)) {
        log(level, msgSupplier.get()); // λžŒλ‹€ μ‹€ν–‰
    }
}

λ§Œμ•½ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œμ—μ„œ 객체 μƒνƒœλ₯Ό 자주 ν™•μΈν•˜κ±°λ‚˜(ex. logger의 μƒνƒœ), 객체의 일뢀 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” 상황(ex. λ©”μ‹œμ§€ λ‘œκΉ…)이라면 λ‚΄λΆ€μ μœΌλ‘œ 객체의 μƒνƒœλ₯Ό ν™•μΈν•œ λ‹€μŒμ— λ©”μ„œλ“œλ₯Ό 호좜(λžŒλ‹€λ‚˜ λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό 인수둜 μ‚¬μš©)ν•˜λ„λ‘ μƒˆλ‘œμš΄ λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜λŠ” 것이 μ’‹λ‹€. 그러면 μ½”λ“œ 가독성이 μ’‹μ•„μ§ˆ 뿐 μ•„λ‹ˆλΌ μΊ‘μŠν™”λ„ κ°•ν™”λœλ‹€(객체 μƒνƒœκ°€ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλ‘œ λ…ΈμΆœλ˜μ§€ μ•ŠλŠ”λ‹€)!

μ‹€ν–‰ μ–΄λΌμš΄λ“œ

맀번 같은 μ€€λΉ„, μ’…λ£Œ 과정을 반볡적으둜 μˆ˜ν–‰ν•˜λŠ” μ½”λ“œκ°€ μžˆλ‹€λ©΄ 이λ₯Ό λžŒλ‹€λ‘œ λ³€ν™˜ν•  수 μžˆλ‹€. μ€€λΉ„, μ’…λ£Œ 과정을 μ²˜λ¦¬ν•˜λŠ” λ‘œμ§μ„ μž¬μ‚¬μš©ν•¨μœΌλ‘œμ¨ μ½”λ“œ 쀑볡을 쀄일 수 μžˆλ‹€.

μ•„λž˜ μ½”λ“œλŠ” νŒŒμΌμ„ μ—΄κ³  닫을 λ•Œ 같은 λ‘œμ§μ„ μ‚¬μš©ν–ˆμ§€λ§Œ λžŒλ‹€λ₯Ό μ΄μš©ν•΄ λ‹€μ–‘ν•œ λ°©μ‹μœΌλ‘œ νŒŒμΌμ„ μ²˜λ¦¬ν•  수 μžˆλ„λ‘ νŒŒλΌλ―Έν„°ν™”λ˜μ—ˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
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/chap9/data.txt"))) {
        return p.process(br); //인수둜 μ „λ‹¬λœ BufferedReaderProcessorλ₯Ό μ‹€ν–‰
    }
} // IOException을 던질 수 μžˆλŠ” λžŒλ‹€μ˜ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ 
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

λžŒλ‹€λ‘œ BufferedReader 객체의 λ™μž‘μ„ κ²°μ •ν•  수 μžˆλŠ” 것은 ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€ BufferedReaderProcessor 덕뢄이닀.

9.2 λžŒλ‹€λ‘œ 객체지ν–₯ λ””μžμΈ νŒ¨ν„΄ λ¦¬νŒ©ν„°λ§ν•˜κΈ°

언어에 μƒˆλ‘œμš΄ κΈ°λŠ₯이 μΆ”κ°€λ˜λ©΄μ„œ κΈ°μ‘΄ μ½”λ“œ νŒ¨ν„΄μ΄λ‚˜ κ΄€μš©μ½”λ“œμ˜ 인기가 식기도 ν•œλ‹€.

μžλ°” 5: for-each λ£¨ν”„λŠ” μ—λŸ¬ λ°œμƒλ₯ μ΄ 적으며 κ°„κ²°ν•˜λ―€λ‘œ 기쑴의 반볡자 μ½”λ“œλ₯Ό λŒ€μ²΄ν–ˆλ‹€.

μžλ°” 7: 닀이아λͺ¬λ“œ μ—°μ‚°μž <> λ•Œλ¬Έμ— 기쑴의 μ œλ„€λ¦­ μΈμŠ€ν„΄μŠ€λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μƒμ„±ν•˜λŠ” λΉˆλ„κ°€ μ€„μ—ˆλ‹€.

λ‹€μ–‘ν•œ νŒ¨ν„΄μ„ μœ ν˜•λ³„λ‘œ μ •λ¦¬ν•œ 것이 λ””μžμΈ νŒ¨ν„΄(design pattern)이닀.

λ””μžμΈ νŒ¨ν„΄μ€ 곡톡적인 μ†Œν”„νŠΈμ›¨μ–΄ 문제λ₯Ό 섀계할 λ•Œ μž¬μ‚¬μš©ν•  수 μžˆλŠ” κ²€μ¦λœ 청사진을 μ œκ³΅ν•œλ‹€. λͺ‡ 가지 μ˜ˆμ‹œλŠ” λ‹€μŒκ³Ό κ°™λ‹€.

  • ꡬ쑰체와 λ™μž‘ν•˜λŠ” μ•Œκ³ λ¦¬μ¦˜μ„ μ„œλ‘œ λΆ„λ¦¬ν•˜κ³  싢을 λ•Œ 방문자 λ””μžμΈ νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 μžˆλ‹€.
  • μ‹±κΈ€ν„΄ νŒ¨ν„΄μ„ μ΄μš©ν•΄μ„œ 클래슀 μΈμŠ€ν„΄μŠ€ν™”λ₯Ό ν•˜λ‚˜μ˜ 객체둜 μ œν•œν•  수 μžˆλ‹€.

λžŒλ‹€λ₯Ό μ΄μš©ν•˜λ©΄ λ””μžμΈ νŒ¨ν„΄μœΌλ‘œ ν•΄κ²°ν•˜λ˜ 문제λ₯Ό 더 κ°„λ‹¨ν•˜κ²Œ ν•΄κ²°ν•˜κ³ , 기쑴의 λ§Žμ€ 객체지ν–₯ λ””μžμΈ νŒ¨ν„΄μ„ μ œκ±°ν•˜κ±°λ‚˜ κ°„κ²°ν•˜κ²Œ μž¬κ΅¬ν˜„ν•  수 μžˆλ‹€.

λ‹€μŒ λ‹€μ„― 가지 νŒ¨ν„΄μ„ μ‚΄νŽ΄λ³Έλ‹€.

  • μ „λž΅(strategy)
  • ν…œν”Œλ¦Ώ λ©”μ„œλ“œ(template method)
  • μ˜΅μ €λ²„(observer)
  • 의무 체인(chain of responsibility)
  • νŒ©ν† λ¦¬(factory)

9.2.1 μ „λž΅

μ „λž΅ νŒ¨ν„΄

ν•œ μœ ν˜•μ˜ μ•Œκ³ λ¦¬μ¦˜μ„ λ³΄μœ ν•œ μƒνƒœμ—μ„œ λŸ°νƒ€μž„μ— μ μ ˆν•œ μ•Œκ³ λ¦¬μ¦˜μ„ μ„ νƒν•˜λŠ” 기법

κ·Έλ¦Όκ³Ό 같이 μ „λž΅ νŒ¨ν„΄μ€ μ„Έ λΆ€λΆ„μœΌλ‘œ κ΅¬μ„±λœλ‹€.

  • μ•Œκ³ λ¦¬μ¦˜μ„ λ‚˜νƒ€λ‚΄λŠ” μΈν„°νŽ˜μ΄μŠ€(Strategy μΈν„°νŽ˜μ΄μŠ€)
  • λ‹€μ–‘ν•œ μ•Œκ³ λ¦¬μ¦˜μ„ λ‚˜νƒ€λ‚΄λŠ” ν•œ 개 μ΄μƒμ˜ μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„(ConcreteStrategyA, ConcreteStrategyB 같은 ꡬ체적인 κ΅¬ν˜„ 클래슀)
  • μ „λž΅ 객체λ₯Ό μ‚¬μš©ν•˜λŠ” ν•œ 개 μ΄μƒμ˜ ν΄λΌμ΄μ–ΈνŠΈ

예λ₯Ό λ“€μ–΄ 였직 μ†Œλ¬Έμž λ˜λŠ” 숫자둜 이루어져야 ν•˜λŠ” λ“± ν…μŠ€νŠΈ μž…λ ₯이 λ‹€μ–‘ν•œ 쑰건에 맞게 포맷 λ˜μ–΄ μžˆλŠ”μ§€ κ²€μ¦ν•œλ‹€κ³  κ°€μ •ν•˜μž. λ¨Όμ € String λ¬Έμžμ—΄μ„ κ²€μ¦ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λΆ€ν„° κ΅¬ν˜„ν•œλ‹€.

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

μ΄λ²ˆμ—λŠ” μœ„μ—μ„œ μ •μ˜ν•œ μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” 클래슀λ₯Ό ν•˜λ‚˜ 이상 μ •μ˜ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
// 였직 μ†Œλ¬Έμžλ‘œ μ΄λ£¨μ–΄μ‘ŒλŠ”μ§€ 검증
public class IsAllLowerCase 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+");
    }
}

μ§€κΈˆκΉŒμ§€ κ΅¬ν˜„ν•œ 클래슀λ₯Ό λ‹€μ–‘ν•œ 검증 μ „λž΅μœΌλ‘œ ν™œμš©ν•  수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 = numericValidator.validate("aaaa"); // false λ°˜ν™˜
Validator lowerCaseValidator = new Validator(new IsAllLovwerCase());
boolean b2 = lowerCaseValidator.validate("bbbb"); // true λ°˜ν™˜ 

λžŒλ‹€ ν‘œν˜„μ‹ μ‚¬μš©

ValidationStrategyλŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ©° Predicateκ³Ό 같은 ν•¨μˆ˜ λ””μŠ€ν¬λ¦½ν„°λ₯Ό κ°–κ³  μžˆλ‹€. λ”°λΌμ„œ λ‹€μ–‘ν•œ μ „λž΅μ„ κ΅¬ν˜„ν•˜λŠ” μƒˆλ‘œμš΄ 클래슀λ₯Ό κ΅¬ν˜„ν•  ν•„μš” 없이 λžŒλ‹€ ν‘œν˜„μ‹μ„ 직접 μ „λ‹¬ν•˜λ©΄ μ½”λ“œκ°€ 간결해진닀.

1
2
3
4
5
6
7
Validator numericValidator = 
    new Validator((String s) -> s.matches("[a-z]+")); // λžŒλ‹€λ₯Ό 직접 전달
boolean b1 = numericValidator.validate("aaaa");

Validator lowerCaseValidator = 
    new Validator((String s) -> s.matches("\\d+")); // λžŒλ‹€λ₯Ό 직접 전달
boolean b2 = lowerCaseValidator.validate("bbbb");

μœ„ μ½”λ“œμ—μ„œ 확인할 수 μžˆλ“―μ΄ λžŒλ‹€ ν‘œν˜„μ‹μ„ μ΄μš©ν•˜λ©΄ μ „λž΅ λ””μžμΈ νŒ¨ν„΄μ—μ„œ λ°œμƒν•˜λŠ” μžμž˜ν•œ μ½”λ“œλ₯Ό μ œκ±°ν•  수 μžˆλ‹€. λžŒλ‹€ ν‘œν˜„μ‹μ€ μ½”λ“œ 쑰각(λ˜λŠ” μ „λž΅)을 μΊ‘μŠν™”ν•œλ‹€. 즉, λžŒλ‹€ ν‘œν˜„μ‹μœΌλ‘œ μ „λž΅ λ””μžμΈ νŒ¨ν„΄μ„ λŒ€μ‹ ν•  수 μžˆλ‹€.

9.2.2 ν…œν”Œλ¦Ώ λ©”μ„œλ“œ

ν…œν”Œλ¦Ώ λ©”μ„œλ“œ

β€˜μ΄ μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜κ³  싢은데 κ·ΈλŒ€λ‘œλŠ” μ•ˆ 되고 쑰금 고쳐야 ν•˜λŠ”β€™ 상황에 μ ν•©ν•˜λ‹€.

κ°„λ‹¨ν•œ 온라인 λ±…ν‚Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ κ΅¬ν˜„ν•œλ‹€κ³  κ°€μ •ν•˜μž. μ‚¬μš©μžκ°€ 고객 IDλ₯Ό μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ— μž…λ ₯ν•˜λ©΄ 은행 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ 고객 정보λ₯Ό κ°€μ Έμ˜€κ³  고객이 μ›ν•˜λŠ” μ„œλΉ„μŠ€λ₯Ό μ œκ³΅ν•  수 μžˆλ‹€.

예λ₯Ό λ“€μ–΄ 고객 κ³„μ’Œμ— λ³΄λ„ˆμŠ€λ₯Ό μž…κΈˆν•œλ‹€κ³  κ°€μ •ν•˜μž. μ€ν–‰λ§ˆλ‹€ λ‹€μ–‘ν•œ 온라인 λ±…ν‚Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‚¬μš©ν•˜λ©° λ™μž‘ 방법도 λ‹€λ₯΄λ‹€. λ‹€μŒμ€ 온라인 λ±…ν‚Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ λ™μž‘μ„ μ •μ˜ν•˜λŠ” 좔상 ν΄λž˜μŠ€λ‹€.

1
2
3
4
5
6
7
abstract class OnlineBanking {
    public void processCustomer(int id) {
        Customer c = Database.getCustomerWithId(id);
        makeCustomerHappy(c);
    }
    abstract void makeCustomerHappy(Customer c);
}

processCustomer λ©”μ„œλ“œλŠ” 온라인 λ±…ν‚Ή μ•Œκ³ λ¦¬μ¦˜μ΄ ν•΄μ•Ό ν•  일을 보여쀀닀. μš°μ„  주어진 고객 IDλ₯Ό μ΄μš©ν•΄μ„œ 고객을 λ§Œμ‘±μ‹œμΌœμ•Ό ν•œλ‹€. 각각의 지점은 OnlineBanking 클래슀λ₯Ό 상속받아 makeCustomerHappy λ©”μ„œλ“œκ°€ μ›ν•˜λŠ” λ™μž‘μ„ μˆ˜ν–‰ν•˜λ„λ‘ κ΅¬ν˜„ν•  수 μžˆλ‹€.

λžŒλ‹€ ν‘œν˜„μ‹ μ‚¬μš©

λžŒλ‹€λ‚˜ λ©”μ„œλ“œ 참쑰둜 μ•Œκ³ λ¦¬μ¦˜μ— μΆ”κ°€ν•  λ‹€μ–‘ν•œ μ»΄ν¬λ„ŒνŠΈλ₯Ό κ΅¬ν˜„ν•  수 μžˆλ‹€.

이전에 μ •μ˜ν•œ makeCustomerHappy의 λ©”μ„œλ“œ μ‹œκ·Έλ‹ˆμ²˜μ™€ μΌμΉ˜ν•˜λ„λ‘ Consumer ν˜•μ‹μ„ κ°–λŠ” 두 번째 인수λ₯Ό processCustomer에 μΆ”κ°€ν•œλ‹€.

1
2
3
4
public void processCustomer(int id, Consumer<Customer> makeCustomerHappy) {
    Customer c = Database.getCustomerWithId(id);
    makeCustomerHappy.accept(c);
}

이제 onlineBanking 클래슀λ₯Ό 상속받지 μ•Šκ³  직접 λžŒλ‹€ ν‘œν˜„μ‹μ„ μ „λ‹¬ν•΄μ„œ λ‹€μ–‘ν•œ λ™μž‘μ„ μΆ”κ°€ν•  수 μžˆλ‹€.

1
2
new OnlineBankingLambda().processCustomer(1337, (Customer c) ->
    System.out.println("Hello " + c.getName());

λžŒλ‹€ ν‘œν˜„μ‹μ„ μ΄μš©ν•˜λ©΄ ν…œν”Œλ¦Ώ λ©”μ„œλ“œ λ””μžμΈ νŒ¨ν„΄μ—μ„œ λ°œμƒν•˜λŠ” μžμž˜ν•œ μ½”λ“œλ„ μ œκ±°ν•  수 μžˆλ‹€.

9.2.3 μ˜΅μ €λ²„

μ˜΅μ €λ²„ λ””μžμΈ νŒ¨ν„΄

μ–΄λ–€ μ΄λ²€νŠΈκ°€ λ°œμƒν–ˆμ„ λ•Œ ν•œ 객체(주제)κ°€ λ‹€λ₯Έ 객체 리슀트(μ˜΅μ €λ²„)μ—κ²Œ μžλ™μœΌλ‘œ μ•Œλ¦Όμ„ 보내야 ν•˜λŠ” μƒν™©μ—μ„œ μ‚¬μš©

  • GUI μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ—μ„œ μ˜΅μ €λ²„ νŒ¨ν„΄μ΄ 자주 λ“±μž₯ν•œλ‹€. λ²„νŠΌ 같은 GUI μ»΄ν¬λ„ŒνŠΈμ— μ˜΅μ €λ²„λ₯Ό μ„€μ •ν•  수 μžˆλ‹€. 그리고 μ‚¬μš©μžκ°€ λ²„νŠΌμ„ ν΄λ¦­ν•˜λ©΄ μ˜΅μ €λ²„μ— μ•Œλ¦Όμ΄ μ „λ‹¬λ˜κ³  정해진 λ™μž‘μ΄ μˆ˜ν–‰λœλ‹€.
  • μ£Όμ‹μ˜ 가격(주제) 변동에 λ°˜μ‘ν•˜λŠ” λ‹€μˆ˜μ˜ 거래자(μ˜΅μ €λ²„) μ˜ˆμ œμ—μ„œλ„ μ˜΅μ €λ²„ νŒ¨ν„΄μ„ μ‚¬μš©ν•  수 μžˆλ‹€.

μ˜΅μ €λ²„ νŒ¨ν„΄μœΌλ‘œ νŠΈμœ„ν„° 같은 μ»€μŠ€ν„°λ§ˆμ΄μ¦ˆλœ μ•Œλ¦Ό μ‹œμŠ€ν…œμ„ μ„€κ³„ν•˜κ³  κ΅¬ν˜„ν•  수 μžˆλ‹€. λ‹€μ–‘ν•œ μ‹ λ¬Έ 맀체(λ‰΄μš•νƒ€μž„μŠ€, κ°€λ””μ–Έ, λ₯΄λͺ½λ“œ)κ°€ λ‰΄μŠ€ νŠΈμœ—μ„ κ΅¬λ…ν•˜κ³  있으며 νŠΉμ • ν‚€μ›Œλ“œλ₯Ό ν¬ν•¨ν•˜λŠ” νŠΈμœ—μ΄ λ“±λ‘λ˜λ©΄ μ•Œλ¦Όμ„ λ°›κ³  μ‹Άμ–΄ν•œλ‹€.

μš°μ„  λ‹€μ–‘ν•œ μ˜΅μ €λ²„λ₯Ό κ·Έλ£Ήν™”ν•  Observer μΈν„°νŽ˜μ΄μŠ€κ°€ ν•„μš”ν•˜λ‹€.

Observer μΈν„°νŽ˜μ΄μŠ€λŠ” μƒˆλ‘œμš΄ νŠΈμœ—μ΄ μžˆμ„ λ•Œ 주제(Feed)κ°€ ν˜ΈμΆœν•  수 μžˆλ„λ‘ notify라고 ν•˜λŠ” ν•˜λ‚˜μ˜ λ©”μ„œλ“œλ₯Ό μ œκ³΅ν•œλ‹€.

1
2
3
interface Observer {
    void notify(String tweet);
}

이제 νŠΈμœ—μ— ν¬ν•¨ν•œ λ‹€μ–‘ν•œ ν‚€μ›Œλ“œμ— λŒ€ν•œ λ™μž‘μ„ μˆ˜ν–‰ν•  수 μžˆλŠ” μ—¬λŸ¬ μ˜΅μ €λ²„λ₯Ό μ •μ˜ν•  수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class NYTimes implements Observer {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("money")) {
            System.out.println("Breaking news in NY! " + tweet);
        }
    }
}
class Guardian implements Observer {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("queen")) {
            System.out.println("Yet more news from London..." + tweet);
        }
    }
}
class LeMonde implements Observer {
    public void notify(String tweet) {
        if(tweet != null && tweet.contains("wine")) {
            System.out.println("Today cheese, wine and news! " + tweet);
        }
    }
}

그리고 μ£Όμ œλ„ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€. λ‹€μŒμ€ Subject μΈν„°νŽ˜μ΄μŠ€μ˜ μ •μ˜λ‹€.

1
2
3
4
interface Subject {
    void registerObserver(Observer o);
    void notifyObservers(String tweet);
}

μ£Όμ œλŠ” registerObsesrver λ©”μ„œλ“œλ‘œ μƒˆλ‘œμš΄ μ˜΅μ €λ²„λ₯Ό λ“±λ‘ν•œ λ‹€μŒμ— notifyObservers λ©”μ„œλ“œλ‘œ νŠΈμœ—μ˜ μ˜΅μ €λ²„μ— 이λ₯Ό μ•Œλ¦°λ‹€.

1
2
3
4
5
6
7
8
9
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λŠ” νŠΈμœ—μ„ λ°›μ•˜μ„ λ•Œ μ•Œλ¦Όμ„ 보낼 μ˜΅μ €λ²„ 리슀트λ₯Ό μœ μ§€ν•œλ‹€. 이제 μ£Όμ œμ™€ μ˜΅μ €λ²„λ₯Ό μ—°κ²°ν•˜λŠ” 데λͺ¨ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ λ§Œλ“€ 수 μžˆλ‹€.

1
2
3
4
5
Feed f = new Feed();
f.registerObserver(new NYTimes());
f.registerObserver(new Guardian());
f.registerObserver(new LeMonde());
f.notifyObservers("The queen said her favorite book is Modern Java in Action!");

가디언도 우리의 νŠΈμœ—μ„ λ°›μ•„λ³Ό 수 있게 λ˜μ—ˆλ‹€.

λžŒλ‹€ ν‘œν˜„μ‹ μ‚¬μš©ν•˜κΈ°

μ„Έ 개의 μ˜΅μ €λ²„λ₯Ό λͺ…μ‹œμ μœΌλ‘œ μΈμŠ€ν„΄μŠ€ν™”ν•˜μ§€ μ•Šκ³  λžŒλ‹€ ν‘œν˜„μ‹μ„ 직접 μ „λ‹¬ν•΄μ„œ μ‹€ν–‰ν•  λ™μž‘μ„ 지정할 수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
10
f.registerObserver((String tweet) -> {
    if(tweet != null && tweet.contains(" money ")) {
        System.out.println("Breaking news in NY! " + tweet);
    }
});
f.registerObserver((String tweet) -> {
    if(tweet != null && tweet.contains(" queen ")) {
        System.out.println("Yet more news from London... " + tweet);
    }
});

9.2.4 의무 체인

의무 체인 νŒ¨ν„΄

μž‘μ—… 처리 객체의 체인(λ™μž‘ 체인 λ“±)을 λ§Œλ“€ λ•Œ μ‚¬μš©

ν•œ 객체가 μ–΄λ–€ μž‘μ—…μ„ μ²˜λ¦¬ν•œ λ‹€μŒμ— λ‹€λ₯Έ 객체둜 κ²°κ³Όλ₯Ό μ „λ‹¬ν•˜κ³ , λ‹€λ₯Έ 객체도 ν•΄μ•Ό ν•  μž‘μ—…μ„ μ²˜λ¦¬ν•œ λ‹€μŒμ— 또 λ‹€λ₯Έ 객체둜 μ „λ‹¬ν•˜λŠ” 식이닀.

일반적으둜 λ‹€μŒμœΌλ‘œ μ²˜λ¦¬ν•  객체 정보λ₯Ό μœ μ§€ν•˜λŠ” ν•„λ“œλ₯Ό ν¬ν•¨ν•˜λŠ” μž‘μ—… 처리 좔상 클래슀둜 의무 체인 νŒ¨ν„΄μ„ κ΅¬μ„±ν•œλ‹€. μž‘μ—… 처리 객체가 μžμ‹ μ˜ μž‘μ—…μ„ λλƒˆμœΌλ©΄ λ‹€μŒ μž‘μ—… 처리 객체둜 κ²°κ³Όλ₯Ό μ „λ‹¬ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// μž‘μ—… 처리 객체 예제 μ½”λ“œ
public abstract class ProcessingObject<T> {
    protected ProcessingObject<T> successor;
    public void setSuccessor(ProcessingObject<T> succesor) {
        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);
}

handle λ©”μ„œλ“œλŠ” 일뢀 μž‘μ—…μ„ μ–΄λ–»κ²Œ μ²˜λ¦¬ν•΄μ•Ό 할지 μ „μ²΄μ μœΌλ‘œ κΈ°μˆ ν•œλ‹€. ProcessingObject 클래슀λ₯Ό 상속받아 handleWork λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•˜μ—¬ λ‹€μ–‘ν•œ μ’…λ₯˜μ˜ μž‘μ—… 처리 객체λ₯Ό λ§Œλ“€ 수 μžˆλ‹€.

λ‹€μŒμ˜ 두 μž‘μ—… 처리 κ°μ²΄λŠ” ν…μŠ€νŠΈλ₯Ό μ²˜λ¦¬ν•˜λŠ” μ˜ˆμ œλ‹€(μœ„ νŒ¨ν„΄ ν™œμš© 예제)

1
2
3
4
5
6
7
8
9
10
11
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");
    }
}

두 μž‘μ—… 처리 객체λ₯Ό μ—°κ²°ν•΄μ„œ μž‘μ—… 체인을 λ§Œλ“€ 수 μžˆλ‹€.

1
2
3
4
5
6
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);
// 'From Raoul, Mario and Alan: Aren't lambdas really sexy?!!' 좜λ ₯

λžŒλ‹€ ν‘œν˜„μ‹ μ‚¬μš©

μœ„ νŒ¨ν„΄μ€ ν•¨μˆ˜ 체인(ν•¨μˆ˜ μ‘°ν•©)κ³Ό λΉ„μŠ·ν•˜λ‹€.

μž‘μ—… 처리 객체λ₯Ό Function<String, String>, 더 μ •ν™•νžˆ ν‘œν˜„ν•˜μžλ©΄ UnaryOperator ν˜•μ‹μ˜ μΈμŠ€ν„΄μŠ€λ‘œ ν‘œν˜„ν•  수 μžˆλ‹€. andThen λ©”μ„œλ“œλ‘œ μ‘°ν•©ν•΄μ„œ 체인을 λ§Œλ“€ 수 μžˆλ‹€.

1
2
3
4
5
6
7
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 labdas really sexy?!!");

9.2.5 νŒ©ν† λ¦¬

νŒ©ν† λ¦¬ λ””μžμΈ νŒ¨ν„΄

μΈμŠ€ν„΄μŠ€ν™” λ‘œμ§μ„ ν΄λΌμ΄μ–ΈνŠΈμ— λ…ΈμΆœν•˜μ§€ μ•Šκ³  객체λ₯Ό λ§Œλ“€ λ•Œ μ‚¬μš©

예λ₯Ό λ“€μ–΄ μ€ν–‰μ—μ„œ μ·¨κΈ‰ν•˜λŠ” λŒ€μΆœ, μ±„κΆŒ, 주식 λ“± λ‹€μ–‘ν•œ μƒν’ˆμ„ λ§Œλ“€μ–΄μ•Ό ν•œλ‹€κ³  κ°€μ •ν•˜μž. λ‹€μŒ μ½”λ“œμ—μ„œ λ³΄μ—¬μ£ΌλŠ” κ²ƒμ²˜λŸΌ λ‹€μ–‘ν•œ μƒν’ˆμ„ λ§Œλ“œλŠ” Factory ν΄λž˜μŠ€κ°€ ν•„μš”ν•˜λ‹€.

1
2
3
4
5
6
7
8
9
10
public class ProductFactory {
    public static Product createProduct(String name) {
        switch(name){
            case " loan " : return new Loan();
            case " stock " : return new Stock();
            case " bond " : return new Bond();
            default: throw new RuntimeException("No such product " + name);
        }
    }
}

μ—¬κΈ°μ„œ Loan, Stock, Bond λͺ¨λ‘ Product의 μ„œλΈŒν˜•μ‹μ΄λ‹€. createProduct λ©”μ„œλ“œλŠ” μƒμ‚°λœ μƒν’ˆμ„ μ„€μ •ν•˜λŠ” λ‘œμ§μ„ 포함할 수 μžˆλ‹€.

μ΄λŠ” 뢀가적인 κΈ°λŠ₯일 뿐 μœ„ μ½”λ“œμ˜ μ§„μ§œ μž₯점은 μƒμ„±μžμ™€ 섀정을 μ™ΈλΆ€λ‘œ λ…ΈμΆœν•˜μ§€ μ•ŠμŒμœΌλ‘œμ¨ ν΄λΌμ΄μ–ΈνŠΈκ°€ λ‹¨μˆœν•˜κ²Œ μƒν’ˆμ„ 생산할 수 μžˆλ‹€λŠ” 것이닀.

1
Product p = ProductFactory.createProduct("loan");

λžŒλ‹€ ν‘œν˜„μ‹ μ‚¬μš©

μƒμ„±μžλ„ λ©”μ„œλ“œ 참쑰처럼 μ‚¬μš©ν•  수 μžˆλ‹€.

λ‹€μŒμ€ Loan μƒμ„±μžλ₯Ό μ‚¬μš©ν•˜λŠ” μ½”λ“œλ‹€.

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

이제 λ‹€μŒ μ½”λ“œμ²˜λŸΌ μƒν’ˆλͺ…을 μƒμ„±μžλ‘œ μ—°κ²°ν•˜λŠ” Map을 λ§Œλ“€μ–΄μ„œ μ½”λ“œλ₯Ό μž¬κ΅¬ν˜„ν•  수 μžˆλ‹€.

1
2
3
4
5
6
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을 μ΄μš©ν•΄μ„œ νŒ©ν† λ¦¬ λ””μžμΈ νŒ¨ν„΄μ—μ„œ ν–ˆλ˜ κ²ƒμ²˜λŸΌ λ‹€μ–‘ν•œ μƒν’ˆμ„ μΈμŠ€ν„΄μŠ€ν™”ν•  수 μžˆλ‹€.

1
2
3
4
5
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);
}

νŒ©ν† λ¦¬ νŒ¨ν„΄μ΄ μˆ˜ν–‰ν•˜λ˜ μž‘μ—…μ„ μžλ°” 8의 μƒˆλ‘œμš΄ κΈ°λŠ₯으둜 κΉ”λ”ν•˜κ²Œ μ •λ¦¬ν–ˆλ‹€.

ν•˜μ§€λ§Œ νŒ©ν† λ¦¬ λ©”μ„œλ“œ createProductκ°€ μƒν’ˆ μƒμ„±μžλ‘œ μ—¬λŸ¬ 인수λ₯Ό μ „λ‹¬ν•˜λŠ” μƒν™©μ—μ„œλŠ” 이 기법을 μ μš©ν•˜κΈ° μ–΄λ ΅λ‹€. λ‹¨μˆœν•œ Supplier ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ‘œλŠ” 이 문제λ₯Ό ν•΄κ²°ν•  수 μ—†λ‹€.

예λ₯Ό λ“€μ–΄ μ„Έ 인수(Integer λ‘κ°œ, λ¬Έμžμ—΄ ν•˜λ‚˜)λ₯Ό λ°›λŠ” μƒν’ˆμ˜ μƒμ„±μžκ°€ μžˆλ‹€κ³  κ°€μ •ν•˜μž. μ„Έ 인수λ₯Ό μ§€μ›ν•˜λ €λ©΄ TriFunctionμ΄λΌλŠ” νŠΉλ³„ν•œ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•œλ‹€. κ²°κ΅­ λ‹€μŒμ½”λ“œμ²˜λŸΌ Map의 μ‹œκ·Έλ‹ˆμ²˜κ°€ λ³΅μž‘ν•΄μ§„λ‹€.

1
2
3
4
5
public interface TriFunction<T, U, V, R> {
    R apply(T t, U u, V v);
}
Map<String, TriFunction<Integer, Integer, String, Product>> map
    = new HashMap<>();

9.3 λžŒλ‹€ ν…ŒμŠ€νŒ…

ν”„λ‘œκ·Έλž¨μ΄ μ˜λ„λŒ€λ‘œ λ™μž‘ν•˜λŠ”μ§€ 확인할 수 μžˆλŠ” λ‹¨μœ„ ν…ŒμŠ€νŒ…μ„ 진행해야 ν•œλ‹€. μ†ŒμŠ€ μ½”λ“œμ˜ 일뢀가 μ˜ˆμƒλœ κ²°κ³Όλ₯Ό λ„μΆœν•  것이라 λ‹¨μ–Έν•˜λŠ” ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•œλ‹€.

예λ₯Ό λ“€μ–΄ λ‹€μŒμ²˜λŸΌ κ·Έλž˜ν”½ μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ˜ 일뢀인 Point ν΄λž˜μŠ€κ°€ μžˆλ‹€κ³  κ°€μ •ν•˜μž.

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Point {
    private final int x;
    private final int y;
    private Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() { return x; }
    public int getY() { return y; }
    public Point moveRightBy(int x) {
        return new Point(this.x + x, this.y);
    }
}

λ‹€μŒμ€ moveRightBy λ©”μ„œλ“œκ°€ μ˜λ„ν•œ λŒ€λ‘œ λ™μž‘ν•˜λŠ”μ§€ ν™•μΈν•˜λŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈλ‹€.

1
2
3
4
5
6
7
@Test
public void testMoveRightBy() throws Exception {
    Point p1 = new Point(5, 5);
    Point p2 = p1.moveRightBy(10);
    assertEquals(15, p2.getX());
    assertEquals(5, p2.getY());
}

9.3.1 λ³΄μ΄λŠ” λžŒλ‹€ ν‘œν˜„μ‹μ˜ λ™μž‘ ν…ŒμŠ€νŒ…

moveRightByλŠ” public μ΄λ―€λ‘œ μœ„ μ½”λ“œλŠ” λ¬Έμ œμ—†μ΄ μž‘λ™ν•œλ‹€. λ”°λΌμ„œ ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€ λ‚΄λΆ€μ—μ„œ Point 클래슀 μ½”λ“œλ₯Ό ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€. ν•˜μ§€λ§Œ λžŒλ‹€λŠ” 읡λͺ…(κ²°κ΅­ 읡λͺ… ν•¨μˆ˜)μ΄λ―€λ‘œ ν…ŒμŠ€νŠΈ μ½”λ“œ 이름을 ν˜ΈμΆœν•  수 μ—†λ‹€.

λ”°λΌμ„œ ν•„μš”ν•˜λ‹€λ©΄ λžŒλ‹€λ₯Ό ν•„λ“œμ— μ €μž₯ν•΄μ„œ μž¬μ‚¬μš©ν•  수 있으며 λžŒλ‹€μ˜ λ‘œμ§μ„ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€. λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜λŠ” κ²ƒμ²˜λŸΌ λžŒλ‹€λ₯Ό μ‚¬μš©ν•œλ‹€.

예λ₯Ό λ“€μ–΄ Point ν΄λž˜μŠ€μ— compareByXAndThenYλΌλŠ” 정적 ν•„λ“œλ₯Ό μΆ”κ°€ν–ˆλ‹€κ³  κ°€μ •ν•˜μž(compareByXAndThenYλ₯Ό μ΄μš©ν•˜λ©΄ λ©”μ„œλ“œ 참쑰둜 μƒμ„±ν•œ Comparator 객체에 μ ‘κ·Όν•  수 μžˆλ‹€).

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

λžŒλ‹€ ν‘œν˜„μ‹μ€ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•œλ‹€λŠ” 사싀을 κΈ°μ–΅ν•˜μž. λ”°λΌμ„œ μƒμ„±λœ μΈμŠ€ν„΄μŠ€μ˜ λ™μž‘μœΌλ‘œ λžŒλ‹€ ν‘œν˜„μ‹μ„ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.

1
2
3
4
5
6
7
@Test
public void testCompaingTwoPoints() throws Exception {
    Point p1 = new Point(10, 15);
    Point p2 = new Point(10, 20);
    int result = Point.compareByXAndThenY.compare(p1, p2);
    assertTrue(result < 0);
}

9.3.2 λžŒλ‹€λ₯Ό μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œμ˜ λ™μž‘μ— μ§‘μ€‘ν•˜λΌ

λžŒλ‹€μ˜ λͺ©ν‘œλŠ” 정해진 λ™μž‘μ„ λ‹€λ₯Έ λ©”μ„œλ“œμ—μ„œ μ‚¬μš©ν•  수 μžˆλ„λ‘ ν•˜λ‚˜μ˜ 쑰각으둜 μΊ‘μŠν™”ν•˜λŠ” 것이닀. 그러렀면 μ„ΈλΆ€ κ΅¬ν˜„μ„ ν¬ν•¨ν•˜λŠ” λžŒλ‹€ ν‘œν˜„μ‹μ„ κ³΅κ°œν•˜μ§€ 말아야 ν•œλ‹€.

λžŒλ‹€ ν‘œν˜„μ‹μ„ μ‚¬μš©ν•˜λŠ” λ©”μ„œλ“œμ˜ λ™μž‘μ„ ν…ŒμŠ€νŠΈν•¨μœΌλ‘œμ¨ λžŒλ‹€λ₯Ό κ³΅κ°œν•˜μ§€ μ•ŠμœΌλ©΄μ„œλ„ λžŒλ‹€ ν‘œν˜„μ‹μ„ 검증할 수 μžˆλ‹€.

1
2
3
4
5
public static List<Point> moveAllPointsRightBy(List<Point> points, int x) {
    return points.stream()
                .map(p -> new Point(p.getX() + x, p.getY()))
                .collect(toList());
}

μœ„ μ½”λ“œμ— λžŒλ‹€ ν‘œν˜„μ‹ p β†’ new Point(p.getX() + p.getY());λ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” 뢀뢄은 μ—†λ‹€. κ·Έλƒ₯ moveAllPointsRightBy λ©”μ„œλ“œλ₯Ό κ΅¬ν˜„ν•œ μ½”λ“œμΌ 뿐이닀. 이제 moveAllPointsRightBy λ©”μ„œλ“œμ˜ λ™μž‘μ„ 확인할 수 μžˆλ‹€.

1
2
3
4
5
6
7
8
9
@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);
}

9.3.3 λ³΅μž‘ν•œ λžŒλ‹€λ₯Ό κ°œλ³„ λ©”μ„œλ“œλ‘œ λΆ„ν• ν•˜κΈ°

ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œ λžŒλ‹€ ν‘œν˜„μ‹μ„ μ°Έμ‘°ν•  수 μ—†λŠ”λ°, ν•œ 가지 해결책은 9.1.3μ ˆμ—μ„œ μ„€λͺ…ν•œ 것 처럼 λžŒλ‹€ ν‘œν˜„μ‹μ„ λ©”μ„œλ“œ 참쑰둜 λ°”κΎΈλŠ” 것이닀(μƒˆλ‘œμš΄ 일반 λ©”μ„œλ“œ μ„ μ–Έ). 그러면 일반 λ©”μ„œλ“œλ₯Ό ν…ŒμŠ€νŠΈν•˜λ“―μ΄ λžŒλ‹€ ν‘œν˜„μ‹μ„ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.

9.3.4 고차원 ν•¨μˆ˜ ν…ŒμŠ€νŒ…

고차원 ν•¨μˆ˜

ν•¨μˆ˜λ₯Ό 인수둜 λ°›κ±°λ‚˜ λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•˜λŠ” λ©”μ„œλ“œ

고차원 ν•¨μˆ˜λ₯Ό ν…ŒμŠ€νŒ…ν•˜λŠ” 것은 μ–΄λ ΅λ‹€. λ©”μ„œλ“œκ°€ λžŒλ‹€λ₯Ό 인수둜 λ°›λŠ”λ‹€λ©΄ λ‹€λ₯Έ λžŒλ‹€λ‘œ λ©”μ„œλ“œμ˜ λ™μž‘μ„ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€. 예λ₯Ό λ“€μ–΄ ν”„λ ˆλ””μΌ€μ΄νŠΈλ‘œ λ§Œλ“  filter λ©”μ„œλ“œλ₯Ό ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.

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

ν…ŒμŠ€νŠΈν•΄μ•Ό ν•  λ©”μ„œλ“œκ°€ λ‹€λ₯Έ ν•¨μˆ˜λ₯Ό λ°˜ν™˜ν•œλ‹€λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒ? μ΄λ•ŒλŠ” Comparatorμ—μ„œ μ‚΄νŽ΄λ΄€λ˜ κ²ƒμ²˜λŸΌ ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€μ˜ μΈμŠ€ν„΄μŠ€λ‘œ κ°„μ£Όν•˜κ³  ν•¨μˆ˜μ˜ λ™μž‘μ„ ν…ŒμŠ€νŠΈν•  수 μžˆλ‹€.

9.4 디버깅

λ¬Έμ œκ°€ λ°œμƒν•œ μ½”λ“œλ₯Ό 디버깅할 λ•Œ κ°œλ°œμžλŠ” λ‹€μŒ 두 가지λ₯Ό κ°€μž₯ λ¨Όμ € 확인해야 ν•œλ‹€.

  • μŠ€νƒ 트레이슀
  • λ‘œκΉ…

ν•˜μ§€λ§Œ λžŒλ‹€ ν‘œν˜„μ‹κ³Ό μŠ€νŠΈλ¦Όμ€ 기쑴의 디버깅 기법을 무λ ₯ν™”ν•œλ‹€.

9.4.1 μŠ€νƒ 트레이슀 확인

예λ₯Ό λ“€μ–΄ μ˜ˆμ™Έ λ°œμƒμœΌλ‘œ ν”„λ‘œκ·Έλž¨ 싀행이 κ°‘μžκΈ° μ€‘λ‹¨λ˜μ—ˆλ‹€λ©΄ λ¨Όμ € μ–΄λ””μ—μ„œ λ©ˆμ·„κ³  μ–΄λ–»κ²Œ λ©ˆμΆ”κ²Œ λ˜μ—ˆλŠ”μ§€ μ‚΄νŽ΄λ΄μ•Ό ν•œλ‹€. λ°”λ‘œ μŠ€νƒ ν”„λ ˆμž„μ—μ„œ 이 정보λ₯Ό 얻을 수 μžˆλ‹€.

ν”„λ‘œκ·Έλž¨μ΄ λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•  λ•Œλ§ˆλ‹€ ν”„λ‘œκ·Έλž¨μ—μ„œμ˜ 호좜 μœ„μΉ˜, ν˜ΈμΆœν•  λ•Œμ˜ μΈμˆ˜κ°’, 호좜된 λ©”μ„œλ“œμ˜ 지역 λ³€μˆ˜ 등을 ν¬ν•¨ν•œ 호좜 정보가 μƒμ„±λ˜λ©° 이듀 μ •λ³΄λŠ” μŠ€νƒ ν”„λ ˆμž„μ— μ €μž₯λœλ‹€.

λ”°λΌμ„œ ν”„λ‘œκ·Έλž¨μ΄ λ©ˆμ·„λ‹€λ©΄ ν”„λ‘œκ·Έλž¨μ΄ μ–΄λ–»κ²Œ λ©ˆμΆ”κ²Œ λ˜μ—ˆλŠ”μ§€ ν”„λ ˆμž„λ³„λ‘œ λ³΄μ—¬μ£ΌλŠ” μŠ€νƒ 트레이슀λ₯Ό 얻을 수 μžˆλ‹€. 즉, λ¬Έμ œκ°€ λ°œμƒν•œ 지점에 이λ₯΄κ²Œ 된 λ©”μ„œλ“œ 호좜 리슀트λ₯Ό 얻을 수 μžˆλ‹€.

λžŒλ‹€μ™€ μŠ€νƒ 트레이슀

λžŒλ‹€ ν‘œν˜„μ‹μ€ 이름이 μ—†κΈ° λ•Œλ¬Έμ— 쑰금 λ³΅μž‘ν•œ μŠ€νƒ νŠΈλ ˆμ΄μŠ€κ°€ μƒμ„±λœλ‹€.

λ‹€μŒμ€ 고의적으둜 문제λ₯Ό μΌμœΌν‚€λ„λ‘ κ΅¬ν˜„ν•œ μ½”λ“œλ‹€.

1
2
3
4
5
6
7
8
import java.util.*;

public class Debugging {
    public static void main(String[] args) {
        List<Point> points = Arrays.asList(new Point(12, 2), null);
        points.stream().map(p -> p.getX()).forEach(System.out::println);
    }
}

ν”„λ‘œκ·Έλž¨μ„ μ‹€ν–‰ν•˜λ©΄ λ‹€μŒκ³Ό 같은 μŠ€νƒ νŠΈλ ˆμ΄μŠ€κ°€ 좜λ ₯λœλ‹€.

1
2
3
4
5
6
Exception in thread "main" java.lang.NullPointerExceptino
    at Debugging.lambda$main$0(Debugging.java:6) //$0은 무슨 μ˜λ―ΈμΈκ°€?
    at Debugging$$Lambda$5/284720968.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
    at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
...

μ˜λ„ν–ˆλ˜ λŒ€λ‘œ points 리슀트의 λ‘˜μ§Έ μΈμˆ˜κ°€ nullμ΄λ―€λ‘œ ν”„λ‘œκ·Έλž¨μ˜ 싀행이 λ©ˆμ·„λ‹€.

슀트림 νŒŒμ΄ν”„λΌμΈμ—μ„œ μ—λŸ¬κ°€ λ°œμƒν–ˆμœΌλ―€λ‘œ 슀트림 νŒŒμ΄ν”„λΌμΈ μž‘μ—…κ³Ό κ΄€λ ¨λœ 전체 λ©”μ„œλ“œ 호좜 λ¦¬μŠ€νŠΈκ°€ 좜λ ₯λ˜μ—ˆλ‹€. λ©”μ„œλ“œ 호좜 λ¦¬μŠ€νŠΈμ— λ‹€μŒμ²˜λŸΌ 수수께끼 같은 정보도 ν¬ν•¨λ˜μ–΄ μžˆλ‹€.

1
2
at Debugging.lambda$main$0(Debugging.java:6) 
at Debugging$$Lambda$5/284720968.apply(Unknown Source)

이와 같은 μ΄μƒν•œ λ¬ΈμžλŠ” λžŒλ‹€ ν‘œν˜„μ‹ λ‚΄λΆ€μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν–ˆμŒμ„ 가리킨닀.

λžŒλ‹€ ν‘œν˜„μ‹μ€ 이름이 μ—†μœΌλ―€λ‘œ μ»΄νŒŒμΌλŸ¬κ°€ λžŒλ‹€λ₯Ό μ°Έμ‘°ν•˜λŠ” 이름을 λ§Œλ“€μ–΄λ‚Έ 것이닀.

λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό μ‚¬μš©ν•΄λ„ μŠ€νƒ νŠΈλ ˆμ΄μŠ€μ—λŠ” λ©”μ„œλ“œλͺ…이 λ‚˜νƒ€λ‚˜μ§€ μ•ŠλŠ”λ‹€. 기쑴으이 λžŒλ‹€ ν‘œν˜„μ‹ p β†’ p.getX() λ©”μ„œλ“œ μ°Έμ‘° Point::getX둜 고쳐도 μ—¬μ „νžˆ μŠ€νƒ νŠΈλ ˆμ΄μŠ€λ‘œλŠ” μ΄μƒν•œ 정보가 좜λ ₯λœλ‹€.

1
points.stream().map(Point::getX).forEach(System.out::println);
1
2
3
4
Exception in thread "main" java.lang.NullPointerException
    at Debugging$$Lambda$5/284720968.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
...

λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό μ‚¬μš©ν•˜λŠ” ν΄λž˜μŠ€μ™€ 같은 곳에 μ„ μ–Έλ˜μ–΄ μžˆλŠ” λ©”μ„œλ“œλ₯Ό μ°Έμ‘°ν•  λ•ŒλŠ” λ©”μ„œλ“œ μ°Έμ‘° 이름이 μŠ€νƒ νŠΈλ ˆμ΄μ„œμ— λ‚˜νƒ€λ‚œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.*;

public class Debugging {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3);
        numbers.stream().map(Debugging::divideByZero).forEach(System.out::println);
    }
    // λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό μ‚¬μš©ν•˜λŠ” 클래슀(Debugging)κ³Ό 같은 곳에 μ„ μ–Έλ˜μ–΄ μžˆλŠ” λ©”μ„œλ“œ
    public static int divideByZero(int n) {
        return n / 0;
    }
}

divideByZero λ©”μ„œλ“œλŠ” μŠ€νƒ νŠΈλ ˆμ΄μŠ€μ— μ œλŒ€λ‘œ ν‘œμ‹œλœλ‹€.

1
2
3
4
5
Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Debugging.**divideByZero**(Debugging.java:10) // μŠ€νƒ νŠΈλ ˆμ΄μŠ€μ— divideByZeroκ°€ 보인닀.
    at Debugging.$$Lambda$1/999966131.apply(Unknown Source)
    at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
...

9.4.2 정보 λ‘œκΉ…

슀트림의 νŒŒμ΄ν”„λΌμΈ 연산을 λ””λ²„κΉ…ν•œλ‹€κ³  κ°€μ •ν–ˆμ„ λ•Œ, λ‹€μŒμ²˜λŸΌ forEach둜 슀트림 κ²°κ³ λ₯Ό 좜λ ₯ν•˜κ±°λ‚˜ λ‘œκΉ…ν•  수 μžˆλ‹€.

1
2
3
4
5
6
List<Integer> numbers = Arrays.asList(2, 3, 4, 5);
numbers.stream()
        .map(x -> x + 17)
        .filter(x -> x % 2 == 0)
        .limit(3)
        .forEach(System.out::println);

λ‹€μŒμ€ ν”„λ‘œκ·Έλž¨ 좜λ ₯ κ²°κ³Όλ‹€.

1
2
20
22

μ•ˆνƒ€κΉκ²Œλ„ forEachλ₯Ό ν˜ΈμΆœν•˜λŠ” μˆœκ°„ 전체 슀트림이 μ†ŒλΉ„λœλ‹€. 슀트림 νŒŒμ΄ν”„λΌμΈμ— 적용된 각각의 μ—°μ‚°(map, filter, limit)이 μ–΄λ–€ κ²°κ³Όλ₯Ό λ„μΆœν•˜λŠ”μ§€ 확인할 수 μžˆλ‹€λ©΄ 쒋을 것이닀.

μ΄λ•Œ peekμ΄λΌλŠ” 슀트림 연산을 ν™œμš©ν•  수 μžˆλ‹€. peek은 슀트림의 각 μš”μ†Œλ₯Ό μ†ŒλΉ„ν•œ κ²ƒμ²˜λŸΌ λ™μž‘μ„ μ‹€ν–‰ν•œλ‹€. ν•˜μ§€λ§Œ forEach처럼 μ‹€μ œλ‘œ 슀트림의 μš”μ†Œλ₯Ό μ†ŒλΉ„ν•˜μ§€λŠ” μ•ŠλŠ”λ‹€.

peek은 μžμ‹ μ΄ ν™•μΈν•œ μš”μ†Œλ₯Ό νŒŒμ΄ν”„λΌμΈμ˜ λ‹€μŒ μ—°μ‚°μœΌλ‘œ κ·ΈλŒ€λ‘œ μ „λ‹¬ν•œλ‹€.

λ‹€μŒ μ½”λ“œμ—μ„œλŠ” peek으둜 슀트림 νŒŒμ΄ν”„λΌμΈμ˜ 각 λ™μž‘ μ „ν›„μ˜ 쀑간값을 좜λ ₯ν•œλ‹€.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
List<Integer> result =
    numbers.stream()
            // μ†ŒμŠ€μ—μ„œ 처음 μ†ŒλΉ„ν•œ μš”μ†Œλ₯Ό 좜λ ₯ν•œλ‹€.
            .peek(x -> System.out.println("from stream: " + x))
            .map(x -> x + 17)
            // map λ™μž‘ μ‹€ν–‰ κ²°κ³Όλ₯Ό 좜λ ₯ν•œλ‹€. 
            .peek(x -> System.out.println("after map: " + x))
            .filter(x -> x % 2 == 0)
            // filter λ™μž‘ ν›„ μ„ νƒλœ 숫자λ₯Ό 좜λ ₯ν•œλ‹€. 
            .peek(x -> Systeml.out.println("after filter: " + x))
            .limit(3)
            // limit λ™μž‘ ν›„ μ„ νƒλœ 숫자λ₯Ό 좜λ ₯ν•œλ‹€. 
            .peek(x -> System.out.println("after limit: " + x))
            .collect(toList());

λ‹€μŒμ€ νŒŒμ΄ν”„λΌμΈμ˜ 각 단계별 μƒνƒœλ₯Ό 보여쀀닀.

1
2
3
4
5
6
7
8
9
10
11
from stream: 2
after map: 19
from stream: 3
after map: 20
after filter: 20
from stream: 4
after map: 21
from stream: 5
after map: 22
after filter: 22
after limit: 22
This post is licensed under CC BY 4.0 by the author.