λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

πŸ“šμ½μ€ μ±… 정리/μžλ°” μ½”λ”©μ˜ 기술

[μžλ°” μ½”λ”©μ˜ 기술] 8μž₯ : 데이터 흐름

8μž₯μ—μ„œλŠ” μžλ°” ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž¨μ˜ 핡심 κ°œλ…μ„ μ„€λͺ…ν•˜κ² λ‹€.

 

- ν•¨μˆ˜ν˜• 방식이 λͺ…λ Ήν˜• 방식을 μ›”λ“±νžˆ λŠ₯κ°€ν•˜λŠ” 경우.

- 읡λͺ… 클래슀λ₯Ό λͺ…μΎŒν•˜κ²Œ λŒ€μ²΄ν•˜λŠ” 방법

- 자료 ꡬ쑰λ₯Ό 더 효율적으둜 μˆœνšŒν•˜λŠ” 방법

- μ˜΅μ…”λ„λ‘œ ν”„λ‘œκ·Έλž¨μ„ 덜 μ·¨μ•½ν•˜κ²Œ λ§Œλ“œλŠ” 방법

8.1 읡λͺ… 클래슀 λŒ€μ‹  λžŒλ‹€ μ‚¬μš©ν•˜κΈ°

문제 μ½”λ“œ

class Calculator {

    Map<Double, Double> values = new HashMap<>();

    Double square(Double x) {
        Function<Double, Double> squareFunction =
                new Function<Double, Double>() {
                    @Override
                    public Double apply(Double value) {
                        return value * value;
                    }
                };
        return values.computeIfAbsent(x, squareFunction);
    }
}

μœ„ μ½”λ“œλŠ” 읡λͺ… 클래슀λ₯Ό μ΄μš©ν•΄μ„œ Function<Double, Double> μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆλ‹€.

-> μ΄λŸ¬ν•œ 읡λͺ… ν΄λž˜μŠ€λŠ” μ½”λ“œλŸ‰μ„ 늘리고 νƒ€μž…κ³Ό λ©”μ„œλ“œλ₯Ό λ°˜λ³΅ν•˜λ©° μ‹€μ œ μ—°μ‚°μ΄μ™Έμ˜ 것듀이 μž₯ν™©ν•˜κ²Œ νŽΌμ³μ§„λ‹€.

 

μžλ°” 8μ—μ„œλŠ” 읡λͺ… 클래슀 λŒ€μ‹  "λžŒλ‹€ ν‘œν˜„μ‹"으둜 μ½”λ“œλ₯Ό ν–₯μƒμ‹œν‚¬ 수 μžˆλ‹€.

κ°œμ„  μ½”λ“œ

class Calculator {

    Map<Double, Double> values = new HashMap<>();

    Double square(Double value) {
        Function<Double, Double> squareFunction = factor -> factor * factor;
        return values.computeIfAbsent(value, squareFunction);
    }
}

Function<Double, Double> squareFunction = factor -> factor * factor;

처럼 λžŒλ‹€μ‹μœΌλ‘œ ν‘œν˜„ν–ˆλ”λ‹ˆ μ—°μ‚° 둜직이 λ°”λ‘œ 보이며 μ½”λ“œκ°€ κ°„κ²°ν•΄μ‘Œλ‹€.

 

λžŒλ‹€λŠ” ν•¨μˆ˜ν˜• μΈν„°νŽ˜μ΄μŠ€, 즉 단일 좔상 λ©”μ„œλ“œ(SAM)λ₯Ό ν¬ν•¨ν•˜λŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„.

μœ„ μ˜ˆμ œμ—μ„œλŠ” Function μΈν„°νŽ˜μ΄μŠ€κ°€ apply() 좔상 λ©”μ„œλ“œλ§Œ ν¬ν•¨ν•˜λ‹ˆ λžŒλ‹€μ™€ λ“€μ–΄λ§žλŠ”λ‹€.

 

λžŒλ‹€λŠ” μ•„λž˜ 처럼 ν•œ 쀄 λ˜λŠ” μ—¬λŸ¬ μ€„λ‘œ μž‘μ„± κ°€λŠ₯ν•˜λ‹€.

        // single
        Function<Double, Double> squareFunction = factor -> factor * factor;
        
        //  multi-liner
        Function<Double, Double> squareFunction = factor -> {
            return factor * factor;

8.2 λͺ…λ Ήν˜• 방식 λŒ€μ‹  ν•¨μˆ˜ν˜•

μ»¬λ ‰μ…˜ μ²˜λ¦¬μ— λŒ€ν•΄μ„œλŠ” ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ° 방식이 λͺ…λ Ήν˜• 방식보닀 읽기 쉽닀.

문제 μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    long countDifferentKinds() {
        List<String> names = new ArrayList<>();
        for (Supply supply : supplies) {
            if (supply.isUncontaminated()) {
                String name = supply.getName();
                if (!names.contains(name)) {
                    names.add(name);
                }
            }
        }
        return names.size();
    }
}

λͺ…λ Ήν˜• 방식은 무엇을 ν•΄μ•Όν•˜λŠ”μ§€ μ–΄λ–»κ²Œ ν•΄μ•Όν•˜λŠ”μ§€ λͺ¨λ“  λͺ…μ‹œμ μœΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•©λ‹ˆλ‹€.

 

ν•˜μ§€λ§Œ λ©”μ„œλ“œμ˜ μ˜λ„λ₯Ό ν™•μ‹€νžˆ λ“€μ–΄λ‚΄λ €λ©΄ μ–΄λ–»κ²ŒλŠ” ν•˜λŠ”μ§€λŠ” λΉΌκ³  무엇을 ν•˜λŠ”μ§€λ§Œ λͺ…μ‹œν•˜λ©΄ λœλ‹€.

λžŒλ‹€λ‘œ 무엇이 이루어지길 μ›ν•˜λŠ”μ§€λ§Œ λͺ…μ‹œν•  수 μžˆλ‹€.

κ°œμ„  μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    long countDifferentKinds() {
        return supplies.stream()
                       .filter(supply -> supply.isUncontaminated())
                       .map(supply -> supply.getName())
                       .distinct()
                       .count();
    }
}
  1. stream() μ»¬λ ‰μ…˜μ„ 슀트림으둜 λ³€ν™˜ (μ»¬λ ‰μ…˜ λ‚΄ 각 μ›μ†Œ μ°Έμ‘° κ°€λŠ₯)
  2. filter()λ₯Ό 톡해 μ˜€μ—Όλœ μ›μ†Œ κ±ΈλŸ¬λƒ„ (μΌμ’…μ˜ κ΄€λ¬Έ)
  3. 각 μ›μ†Œλ₯Ό λ³€ν™˜ν•œλ‹€. (μ–΄λ–€ ν•¨μˆ˜κ°€ νƒ€μž…μ„ λ‹€λ₯Έ νƒ€μž…μœΌλ‘œ 맀핑 // supplyμ—μ„œ .getName()으둜)
  4. dinstnct()둜 쀑볡 걸러 λ‚΄κΈ°
  5. count()둜 남은 갯수 μ„ΈκΈ°

8.3 λžŒλ‹€ λŒ€μ‹  λ©”μ„œλ“œ μ°Έμ‘°

λžŒλ‹€ ν‘œν˜„μ‹μ„ μ‚¬μš©ν•˜λ©΄ μ½”λ“œκ°€ 읽기 νŽΈν•΄μ§„λ‹€.

ν•˜μ§€λ§Œ 슀트림 쀑간뢀터 μ‹€ν–‰ν•  수 μ—†κ³  였직 슀트림 전체에 λŒ€ν•΄μ„œλ§Œ μ‹€ν–‰ν•  수 μžˆλ‹€. (μΌλΆ€λ§Œ ν…ŒμŠ€νŠΈ ν•˜κΈ° μ–΄λ ΅λ‹€)

 

λ§Œμ•½, λžŒλ‹€ν‘œν˜„μ‹μ΄ λ³΅μž‘ν•΄μ§€κ³  논리가 λ§Žμ•„μ§€λ©΄ 였λ₯˜ κ°€λŠ₯성이 λ†’μ•„μ§€λŠ”λ° μ°Έμ‘°κ°€ λΆˆκ°€λŠ₯ν•΄ λ‹¨μœ„ ν…ŒμŠ€νŠΈλ„ μ•ˆλ˜λ‹ˆ κΈ°λŒ€λŒ€λ‘œ λ™μž‘ν•˜λŠ” 지 검증할 수 도 μ—†λ‹€.

 

μžλ°”μ˜ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œλ„ λ©”μ„œλ“œ μ°Έμ‘°λ₯Ό μ‚¬μš©ν•˜λ©΄ λ©”μ„œλ“œ ν˜ΈμΆœμ„ λžŒλ‹€ ν‘œν˜„μ‹μ— λΌμ›Œ 넣을 수 있고 ν’ˆμ§ˆ 보증이 μ‰¬μ›Œμ§„λ‹€.

κ°œμ„  μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    long countDifferentKinds() {
        return supplies.stream()
                       .filter(Supply::isUncontaminated)
                       .map(Supply::getName)
                       .distinct()
                       .count();
    }
}

일반적인 λžŒλ‹€ ν‘œν˜„μ‹ λŒ€μ‹  미리 μ •μ˜λœ λ©”μ„œλ“œλ₯Ό 슀트림 λ‚΄μ—μ„œ λ°”λ‘œ μ°Έμ‘°ν•˜λ©΄ λœλ‹€.

 

μ΄λ ‡κ²Œ ν•˜λ©΄ λ©”μ„œλ“œ 각각을 ν˜ΈμΆœν•  수 있고 λΆ€μ • ν”Όν•˜κΈ°λ¬Έμ œλ„ ν•΄κ²°ν•  수 μžˆλ‹€.

8.4 λΆ€μˆ˜ 효과 ν”Όν•˜κΈ°

ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ€ μž…λ ₯을 λ°μ΄ν„°λ‘œ λ°›μ•„ 좜λ ₯으둜 μƒˆλ£¨μš΄ 데이터λ₯Ό μƒμ„±ν•˜λŠ” ν•¨μˆ˜μ΄λ―€λ‘œ λ°μ΄ν„°λŠ” λΆˆλ³€μ΄λ‹€. λ”°λΌμ„œ λΆ€μˆ˜ 효과(side effect)κ°€ μ—†λ‹€.

 

ν•˜μ§€λ§Œ λͺ…λ Ήν˜•κ³Ό 객체 지ν–₯ ν”„λ‘œκ·Έλž˜λ°μ€ 데이터λ₯Ό λ°”κΎΈλ―€λ‘œ λΆ€μˆ˜ νš¨κ³Όμ— μ˜μ‘΄ν•œλ‹€.

μžλ°”λŠ” ν˜„μž¬ μ„Έ 가지 방식을 λͺ¨λ‘ μ‚¬μš©ν•˜λ―€λ‘œ κ°•λ ₯ν•˜μ§€λ§Œ 였λ₯˜κ°€ λ°œμƒν•˜κΈ°λ„ 쉽닀. λ”°λΌμ„œ μ½”λ“œ λ‚΄ λΆ€μˆ˜νš¨κ³Όλ₯Ό μ΅œμ†Œν™”ν•˜λŠ” 방법을 μ•Œμ•„μ•Ό ν•œλ‹€.

문제 μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    long countDifferentKinds() {
        List<String> names = new ArrayList<>();

        Consumer<String> addToNames = name -> names.add(name);

        supplies.stream()
                .filter(Supply::isUncontaminated)
                .map(Supply::getName)
                .distinct()
                .forEach(addToNames);
        return names.size();
    }
}

슀트림의 forEach() λΆ€λΆ„μ—μ„œ addToNamesλ₯Ό ν˜ΈμΆœν•˜λŠ”λ° μ΄λŠ” λžŒλ‹€ ν‘œν˜„μ‹ 밖에 μžˆλŠ” λ¦¬μŠ€νŠΈμ— μ›μ†Œλ₯Ό μΆ”κ°€ν•œλ‹€.

-> μ΄λ•Œ λΆ€μˆ˜νš¨κ³Όκ°€ λ°œμƒν•œλ‹€.

 

μ½”λ“œκ°€ κΈ°λŠ₯상에 였λ₯˜λŠ” μ—†μ§€λ§Œ λ™μ‹œ 싀행이 κ°€λŠ₯ν•˜λ„λ‘ λ°”κΎΈλ©΄ μ‰½κ²Œ κ³ μž₯μ΄λ‚œλ‹€. (μžλ°”λŠ” μ—¬λŸ¬ μŠ€λ ˆλ“œ κ°„ λΆ€μˆ˜νš¨κ³Ό λ°œμƒμ— λŒ€ν•΄ 보μž₯ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ)

 

그러면 "λΆ€μˆ˜νš¨κ³Ό"λ₯Ό μΌμœΌν‚€μ§€ μ•ŠμœΌλ©΄μ„œ λžŒλ‹€ ν‘œν˜„μ‹μ„ μ’…λ£Œν•˜λ €λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒ?

κ°œμ„  μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    long countDifferentKinds() {
        List<String> names = supplies.stream()
                                     .filter(Supply::isUncontaminated)
                                     .map(Supply::getName)
                                     .distinct()
                                     .collect(Collectors.toList());
        return names.size();
    }
}

μœ„ μ½”λ“œμ—μ„œ κ°€μž₯ μ€‘μš”ν•œ 뢀뢄은 λžŒλ‹€ ν‘œν˜„μ‹μ—μ„œ μƒμ„±ν•˜λŠ” λ¦¬μŠ€νŠΈμ΄λ‹€.

리슀트λ₯Ό 직접 λ§Œλ“€μ§€ μ•Šκ³  μ»¬λ ‰μ…˜ λ‚΄ μŠ€νŠΈλ¦Όμ— 남아 μžˆλŠ” 각 μ›μ†Œλ₯Ό collect() ν–ˆλ‹€.

 

μ΄λ•Œ 리슀트λ₯Ό λ§Œλ“€κΈ° μœ„ν•΄μ„œλŠ” collect(Collectors.toList());둜 μŠ€νŠΈλ¦Όμ„ μ’…λ£Œν•΄μ•Όλ§Œ ν•œλ‹€.
(Collectors.toSet()ν•˜λ©΄ Set 자료ꡬ쑰둜 λ§Œλ“¬)

 

**μš”μ•½**

- μŠ€νŠΈλ¦Όμ„ μ’…λ£Œμ‹œν‚¬ λ•Œ forEach()λŠ” λΆ€μˆ˜νš¨κ³Όλ₯Ό μΌμœΌν‚€λ‹ˆ κ°€λŠ₯ν•˜λ©΄ μ“°μ§€λ§μž

- collect()와 reduce()λ₯Ό μ“°λ €κ³  ν•˜μž. (슀트림 직접 μ’…λ£Œ + μ›ν•˜λŠ” 자료ꡬ쑰 생성 κ°€λŠ₯)

8.5 λ³΅μž‘ν•œ 슀트림 μ’…λ£Œ μ‹œ μ»¬λ ‰νŠΈ μ‚¬μš©ν•˜κΈ°

λ³΅μž‘ν•œ 슀트림 μ’…λ£Œ μ‹œμ— λΆ€μˆ˜νš¨κ³Όλ₯Ό ν”Όν•˜λŠ” 방법을 μ΅ν˜€λ³΄μž..

 

μš°λ¦¬κ°€ μ›ν•˜λŠ” λ°μ΄ν„°λŠ”
supplies 리슀트 λ‚΄ 고유 μ›μ†Œ 수λ₯Ό λͺ¨λ‘ μ„ΈλŠ” λŒ€μ‹  남은 μ œν’ˆ 수λ₯Ό μ΄λ¦„λ³„λ‘œ λ¬Άμ–΄ κ³„μ‚°ν•˜λŠ” Map<String, Long>ν˜•νƒœλ₯Ό λ§Œλ“€κΈ°λ₯Ό μ›ν•œλ‹€.

 

쿼리둜 μƒκ°ν•˜μžλ©΄ 'SELECT name, count(*) FROM supplies GROUP BY name;'이라고 λ³Ό 수 μžˆλ‹€.

문제 μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    Map<String, Long> countDifferentKinds() {
        Map<String, Long> nameToCount = new HashMap<>();

        Consumer<String> addToNames = name -> {
            if (!nameToCount.containsKey(name)) {
                nameToCount.put(name, 0L);
            }
            nameToCount.put(name, nameToCount.get(name) + 1);
        };

        supplies.stream()
                .filter(Supply::isUncontaminated)
                .map(Supply::getName)
                .forEach(addToNames);
        return nameToCount;
    }
}

κ°œμ„  μ½”λ“œ

class Inventory {

    List<Supply> supplies = new ArrayList<>();

    Map<String, Long> countDifferentKinds() {
        return supplies.stream()
                       .filter(Supply::isUncontaminated)
                       .collect(Collectors.groupingBy(Supply::getName,
                               Collectors.counting())
                       );
    }
}

슀트림의 κ²°κ³Όλ₯Ό Collection으둜 λ§Œλ“€μ–΄μ•Ό ν•  경우 μš°λ¦¬λŠ” Collectorsλ₯Ό μ΄μš©ν•΄ μ›ν•˜λŠ” 자료ꡬ쑰 ν˜•νƒœλ‘œ λ§Œλ“€ 수 μžˆλ‹€. (toList(), toSet(), toMap())

 

μœ„ μ½”λ“œμ—μ„œλŠ” Collectors.groupingBy() μ—°μ‚°μžλ₯Ό Supply μΈμŠ€ν„΄μŠ€μ˜ μŠ€νŠΈλ¦Όμ— μ μš©ν•œλ‹€. 이 μ—°μ‚°μžλŠ” Map자료ꡬ쑰λ₯Ό λ°˜ν™˜ν•œλ‹€.

κ·Έλ£Ήν•‘λ°”μ΄μ˜ 첫번째 μΈμžλ‘œλŠ” ν‚€ νƒ€μž…μ— ν•΄λ‹Ήν•˜λŠ” string, λ‘λ²ˆμ§Έ μΈμžλ‘œλŠ” 갯수인 Long을 λ„£μ–΄μ€€λ‹€.

 

그러면 μš°λ¦¬κ°€ μ›ν•˜λŠ” 이름별 ν•­λͺ© 수인 Map<String, Long>이 결과둜 λ°˜ν™˜λœλ‹€.

 

collect() 방식이 문제 μ½”λ“œ 보닀 훨씬 κ°„κ²°ν•˜κ³  μš”μ μ„ 잘 μ „λ‹¬ν•˜λ©° 심지어 SQL 쿼리 처럼 μ½νžŒλ‹€.

CollectorsλŠ” 이외에도 joining(), maxBy() λ“± λ§Žμ€ κΈ°λŠ₯을 μ œκ³΅ν•˜κ³  μžˆλ‹€.

8.6 슀트림 λ‚΄ μ˜ˆμ™Έ ν”Όν•˜κΈ°

문제 μ½”λ“œ

class LogBooks {

    static List<LogBook> getAll() throws IOException {
        return Files.walk(Paths.get("/var/log"))
                    .filter(Files::isRegularFile)
                    .filter(LogBook::isLogbook)
                    .map(path -> {
                        try {
                            return new LogBook(path);
                        } catch (IOException e) {
                            throw new UncheckedIOException(e);
                        }
                    })
                    .collect(Collectors.toList());
    }
}

λ¬Έμ œμ½”λ“œλ₯Ό 보면 μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜λŠ” 방식이 맀우 λ³΅μž‘ν•˜κ³  쒋아보이지 μ•ŠλŠ”λ‹€.

 

κ·Έλ ‡λ‹€λ©΄ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ—μ„œλŠ” μ–΄λ–»κ²Œ μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν• κΉŒ?

κ°œμ„  μ½”λ“œ

class LogBooks {

    static List<LogBook> getAll() throws IOException {
        try (Stream<Path> stream = Files.walk(Paths.get("/var/log"))) {
            return stream.filter(Files::isRegularFile)
                         .filter(LogBook::isLogbook)
                         .flatMap(path -> {
                             try {
                                 return Stream.of(new LogBook(path));
                             } catch (IOException e) {
                                 return Stream.empty();
                             }
                         })
                         .collect(Collectors.toList());
        }
    }
}

μ—¬μ „νžˆ try-catch문을 μ‚¬μš©ν•˜μ§€λ§Œ flatMap()을 μ‚¬μš©ν•œλ‹€.

이λ₯Ό 톡해 μ–΄λ–€ νƒ€μž…μ„ λ‹€λ₯Έ νƒ€μž…μ˜ Stream으둜 λ§€ν•‘ν•œλ‹€. λ¬Έμ œκ°€ λ°œμƒν•˜λ©΄ 단지 Stream.empty()λ₯Ό λ°˜ν™˜ν•œλ‹€.

 

μ˜ˆμ™Έλ₯Ό ν”Όν•˜λ €λ©΄ ν•¨μˆ˜ν˜• 방식을 λ”°λ₯΄μ„Έμš”..

8.7 널 λŒ€μ‹  μ˜΅μ…”λ„

객체λ₯Ό κ°€λ₯΄ν‚€μ§€ μ•ŠλŠ” μ°Έμ‘°λŠ” null을 κ°€λ₯΄ν‚¨λ‹€. null 참쑰둜 λ©”μ„œλ“œλ₯Ό μ°Έμ‘°ν•˜λ©΄ NullPointerException이 λ°œμƒν•œλ‹€.

 

μžλ°” 8κ³Ό λžŒλ‹€ ν‘œν˜„μ‹μ˜ λ“±μž₯ 이후 기쑴의 널 보호 μ΄μ™Έμ˜ 널 객체λ₯Ό ν•΄κ²°ν•  λ‹€λ₯Έ 방법이 생겼닀.

 λ¬Έμ œ μ½”λ“œ

class Communicator {

    Connection connectionToEarth;

    void establishConnection() {
        // used to set connectionToEarth, but may be unreliable
    }

    Connection getConnectionToEarth() {
    	
        return connectionToEarth;
    }
}

κΈ°μ‘΄μ—λŠ” if 문을 μΆ”κ°€ν•΄ null인지 μ•„λ‹Œμ§€ κ²€μ¦ν–ˆμ„ 것이닀. ν•˜μ§€λ§Œ if 문을 μ‚¬μš©ν•˜λ©΄ λžŒλ‹€ ν‘œν˜„μ‹μ„ μ‘°ν•©ν•΄μ„œ μ‚¬μš©ν•˜κΈ°λž€ λΆˆκ°€λŠ₯ν•˜λ‹€.

μžλ°”λŠ” 더 쒋은 방법을 μ‚¬μš©ν•˜κ³  μžˆλ‹€.

 

κ°œμ„  μ½”λ“œ

    static void main2() {
        Communicator communicationSystem = new Communicator();

        communicationSystem.getConnectionToEarth()
                            .ifPresent(connection ->
                                connection.send("Houston, we got a problem!")
                            );
    }

μžλ°”λŠ” connectionToEarthκ°€ 없을 μˆ˜λ„ μžˆλ”°λŠ” 것을 "Optional"을 ν†΅ν•΄μ„œ λͺ…μ‹œν•œλ‹€. Optionalμ—λŠ” 객체가 nullμΌμˆ˜λ„ 있고 μ•„λ‹μˆ˜λ„ μžˆλ‹€.

 

μ΄λ ‡κ²Œ μ‚¬μš©ν•¨μœΌλ‘œμ¨ 얻을 수 μžˆλŠ” 이점은 ν˜ΈμΆœμžκ°€ null값을 μ–΄λ–»κ²Œ μ²˜λ¦¬ν•  지 생각해볼 수 μžˆκ²Œν•œλ‹€λŠ” 점이닀.

μ—¬μ „νžˆ μ˜ˆμ™ΈλŠ” λ°œμƒν•  수 μžˆμ§€λ§Œ ν˜ΈμΆœμžκ°€ 이λ₯Ό 적절히 ν•΄κ²°ν•  수 있게 ν•΄μ€€λ‹€.

 

8.8 선택 ν•„λ“œλ‚˜ λ§€κ°œλ³€μˆ˜ ν”Όν•˜κΈ°

문제 μ½”λ“œ

class Communicator {

    Optional<Connection> connectionToEarth;
    
    void setConnectionToEarth(Optional<Connection> connectionToEarth) {
        this.connectionToEarth = connectionToEarth;
    }
    Optional<Connection> getConnectionToEarth() {
        return connectionToEarth;
    }
}

ν•„λ“œλ‚˜ λ§€κ°œλ³€μˆ˜μ— μ˜΅μ…”λ„ νƒ€μž…μ„ μ‚¬μš©ν•˜λ©΄ μ•ˆλœλ‹€. μ΄λŠ” μ½”λ“œλ₯Ό 더 λ³΅μž‘ν•΄μ§€κ²Œ λ§Œλ“€ 뿐이닀.

μ˜΅μ…”λ„μ€ 'λΆ€μž¬'와 '쑴재'의 μƒνƒœλ₯Ό κ°€μ§€λŠ”λ° 이 μ˜΅μ…”λ„μ΄ null이면 더 λΆ€μž¬ν•˜λ‹€λŠ” λœ»μΈκ°€..? μ•„λ¬΄νŠΌ 이해 μ•ˆλ¨.. μ•„λ¬΄νŠΌ μ“°λ©΄ ν—·κ°ˆλ¦Ό

λ§Œμ•½, ν•„λ“œμ™€ λ©”μ„œλ“œ λ§€κ°œλ³€μˆ˜ νƒ€μž…μ—μ„œ Optional을 μ‚¬μš©ν–ˆλ‹€λ©΄ μ œκ±°ν•΄λΌ. 그리고 였직 λ°˜ν™˜κ°’μ—μ„œλ§Œ μ‚¬μš©ν•΄λΌ.

κ°œμ„  μ½”λ“œ

class Communicator {

    Connection connectionToEarth;

    void setConnectionToEarth(Connection connectionToEarth) {
        this.connectionToEarth = Objects.requireNonNull(connectionToEarth);
    }
    Optional<Connection> getConnectionToEarth() {
        return Optional.ofNullable(connectionToEarth);
    }

    void reset() {
        connectionToEarth = null;
    }
}

8.9 μ˜΅μ…”λ„μ„ 슀트림으둜 μ‚¬μš©ν•˜κΈ°

μ˜΅μ…”λ„μ€ null을 ν›Œλ₯­νžˆ λŒ€μ²΄ν•  수 μžˆλ‹€. 뿐만 μ•„λ‹ˆλΌ μžλ°” λ‚΄ λžŒλ‹€ ν‘œν˜„μ‹μ— μ˜΅μ…”λ„ 클래슀λ₯Ό μΆ”κ°€ν•˜λ©΄ μ’‹λ‹€.

=> Optional은 0개 λ˜λŠ” 1개 μ›μ†Œλ§Œ ν¬ν•¨ν•˜λŠ” νŠΉλ³„ν•œ ν˜•μ‹μ˜ μŠ€νŠΈλ¦Όμ΄λ‹€.

 

μ˜΅μ…”λ„μ΄ 슀트림이기 λ•Œλ¬Έμ— filter(), map()κ³Ό 같은 일반적인 슀트림 연산을 λ°”λ‘œ μ μš©ν•  수 μžˆλ‹€.

문제 μ½”λ“œ

class BackupJob {

    Communicator communicator;
    Storage storage;

    void backupToEarth() {
        Optional<Connection> connectionOptional =
                communicator.getConnectionToEarth();
        if (!connectionOptional.isPresent()) {
            throw new IllegalStateException();
        }

        Connection connection = connectionOptional.get();
        if (!connection.isFree()) {
            throw new IllegalStateException();
        }

        connection.send(storage.getBackup());
    }
}

μœ„ μ½”λ“œλŠ” μ–΄λŠ 뢀뢄이 보기 쒋지 μ•Šμ„κΉŒ?

 

μ˜΅μ…”λ„μ€ λͺ…λ Ήν˜•μ΄ μ•„λ‹Œ ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ„ μœ„ν•΄ λ§Œλ“€μ–΄μ‘Œλ‹€.

λ”°λΌμ„œ 두 방식 간에 λ³€ν™˜μ„ μœ„ν•΄ μœ„ μ½”λ“œμ—μ„œλ„ get() 같은 λ©”μ„œλ“œλ‘œ λ³€ν™˜μ΄ μˆ˜ν–‰λœ 것을 확인할 수 μžˆλ‹€.

μ΄λŸ¬ν•œ λ¬Έλ§₯κ΅ν™˜μ΄ 없어지면 μ’€ 더 μ½”λ“œλ₯Ό ν–₯μƒμ‹œν‚¬ 수 μžˆλ‹€.

κ°œμ„  μ½”λ“œ

class BackupJob {

    Communicator communicator;
    Storage storage;

    void backupToEarth() {
        Connection connection = communicator.getConnectionToEarth()
                .filter(Connection::isFree)
                .orElseThrow(IllegalStateException::new);
        connection.send(storage.getBackup());
    }
}

ν•¨μˆ˜ν˜•μœΌλ‘œ μž‘μ„±μ‹œ μ˜΅μ…”λ„ ν΄λž˜μŠ€μ— λ“€μ–΄μžˆλŠ” λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

filter(), map(), flatMap()κ³Ό 같은 슀트림 쀑간 μ—°μ‚°κ³Ό orElse(), orElseThrow(), orElseet(), ifPresent()λ₯Ό ν™œμš©ν•  수 μžˆλ‹€.

 

**μ‹€ν–‰ 흐름**

  1. filter()λ₯Ό μ‚¬μš©ν•΄ 연결이 μ΄μ–΄μ Έμžˆκ³  μ‚¬μš©ν•  수 μžˆλŠ”μ§€ 확인
  2. μ—°κ²°ν•  수 μ—†λ‹€λ©΄ μ˜ˆμ™Έ λ°œμƒ
  3. μ—°κ²° κ°€λŠ₯ν•˜λ―€λ‘œ send() μ‹€ν–‰

ν•¨μˆ˜ν˜•μœΌλ‘œ μž‘μ„±ν•˜λ©΄ 기쑴의 λͺ…λ Ήν˜•λ³΄λ‹€ μ½”λ“œκ°€ 간결해지고 μ‹€ν–‰ 흐름을 μ΄ν•΄ν•˜κΈ° μ‰¬μ›Œμ§„λ‹€.

8.10 8μž₯μ—μ„œ 배운 λ‚΄μš©

ν•¨μˆ˜ν˜• 방식은 μžλ°”λ₯Ό μƒˆλ‘­κ²Œ ν”„λ‘œκ·Έλž˜λ° ν•  수 μžˆλŠ” 방식이닀.

μ΅μˆ™ν•΄μ§€λ €κ³  λ…Έλ ₯ν•˜μž.

λ©”μ„œλ“œ μ°Έμ‘°, μ˜΅μ…”λ„, λžŒλ‹€ ν‘œν˜„μ‹ 등을 적극 ν™œμš©ν•˜μž.