├── .gitignore ├── Java8Action ├── chapter1 │ └── 1장.md ├── chapter10 │ └── java8-study-optional.md ├── chapter12,13 │ └── 12장,13장.md ├── chapter14 │ └── ch14.md ├── chapter3 │ ├── 3장.md │ └── lambda1.jpg ├── chapter4 │ └── 4장.md ├── chapter5 │ ├── 5장.md │ ├── Ch5Practice.java │ ├── ch5-img1.jpeg │ └── ch5-img2.png ├── chapter6 │ └── chapter6.md ├── chapter7 │ ├── ForkJoin.md │ ├── ch7.md │ ├── img-forkjoin-1.png │ └── img-forkjoin-2.png ├── chapter8 │ └── ch8.md └── chapter9 │ └── ch9.md ├── README.md └── study-meeting-log ├── meeting-log-1(none).md ├── meeting-log-2(chapater3).md ├── meeting-log-3(chapter4,5).md └── meeting-log-4(chapter6,7).md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /Java8Action/chapter1/1장.md: -------------------------------------------------------------------------------- 1 | # Java 8 in Action 1장 summary 2 | 3 | > 자바 **탄생 이래로 가장 큰 변화**는 8버전에서 일어났다. 그동안 자바는 애플리케이션 개발 측면에서 안정성이 뛰어나고 수많은 라이브러리, 객체지향의 장점을 내세우면서 프로그래밍 세계의 대세로 오랫동안 자리잡고 있었지만, 빅데이터가 등장하고 **concurrent하게 병렬 프로세싱이 가능한 코드를 짜는 데에는 한계**가 있었다. 스레드를 사용하면 여러 문제가 발생하고 불편하기 때문에 새로 탄생하는 함수형 프로그래밍 언어들이 대세로 뜨고 있는 추세였는데, 자바 8 버전에서 그동안 자바에서 볼 수 없었던 새로운 것들이 생겨났고, 그로 인해 **병렬 처리가 쉬워지고 함수형 프로그래밍이 가능**해졌다. `stream`과 `lamda`가 그 주인공이다. 이번 스터디에서는 자바 8에 대해서 자세히 알아보도록 할 것이다. 4 | 5 | #### 이 책의 핵심과 흐름은 다음과 같다 (목차는 아님) 6 | 7 | 1. 프로그래밍 언어 생태계에서 자바의 위치 8 | 2. 스트림 처리 9 | 3. 동작 파라미터화로 메서드에 코드 전달하기 10 | 4. 병렬성과 공유 가변 데이터 11 | 5. 자바가 진화해야 하는 이유 12 | 13 | *이 내용들을 한 줄로 요약하면 '언어는 하드웨어나 프로그래머 기대의 변화에 부응할 수 있도록 변화해야 한다' 이다.* 14 | 15 | #### 자바 8에 추가된 새로운 개념 16 | 17 | * **자바 함수** 18 | 19 | * 자바 8에서는 함수를 새로운 값의 형식으로 추가했다. 멀티코어에서 병렬 프로그래밍을 활용할 수 있는 스트림과 연계될 수 있도록 함수를 만들었다. 즉, 함수 자체가 값이 되어버린 것이다. 20 | * 자바 프로그램에서 조작할 수 있는 값은 `int`, `double`, `String` 등의 기본값과 객체 레퍼런스가 있다. 21 | 22 | > **그런데 왜 함수가 필요할까?** 23 | 24 | 프로그래밍 언어의 핵심은 값을 바꾸는 것. 그리고 이 값을 전통적으로는 ***일급시민*** 이라고 부른다. 자바 프로그래밍 언어의 다양한 구조체(메서드, 클래스 등)가 값의 구조를 자유롭게 전달할 수는 없다. 이렇게 전달할 수 없는 구조체는 ***이급시민*** 이라고 한다. 즉 메서드와 클래스는 그 자체로 값이 될 수 없다. 하지만 런타임에 메서드를 전달할 수 있다면, 프로그래밍에 유용하게 활용할 수 있다. 그래서 자바 8 설계자들은 ***이급시민*** 을 ***일급시민*** 으로 바꿀 수 있는 기능을 추가한 것이다. 25 | 26 | 1. **메서드와 람다를 일급 시민으로** 27 | 28 | * **메서드 레퍼런스** 29 | 30 | ````java 31 | // 기존 자바 코드 (디렉터리에서 모든 숨겨진 파일을 필터링 한다고 가정한 코드) 32 | // FileFilter 객체로 isHidden 메서드를 감싼 다음에 File.listFiles 메서드로 전달해야함 33 | File[] hiddenFiles = new File(".").listFiles(new FileFilter(){ 34 | public boolean accept(File file){ 35 | return file.isHidden(); 36 | } 37 | }) 38 | 39 | ------------------------------------------------------------------------- 40 | // 자바 8 메서드 레퍼런스 적용 41 | 42 | File[] hiddenFiles = new File(".").listFiles(File::isHidden); 43 | ```` 44 | 45 | * `::` 은 자바 8에서 생긴 *메서드 레퍼런스* 라는 것이다. 이것의 의미는 **이 메서드를 값으로 사용하라** 이다. 46 | * 기존에 비해 문제 자체를 더 직접적으로 전달할 수 있다. 기존에 **객체 레퍼런스**(new로 객체 레퍼런스를 생성)를 이용해서 객체를 이리저리 주고받았던 것처럼 자바 8에서는 `File::isHidden` 을 이용해서 **메서드 레퍼런스** 를 만들어 전달할 수 있게 되었다. 47 | 48 | **람다 : 익명 함수** 49 | 50 | > 자바 8에서는 named 메서드를 일급시민으로 취급할 뿐 아니라 **람다(익명 함수)** 를 포함하여 **함수도 값** - 함수와 메서드는 다름 - 으로 취급할 수 있다 51 | 52 | * `(int x) -> x + 1` 의 의미는 'x라는 인수로 호출하면 x + 1을 반환' 이다. 즉, 동작을 수행하여 값이 되도록 코드를 짤 수가 있다. 53 | * 직접 메서드를 정의할 수도 있지만, 이용할 수 있는 편리한 클래스나 메서드가 없을 때 새로운 람다 문법을 이용하면 더 간결하게 코드를 구현할 수 있다. 54 | * 람다 문법 형식으로 구현된 프로그램을 함수형 프로그래밍, 즉 ''*함수를 일급시민으로 넘겨주는 프로그램을 구현한다''* 라고 한다. 55 | 56 | 2. **코드 넘겨주기: 예제** 57 | 58 | * 이것은 후에 자세히 나올 것이므로 생략한다. 59 | 60 | *TIP : 인수로 값을 받아 `true`, `false` 를 반환하는 함수를 Predicate 라고 한다.* 61 | 62 | 3. **메서드 전달에서 람다로** 63 | 64 | * 메서드를 값으로 전달하는 것은 분명 유용한 기능이다. 하지만 한두 번만 사용할 메서드를 매번 정의하는 것은 귀찮다. 자바 8에서는 람다라는 새로운 개념을 통해 해결할 수 있다. 65 | 66 | ```java 67 | fileterApples(inventory, (Apple a) -> "green".equals(a.getColor())); 68 | fileterApples(inventory, (Apple a) -> a.getWeight() > 150); 69 | fileterApples(inventory, (Apple a) -> a.getWeight() < 80 || "brown".equals(a.getColor()); 70 | ``` 71 | 72 | 위 코드는 우리가 넘겨주려는 코드를 애써 찾을 필요가 없을 정도로 더 짧고 간결하다. 하지만 람다가 몇 줄 이상으로 길어진다면, 익명 람다보다는 named 메서드를 정의하고 **메서드 레퍼런스** 를 활용하는 것이 바람직하다. 코드의 명확성이 우선시 되어야한다. 73 | 74 | **멀티코어 CPU**가 아니었다면 원래 자바 8 설계자들의 계획은 여기까지였을 것이다. 그리고 아마 자바는 filter 그리고 다음과 같은 몇몇 일반적인 라이브러리 메서드를 추가하는 방향으로 발전했을 수도 있다. 75 | 76 | ```java 77 | static Collection filter(Collection c, Predicate p); 78 | filter(inventory, (Apple a) - > a.getWeight() > 150); 79 | 80 | // 이렇게 아마 발전해 나갔을 것이다. 이렇게 구현하면. filterApples 메소드를 정의할 필요가 없다. 81 | 82 | filterApples(inventory, (Apple a) -> a.getWeight() > 150); 83 | ``` 84 | 85 | > 하지만 병렬성이라는 중요성 때문에 자바 8에서는 `filter` 와 비슷한 동작을 수행하는 연산집합을 포함하는 새로운 **스트림 API**를 제공한다. 또한 Collection과 Stream간에 변환할 수 있는 메서드(map, reduce 등)들도 제공한다. 86 | 87 | #### Stream (스트림) 88 | 89 | > 거의 모든 자바 애플리케이션은 컬렉션을 **만들고 활용**한다. 하지만 컬렉션으로 모든 문제가 해결되는 것은 아니다. 예를 들어서 리스트에서 고가의 거래만 필터링한 다음에 통화로 결과를 그룹화해야 한다고 가정하자. 다음 코드처럼 **많은 기본 코드를 구현해야한다.** 90 | 91 | ```java 92 | Map> transactionsByCurrencies = new HashMap<>(); 93 | 94 | for(Transaction transaction : transactions) { 95 | if(transaction.getPrice() > 1000) { 96 | Currency currency = transaction.getCurrency(); 97 | List transactionsForCurrency = transactionByCurrencies.get(currency); 98 | if(transactionsForCurrency == null) { 99 | transactionsForCurrency = new ArrayList<>(); 100 | transactionsByCurrencies.put(currency, transactionsForCurrency); 101 | } 102 | transactionsForCurrency.add(transaction); 103 | } 104 | } 105 | 106 | 107 | // 위 코드를 Java 8 Stream 사용해서 구현하기 108 | import static java.util.stream.Collectors.toList; 109 | Map> transactionsByCurrencies = transactions.stream() 110 | .filter((Transaction t) -> t.getPrice() > 1000) 111 | .collect(groupingBy(Transaction::getCurrency)); 112 | ``` 113 | 114 | * 기존 코드는 중첩된 제어 흐름 문장이 많아서 코드를 한 번에 이해하기도 어렵다. 하지만 스트림 API를 이용하면 간결하고 가독성이 뛰어나게 구현할 수 있다. 스트림 API에 대한 지식이 없으면 위 예제 코드가 마술처럼 보일수도 있다. 하지만 익숙해지면 차이가 확실히 눈에 보일 것이다. 115 | * 스트림 API는 컬렉션 API와는 상당히 다르다. 컬렉션에서는 반복 과정을 for-each를 돌면서 작업했다. 이런 방식의 반복을 **외부 반복** 이라고 한다. 반면 스트림 API를 이용하면 LOOP를 신경 쓸 필요가 없다. 스트림 API에서는 라이브러리 내부에서 모든 데이터가 처리된다. 이와 같은 반복을 **내부 반복**이라고 한다. 116 | * 컬렉션을 이용하면 다른 문제도 발생할 수 있다. 많은 요소를 가진 목록을 반복한다면 오랜 시간이 걸릴 수 있다는 것이다. 거대한 리스트는 어떻게 처리할까? 단일 CPU로는 거대한 데이터를 처리하기 힘들 것이다. 8개의 코어를 가진 컴퓨터라면 8개의 코어를 모두 사용하는 것이 8배 빨리 작업할 수 있게 하는 방법이다. 117 | 118 | 1. **멀티스레딩은 어렵다** 119 | 120 | * 이전 자바 버전에서 제공하는 스레드 API로 **멀테스레딩** 코드를 구현해서 병렬성을 이용하는 것은 쉽지 않다. 스레드를 잘 제어하지 못하면 원치 않는 방식으로 데이터가 바뀔 수 있다. 한마디로 다루기가 엄청 어렵다. 121 | 122 | * 자바 8은 스트림 API로 '*컬렉션을 처리하면서 발생하는 모호함과 반복적인 코드 문제*' 와 *멀티코어 활용 어려움* 이라는 두 가지 문제를 모두 해결했다. 기존의 컬렉션에서는 데이터를 처리할 때 반복되는 패턴이 너무 많았다. 이러한 반복되는 패턴을 제공하면 좋을 것이라는 아이디어가 변화의 동기가 되었다. 필터링, 데이터 추출, 데이터 그룹화 등의 기능이 있다 123 | 124 | * 각각의 CPU는 자신이 맡은 부분을 처리한다. (공장 라인을 생각하면 이해하기 쉽다. 맡은 부분만 수행하되, 동시에 돌아가는 것이다). 새로운 스트림 API가 기존의 컬렉션 API와 비슷해 보일 수 있다. 하지만 **컬렉션은 어떻게 데이터를 저장하고 접근할지에 중점을 두었고, 스트림은 데이터에 어떤 계산을 할 것인지 묘사하는 것에 중점을 뒀다.** 125 | 126 | * 컬렉션을 필터링할 수 있는 **가장 빠른 방법**은 *컬렉션을 스트림으로 바꾸고, 병렬로 처리한 다음에, 리스트로 다시 복원하는 것*이다. 스트림과 람다 표현식을 이용하면 *'병렬성을 공짜로'* 얻을 수 있고 병렬로 필터링을 할 수 있다. 127 | 128 | > 자바의 변화 과정에서 자바 8 개발자들이 겪는 어려움 중 하나는 기존 인터페이스의 변경이다. 얘를 들어 `Collections.sort` 는 사실 List 인터페이스에 포함되지만, 실제로 List에 포함된 적은 없다. 이것을 고치려면 인터페이스를 구현하는 모든 클래스를 업데이트 해야하므로 불가능에 가까웠다. 하지만 자바 8에서 **디폴트 메서드**를 통해 이 문제를 해결할 수 있게 되었다. 129 | 130 | 131 | 132 | #### 디폴트 메서드 133 | 134 | > 자바 8 에서는 라이브러리 설계자가 더 쉽게 변화할 수 있는 인터페이스를 만들 수 있도록 디폴트 메서드를 추가했다. 앞으로 인터페이스에서 디폴트 메서드를 많이 만나게 될 것이다. 하지만 프로그래머가 직접 디폴트 메서드를 구현하는 상황은 흔치 않다. 디폴트 메서드는 특정 프로그램을 구현하는 데 도움을 주는 기능이 아니라 미래에 프로그램이 쉽게 변화할 수 있는 환경을 제공하는 기능이다. 135 | 136 | ```java 137 | List heavyApples1 = inventory.stream().filter((Apple a) -> a.getWeight() > 150) 138 | .collect(toList()); 139 | 140 | List heavyApples2 = inventory.parallelStream().filter((Apple a) -> a.getWeight() > 150).collect(toList()); 141 | ``` 142 | 143 | 자바 8 이전에는 위의 stream, parallelStream 메서드를 지원하지 않았다. List 인터페이스나 Collection 인터페이스에 이러한 구현들이 없기 때문이다. 따라서 이 소스는 자바 8 이 아니면 컴파일이 불가능하다. 가장 간단한 해결책은 직접 Collection 인터페이스에 Stream을 구현하고, ArrayList에 메서드를 구현하는 방법이다. 144 | 145 | ​ *하지만 이 방법은 사용자에게 너무 큰 고통을 안겨준다* 146 | 147 | 왜냐하면 이미 Collection API의 인터페이스를 구현하는 많은 Collection Framework가 존재한다. 인터페이스에 새로운 메서드를 추가한다면 인터페이스를 구현하는 모든 클래스는 새로 추가된 메서드를 구현해야 한다. 그래서 우리는 딜레마에 빠진다. 148 | 149 | ​ *어떻게 기존의 구현을 고치지 않고도 이미 공개된 인터페이스를 변경할 수 있을까?* 150 | 151 | - 자바 8은 구현 클래스에서 구현하지 않아도 되는 메서드를 인터페이스가 포함할 수 있는 기능을 제공한다. 이것이 바로 **디폴트 메서드**이다. 152 | - 디폴트 메서드 바디는 인터페이스의 일부로 포함된다. 153 | 154 | > 예를 들어 자바 8에서는 List에 직접 sort 메서드를 호출할 수 있다. 이는 자바 8의 List 인터페이스에 다음과 같은 디폴트 메서드 정의가 추가되었기 때문이다. 155 | 156 | ```java 157 | default void sort(Comparator c) { 158 | Collections.sort(this, c); 159 | } 160 | ``` 161 | 162 | 따라서 자바 8 이전에는 List를 구현하는 모든 클래스가 sort를 구현해야 했지만 자바 8부터는 디폴트 sort를 구현하지 않아도 된다. 163 | 164 | 165 | 166 | #### 함수형 프로그램에서 가져온 다른 유용한 아이디어 167 | 168 | 일반적인 함수형 언어(SML, 오캐멀, 하스켈)도 프로그램을 돕는 여러 장치를 제공한다. 일례로 명시적으로 데이터 형식을 설명하도록 유도함으로써 null을 회피하는 기법이 있다. 컴퓨터 거장인 토니 호아레는 2009년에 다음과 같은 말을 했다. 169 | 170 | > "1965년에 NULL 레퍼런스를 발명했던 일을 회상하며 그 결정은 정말 뼈아픈 실수였다고 반성하고 있다... 단지 구현이 편리하단 이유로 NULL 레퍼런스를 만들어야겠다는 유혹을 뿌리치지 못했다." 171 | 172 | * 자바 8에서는 NullPointer 예외를 피할 수 있도록 도와주는 `Optional` 클래스를 제공한다. 값을 갖거나 갖지 않을 수 있는 컨테이너 객체다. 어떤 변수에 값이 없을 때 어떻게 처리할지 명시할 수 있다. 173 | 174 | --- 175 | 176 | ### *요약* 177 | 178 | * 언어 생태계의 모든 언어는 변화해서 살아남거나 그대로 머물면서 사라지게 되는 상황에 놓인다. 지금은 자바의 위치가 견고하지만 코볼 같은 언어의 선례를 떠올리면 자바가 영원히 지배적인 위치를 유지할 수 있는 것은 아닐 수 있다. 179 | * 자바 8은 프로그램을 더 효과적이고 간결하게 구현할 수 있는 새로운 개념과 기능을 제공한다. 180 | * 기존의 자바 프로그래밍 기법으로는 멀티코어 프로세서를 온전히 활용하기 어렵다. 181 | * 함수는 일급시민이다. 메서드를 어떻게 함수형값으로 넘겨주는지, 람다를 어떻게 구현하는지 기억하자 182 | * 자바 8의 스트림 개념 중 일부는 컬렉션에서 가져온 것이다. 스트림과 컬렉션을 적절하게 활용하면 스트림의 인수를 병렬로 처리할 수 있으며 더 가독성이 좋은 코드를 구현할 수 있다. 183 | * 인터페이스에서 디폴트 메서드를 이용하면 메서드 바디를 제공할 수 있으므로 인터페이스를 구현하는 다른 클래스에서 해당 메서드를 구현하지 않아도 된다. 184 | * 함수형 프로그래밍에서 null 처리 방법과 패턴 매칭 활용 등 흥미로운 기법을 발견할 수 있었다. 185 | -------------------------------------------------------------------------------- /Java8Action/chapter10/java8-study-optional.md: -------------------------------------------------------------------------------- 1 | # 챕터 10 : null 대신 Optional 2 | 3 | #### *이 장의 내용* 4 | 5 | * *null 레퍼런스의 문제점과 null을 멀리해야 하는 이유* 6 | * *null 대신 Optional: null로부터 안전한 도메인 모델 재구현하기* 7 | * *Optional 활용 : null 확인 코드 제거하기* 8 | * *Optional에 저장된 값을 확인하는 방법* 9 | * *값이 없을 수도 있는 상황을 고려하는 프로그래밍* 10 | 11 | --- 12 | 13 | > **null 때문에 어떤 문제가 발생할 수 있을까?** 14 | 15 | ### 1. **값이 없는 상황** 16 | 17 | ```java 18 | pulbic class Person { 19 | private Car car; 20 | public Car getCar() { return car; } 21 | } 22 | 23 | public class Car { 24 | private Insurance insurance; 25 | public Insurance getInsurance() { return insurance; } 26 | } 27 | 28 | public class insurance { 29 | private String name; 30 | public String getName() { return name; } 31 | } 32 | ``` 33 | 34 | 다음 코드에서는 어떤 문제가 발생할까? 35 | 36 | ```java 37 | public String getCarInsuranceName(Person person) { 38 | return person.getCar().getInsurance().getName(); 39 | } 40 | ``` 41 | 42 | 코드는 이상이 없어보이지만 차를 소유하지 않은 사람도 많다. getCar를 하면 Null을 반환한다. 그러면 런타임에 NullpointException이 발생하면서 프로그램 실행이 중단된다. 43 | 44 | 1. **보수적인 자세로 NullPointerException 줄이기** 45 | 46 | * null 체크 코드를 추가해서 예외 문제를 해결한다. 47 | * null로 부터 안전하려면 깊은 의심을 해야한다. 48 | * 들여쓰기 수준이 증가한다. 49 | * 들여쓰기를 없애기 위해서 메서드에 여러개의 출구를 만드는 방법도 있다. 50 | 51 | > 앞의 코드는 쉽게 에러를 일으킬 수 있다. 만약 누군가가 null일 수 있다는 사실을 깜빡 잊었다면 어떤 일이 일어날까? 52 | > 53 | > null로 값이 없다는 사실을 표현하는 것은 좋은 방법이 아니다. 따라서 값이 있거나 없음을 표현할 수 있는 좋은 방법이 필요하다. 54 | 55 | 2. **null 때문에 발생하는 문제** 56 | 57 | 1. 에러의 근원이다. 58 | 2. 코드를 어지럽힌다. 59 | 3. 아무 의미가 없다. 60 | 4. 자바 철학에 위배된다. 61 | * 자바는 개발자로부터 모든 포인터를 숨겼다. 하지만 예외가 있는데 그게 바로 null 포인터다. 62 | 5. 형식 시스템에 구멍을 만든다. 63 | * null은 무형식이며 정보를 포함하고 있지 않으므로 모든 레퍼런스 형식에 Null을 할당할 수 있다. 이런 식으로 null이 할당되기 시작하면서 시스템의 다른 부분으로 null이 퍼졌을 때 애초에 null이 어떤 의미로 사용되었는지 알 수 없다. 64 | 65 | 3. **다른 언어는 null 대신 무엇을 사용할까?** 66 | 67 | 1. Groovy 같은 언어에서는 **안전 내비게이션 연산자**(?.) 를 도입해서 null 문제를 해결했다. 68 | 69 | ```groovy 70 | def carInsuranceName = person?.car?.insurance?.name 71 | ``` 72 | 73 | 호출 체인에 null인 레퍼런스가 있으면 결과로 null이 반환된다. 74 | 75 | 2. 하스켈, 스칼라 등의 함수형 언어는 아예 다른 관점에서 null 문제를 접근한다. 76 | 77 | 1. 하스켈은 선택형값을 저장할 수 있는 Maybe라는 형식을 제공한다. 78 | 2. Maybe는 주어진 형식의 값을 갖거나 아니면 아무 값도 갖지 않을 수 있다. 79 | 3. null 레퍼런스 개념은 자연스럽게 사라진다. 80 | 4. 스칼라도 T 형식의 값을 갖거나 아무 값고 갖지 않을 수 있는 Option[T]라는 구조를 제공한다. 81 | 82 | ### Optional 클래스 소개 83 | 84 | > 자바 8은 하스켈과 스칼라의 영향을 받아서 java.util.Optional라는 새로운 클래스를 제공한다. Optional은 선택형값을 캡슐화하는 클래스다. 값이 있으면 Optional 클래스는 값을 감싼다. 값이 없으면 Optional.empty 메서드로 Optional을 반환한다. 85 | 86 | ```java 87 | pulbic class Person { 88 | private Optional car; // 차를 소유했을 수도 안했을 수도 있음 89 | public Optional getCar() { return car; } 90 | } 91 | 92 | public class Car { 93 | private Optional insurance; // 보험에 가입되어 있을 수도 없을 수도. 94 | public Optional getInsurance() { return insurance; } 95 | } 96 | 97 | public class insurance { 98 | private String name; 99 | public String getName() { return name; } 100 | } 101 | ``` 102 | 103 | * Optional 클래스를 사용함으로써 모델의 의미가 더욱 명확해졌다. (있을수도 없을수도 있는 것인지) 104 | * Optional을 이용하면 값이 없는 상황이 우리 데이터에 문제가 있는 것인지 아니면 알고리즘의 버그인지 명확하게 구분할 수 있다. 105 | * 모든 null 레퍼런스를 Optional로 대치하는 것은 바람직하지 않다. Optional의 역할은 더 이해하기 쉬운 API를 설계하도록 돕는 것이다. 106 | 107 | ### Optional 적용 패턴 108 | 109 | > Optional 형식을 이용하면 도메인 모델의 의미를 더 명확하게 만들 수 있었으며 null 레퍼런스 대신 값이 없는 상황을 표현할 수 있음을 확인했다. 이제는 활용 방법에 대해 알아보도록 하겠다. 110 | 111 | #### 1. Optional 객체 만들기 112 | 113 | 1. 정적 팩토리 메서드를 통해 빈 Optional 객체를 만들 수 있다. 114 | 115 | `Optional car = Optional.empty()` 116 | 117 | 2. null이 아닌 값으로 Optional 만들기 118 | * 정적 팩토리 메서드 Optional.of로 null이 아닌 값을 포함하는 Optional을 만들 수 있다. 119 | * `Optional car = Optional.of(car);` 120 | 3. null 값으로 Optional 만들기 121 | * 정적 팩토리 메서드 Optional.ofNullable로 null 값을 저장할 수 있는 Optional을 만들 수 있다. 122 | * `Optional car = Optional.ofNullable(car)` 123 | 124 | #### 2. 맵으로 Optional의 값을 추출하고 변환하기 125 | 126 | > 보통 객체의 정보를 추출할 때는 Optional을 사용할 때가 많다. 예를 들어 보험회사의 이름을 추출한다고 가정하자. 다음 코드처럼 이름 정보에 접근하기 전에 insurance가 null인지 확인해야 한다. 127 | 128 | ```java 129 | String name = null; 130 | if(insurance != null){ 131 | name = insurance.getName(); 132 | } 133 | ``` 134 | 135 | 이런 유형의 패턴에 사용할 수 있도록 Optional은 map 메서드를 지원한다. 136 | 137 | ```java 138 | Optional optInsurance = Optional.ofNullable(insurance); 139 | Optional name = optInsurance.map(Insurance::getName); 140 | ``` 141 | 142 | 여러 메서드를 안전하게 호출하는데, 이 코드를 어떻게 활용할 수 있을지에 대해서 알아보도록 하겠다. 143 | 144 | flatMap이라는 Optional의 또 다른 메서드를 살펴보도록 하자. 145 | 146 | 147 | 148 | #### 3. flatMap으로 Optional 객체 연결 149 | 150 | > 스트림의 flatMap은 함수를 인수로 받아서 다른 스트림을 반환하는 메서드였다. 보통 인수로 받은 함수를 스트림의 각 요소에 적용하면 스트림의 스트림이 만들어진다. 하지만 flatMap은 인수로 받은 함수를 적용해서 생성된 각각의 스트림에서 콘텐츠만 남긴다. 즉, 함수를 적용해서 새성된 모든 스트림이 하나의 스트림으로 병합되어 평준화된다. 우리도 이차원 Optional 을 일차원 Optional로 평준화해야 한다. 151 | 152 | * Optional로 자동차의 보험회사 이름 찾기 153 | 154 | ```java 155 | public String getCarInsuranceName(Optional person) { 156 | return person.flatMap(Person::getCar) // Optional 157 | .flatMap(Car::getInsurance) // Optional 158 | .map(Insurance::getName) // Optional 159 | .orElse("Unknown"); // 보험회사 이름 출력 (널이면 언노운) 160 | } 161 | ``` 162 | 163 | #### 4. 디폴트 액션과 Optional 언랩 164 | 165 | > Optional이 비어있을 때 디폴트 값을 제공할 수 있는 orElse 메서드로 값을 읽을 수 있다. Optional 클래스는 Optional 인스턴스에서 값을 읽을 수 있는 다양한 인스턴스 메서드를 제공한다. 166 | 167 | * `get()` 은 값을 읽는 가장 간단한 메서드면서 동시에 가장 안전하지 않은 메서드다. 해당 값이 있으면 반환하고 없으면 `NoSuchElementException을` 발생시킨다. 따라서 Optional에 반드시 값이 있다고 확신할 수 있는 상황 아니면 get을 사용하지 않는게 바람직하다. 168 | * `orElse(T other)` 를 사용하면 값이 없을 때 디폴트 값을 제공할 수 있다. 169 | * `orElseGet(Supplier other)` 는 `orElse` 메서드에 대응하는 게으른 버전의 메서드다. 170 | * Optional에 값이 없을 때만 Supplier가 실행된다. 171 | * 디폴트 메서드를 만드는 데 시간이 걸리거나(효율성 때문에) Optional이 비어있을 때만 디폴트값을 생성하고 싶다면 이걸 사용해야한다. 172 | * `orElseThrow` 는 Optional이 비어있을 때 예외를 발생시킨다는 점에서 get 메서드와 비슷하다. 하지만 이 메서드는 발생시킬 예외의 종류를 선택할 수 있다. 173 | * `ifPresent(Consumer consumer)`를 이용하면 값이 존재할 때 인수로 넘겨준 동작을 실행할 수 있다. 값이 없으면 아무 일도 일어나지 않는다. 174 | 175 | #### 5. 두 Optional 합치기 176 | 177 | > Person과 Car 정보를 이용해서 가장 저렴한 보험료를 제공하는 보험회사를 찾는 몇몇 복잡한 비즈니스 로직을 구현한 외부 서비스가 있다고 가정하자. 178 | 179 | ```java 180 | public Insurance findCheapestInsurance(Person person, Car car) { 181 | // 다양한 보험회사가 제공하는 서비스 조회 182 | // 모든 결과 데이터 비교 183 | return cheapestCompany; 184 | } 185 | ``` 186 | 187 | 이제 두 Optional을 인수로 받아서 Optional를 반환하는 null 안전 버전의 메서드를 구현해보자. 188 | 189 | ```java 190 | public Optional nullSafeFindCheapestInsurance(Optional person, Optional car) { 191 | if(person.isPresent() && car.isPresent()) { 192 | return Optional.of(findCheapestInsurance(person.get(), car.get())); 193 | } else { 194 | return Optional.empty(); 195 | } 196 | } 197 | ``` 198 | 199 | 안타깝게도 구현 코드는 null 확인 코드와 크게 다른점이 없다. 200 | 201 | Optional 클래스에서 제공하는 기능을 이용해서 이 코드를 더 자연스럽게 개선해보자. 202 | 203 | ```java 204 | public Optional nullSafeFindCheapestInsurance(Optional person, Optional car) { 205 | return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c))); 206 | } 207 | ``` 208 | 209 | 210 | 211 | #### 6. 필터로 특정값 거르기 212 | 213 | > 종종 객체의 메서드를 호출해서 어떤 프로퍼티를 확인해야 할 때가 있다. 예를 들어 보험회사 이름이 'CambridgeInsurance'인지 확인해야 한다고 가정하자. 이 작업을 안전하게 수행하려면 Insurance 객체가 null인지 여부를 확인한 다음에 getName 메서드를 호출해야 한다. 214 | 215 | ```java 216 | Insurance insurance = ...; 217 | if(insurance != null && "CambridgeInsurance".equals(insurance.getName())) { 218 | System.out.println("ok"); 219 | } 220 | ``` 221 | 222 | Optional 객체에 filter 메서드를 이용해서 다음과 같이 코드를 재구현할 수 있다. 223 | 224 | ```java 225 | Optional optInsurance = ...; 226 | optInsurance.filter(insurance -> 227 | "CambridgeInsurance".equals(Insurance.getName())) 228 | .ifPresent(x -> System.out.println("ok")); 229 | ``` 230 | 231 | *tip* : Optional은 최대 한 개의 요소를 포함할 수 있는 스트림과 같다. 232 | 233 | 234 | 235 | ### Optional을 사용한 실용 예제 236 | 237 | > Optional을 잘 활용하려면 기존의 알고리즘과는 다른 관점에서 접근해야 한다. 238 | 239 | #### 1. 잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기 240 | 241 | ```java 242 | Object value = map.get("key"); 243 | 244 | --> 245 | 246 | Optional value = Optional.ofNullable(map.get("key")); 247 | ``` 248 | 249 | 250 | 251 | 2번 예외와 Optional과 응용부분은 잘 모르겠다. 252 | 253 | 254 | 255 | #### 내가 생각하는 Spring Boot를 쓰면서 Optional 잘 활용하는법 256 | 257 | ```java 258 | public class UserController { 259 | 260 | @Autowired 261 | UserService userService; 262 | 263 | @GetMapping 264 | public String list(Model model){ 265 | model.setAttribute("users", userService.listUsers(?)); 266 | return "/users/list"; 267 | } 268 | } 269 | 270 | 271 | 272 | public class UserService { 273 | 274 | @Autowired 275 | UserRepository userRepository; 276 | 277 | public String listUsers(?) { 278 | return userRepository.findByUserId(?).orElseThrow(EntityNotFoundException::new); 279 | } 280 | } 281 | 282 | 283 | 284 | public interface UserRepository extends JpaRepository { 285 | Optional findByUserId(Long userId); 286 | } 287 | ``` 288 | 289 | -------------------------------------------------------------------------------- /Java8Action/chapter12,13/12장,13장.md: -------------------------------------------------------------------------------- 1 | # 12장 새로운 날짜와 시간 API 2 | 3 | ### 새로운 날짜 API 등장 배경 4 | 5 | - java.util.Date 클래스로 날짜와 시간 관련 기능 제공. 6 | - 0으로 시작하는 인덱스 등으로 유용성이 떨어짐 7 | - Java 1.1 이후, java.util.Calendar 클래스를 대안으로 제시 8 | - 그러나 0부터 시작하는 인덱스, Date, Calendar 클래스 2가지가 혼란을 가져옴 9 | - DateFormat은 스레드에 안전하지 않다. 10 | - Date와 Calendar는 모두 가변 클래스 —> 설계 및 유지보수의 어려움을 가져옴 11 | - 이러한 문제점으로 인하여, Java8에서는 Joda-Time의 많은 기능을 java.time패키지로 추가 12 | 13 | 14 | 15 | ### LocalDate, LocalTime, Instant, Duration, Period 16 | 17 | #### LocalDate와 LocalTime 18 | 19 | - LocalDate 인스턴스는 시간을 제외한 날짜를 표현하는 불변객체 20 | - 단순히, 정적 팩토리 메소드 of로 LocalDate 인스턴스를 만들 수 있음 21 | 22 | ```java 23 | LocalDate date = LocalDatae.of(2014, 3, 18); // 2014-03-18 24 | int year = date.getYear(); // 2014 25 | Month month - date.getMonth(); // MARCH 26 | int date = date.getDayOfMonth(); // 18 27 | DayOfWeek dow = date.getDayOfWeek(); // Tuesday 28 | int len = date.lengthOfMonth(); // 31(3월의 일수) 29 | boolean leap = date.isLeapYear(); // false(윤년 판단) 30 | ``` 31 | 32 | - 팩토리 메소드 now는 시스템 시계의 정보를 이용하여 현재 날짜 정보를 얻는다. 33 | - Get 메소드에 TemporalField를 전달해서 정보를 얻기도 함. 34 | - TemporalField는 시간 관련 객체에서 어떤 필드의 값에 접근할지 정의하는 인터페이스 35 | - ChronicField는 TemporalField 인터페이스를 정의하므로, ChronoField의 열거자 요소를 이용해서 원하는 정보를 얻음 36 | 37 | ```java 38 | int year = date.get(ChronoField.YEAR); 39 | int month = date.get(ChronoField.MONTH_OF_YEAR); 40 | int day = date.get(ChronoField.DAY_OF_MONTH); 41 | ``` 42 | 43 | - LocalTime 객체는 시간과 분을 인수로 받는 of 메소드와 시간, 분, 초를 인수로 받는 of 메소드가 있다. 44 | - 날짜와 시간 문자열로 LocalDate와 LocalTime의 인스턴스를 만드는 방법도 있다. 45 | 46 | ```java 47 | LocalDate date = LocalDate.parse("2014-03-18"); 48 | LocalTime time = LocalTime.parse("13:45:20"); 49 | ``` 50 | 51 | - parse메소드에 DateTimeFormatter를 전달하여, 형식 지정 가능함. 52 | 53 | 54 | 55 | #### Instant : 기계의 날짜와 시간 56 | 57 | - 기계의 관점에서 연속된 시간에서 특정 지점을 하나의 큰수로 표현하는 것이 가장 자연스러움 58 | - Java.time.Instant 클래스에서는 기계적인 관점에서 시간을 표현. 59 | - Instant클래스는 유닉스 에포크 시간(Unix epoch time - 1970/1/1 0:0:0)을 기준으로 특점지점까지의 시간을 초로 표현 60 | 61 | 62 | 63 | #### Duration과 Period의 정의 64 | 65 | - Temporal 인터페이스는 특정 시간을 모델링하는 객체의 값을 어떻게 읽고 조작할지 정의. 66 | - Duration 클래스의 정적 팩토리 메소드 between으로 두 시간 객체 사이의 지속시간을 만들 수 있음. 67 | - LocalDateTime ==> 사람이 사용, Instant ==> 기계가 사용 —> 두 인스턴스는 서로 혼합이 불가. 68 | - Duration 클래스는 초와 나노초로 시간 단위를 표현하므로 between 메소드에 LocalDate를 전달할 수 없음. 69 | - 년, 월, 일로 표현할 때는 Period 클래스를 사용한다. 70 | - Period 클래스의 팩토리 메소드 between을 이용하여, 두 LocalDate의 차이를 확인 할 수 있음. 71 | 72 | 73 | 74 | 75 | 76 | ### 날짜 조정, 파싱, 포매팅 77 | 78 | - withAttribute 메소드로 기존의 LocalDate를 바꾼 버전을 직접 간단하게 만들 수 있음. 79 | 80 | ```java 81 | LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 82 | LocalDate date2 = date1.withYear(2011); // 2011-03-18 83 | LocalDate date3 = date2.withDayOfMonth(25); // 2011-03-25 84 | LocalDate date4 = date3.with(ChronoField.MONTH_OF_YEAR, 9); // 2011-09-25 85 | ``` 86 | 87 | - 상대적인 방식으로 속성 바꾸기 88 | 89 | ```java 90 | LocalDate date1 = LocalDate.of(2013, 3, 18); // 2014-03-18 91 | LocalDate date2 = date1.plusWeeks(1); // 2014-03-25 92 | LocalDate date3 = date2.minusYear(3); // 2011-03-25 93 | LocalDate date4 = date3.plus(6, ChronoUnit.MONTHS); // 2011-09-25 94 | ``` 95 | 96 | 97 | 98 | ### TemporalAdjuster 99 | 100 | - 복잡한 날짜 조정 기능이 필요할 때, TemporalAdjuster를 전달하는 방법으로 문제를 해결. 101 | 102 | ```java 103 | import static java.time.temporal.TemporalAdjusters.*; 104 | 105 | LocalDate date1 = LocalDate.of(2014, 3, 18); // 2014-03-18 106 | LocalDate date2 = date1.with(nextOrSame(DayOfWeek.SUNDAY)); // 2014-03-23 107 | LocalDate date3 = date2.with(lastDayOfMonth()); // 2014-03-31 108 | ``` 109 | 110 | - 복잡한 날짜 조정 기능을 직관적으로 해결 가능, 그리고 커스텀 TemporalAdjuster를 구현하여 이용할 수 있음. 111 | 112 | 113 | 114 | ### 날짜와 시간 객체 출력과 파싱 115 | 116 | - java.util.DateFormat 클래스는 Thread Non-safe, java.util.DateTimeFormatter는 Thread-safe 117 | 118 | ```java 119 | DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy"); 120 | LocalDate date1 = LocalDate.of(2014, 3, 18); 121 | String formattedDate = date1.format(formatter); 122 | LocalDate date2 = LocalDate.parse(formattedDate, formatter); 123 | ``` 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | ## 13장 - 함수형 관점으로 생각하기 134 | 135 | #### 시스템 구현과 유지보수 136 | 137 | - 부작용 없음, 불변성 개념 익히기 138 | 139 | 140 | 141 | #### 공유된 가변 데이터 142 | 143 | - 자신을 포함하는 클래스의 상태 그리고 다른 객체의 상태를 바꾸지 않으며 return 문을 통해서만 자신의 결과를 반환하는 메소드를 **순수 메소드, 부작용 없는 메소드**라고 한다. 144 | - 부작용 ==> 함수 내에 포함되지 못한 기능 145 | - 자료구조를 고치거나 필드에 값을 할당(setter 메소드 같은 생성자 이외의 초기화 동작) 146 | - 예외 발생 147 | - 파일에 쓰기 등의 I/O 동작 수행 148 | - 부작용 없는 시스템의 개념은 함수형 프로그래밍에서 유래되었음. 149 | 150 | 151 | 152 | #### 선언형 프로그래밍 153 | 154 | - 어떻게(how)에 집중하는 프로그래밍 형식은 고전의 객체지향 프로그래밍에서 이용하는 방식(명령형 프로그래밍) 155 | - 어떻게가 아닌 무엇을에 집중하는 방식 —> 질의문 구현 방법 —> 내부반복 —> 질의문 자체로 문제를 어떻게 푸는지 명확하게 보여준다는 것이 내부 반복 프로그래밍의 큰 장점 156 | - '어떻게'로 접근하는 방식을 선언형 프로그래밍이라고 부르기도 한다. 157 | - 선언형 프로그래밍은 어떻게 그 목표를 달성할 것인지 규칙을 정의 158 | 159 | 160 | 161 | ### 함수형 프로그래밍이란 무엇인가. 162 | 163 | - 한 개 이상의 결과를 반환하지만, 부작용이 없어야 한다. 164 | 165 | 166 | 167 | #### 함수형 자바 168 | 169 | - 메소드는 지역변수만을 변경해야 한다. 170 | - 메소드에서 참조하는 객체가 있다면 그 객체는 불변. 171 | - 새로 생성한 객체의 필드 갱신이 외부에 노출되지 않아야 되고, 다음에 메소드를 다시 호출한 결과에 영향을 끼치면 안됨. 172 | - 함수나 메소드가 어떤 예외도 일으키면 안됨. 173 | - Optional로 예외 없이, 결과를 받아볼 수 있음. 174 | - 비함수형 동작을 감출 수 있는 상황에서만 부작용을 포함하는 라이브러리 함수를 사용해야 한다.(먼저 자료구조를 복사한다든가, 발생할 수 있는 예외를 적절히 처리하거나, 자료구조의 변경을 호출자가 알 수 없도록 감춰야 한다.) 175 | 176 | 177 | 178 | #### 참조 투명성 179 | 180 | - 부작용을 감춘다가 참조 투명성의 개념으로 귀결. 181 | - 어떤 입력이 있든 같은 결과를 생성해야 한다. 182 | 183 | 184 | 185 | #### 재귀와 반복 186 | 187 | - 순수 함수형 프로그래밍에서는 while, for문과 같은 반복문을 포함하지 않음. 188 | - 재귀를 이용하면 루프 단계마다 갱신되는 반복 변수를 제거할 수 있음. 189 | - 재귀가 cost가 높음. —> 함수 호출 시, 호출 스택에 각 호출 시 생성되는 정보를 저장할 새로운 스텍 프레임이 만들어지고, 메모리 사용량이 증가. 190 | - 꼬리 호출 최적화라는 해결책을 제공 191 | 192 | ```java 193 | static long factorialTailRecursive(long n){ 194 | return factorialHelper(1, n); 195 | } 196 | 197 | static long factorialHelper(long acc, long n) { 198 | return n == 1? acc : factorialHelper(acc * n, n-1); 199 | } 200 | ``` 201 | 202 | - factorialHelper에서 재귀 호출이 가장 마지막에서 이루어지므로 꼬리 재귀다. 203 | - 자바는 그러나 이런 최적화 기능을 제공하지 않음. 204 | - 자바8에서는 반복을 스트림으로 대체해서 변화를 피할 수 있음. 반복을 재귀로 바꾸면 더 간결하고, 부작용이 없는 알고리즘을 만들 수 있음.(????) -------------------------------------------------------------------------------- /Java8Action/chapter14/ch14.md: -------------------------------------------------------------------------------- 1 | ## 14장 : 함수형 프로그래밍 기법 2 | 3 | ### 14.1 함수는 모든 곳에 존재한다 4 | 5 | - 일급 함수 (first-class function) 6 | 7 | - 일반값처럼 취급할 수 있는 함수 8 | 9 | - 함수를 마치 일반 값처럼 사용해서 인수로 전달하거나, 결과로 반환받거나, 자료구조에 저장할 수 있음 10 | 11 | - 메서드 레퍼런스 12 | 13 | - ```java 14 | Function strToInt = Integer::parseInt; 15 | ``` 16 | 17 | 18 | 19 | #### 14.1.1 고차원 함수 20 | 21 | - 고차원 함수 22 | 23 | - 하나 이상의 함수를 인수로 받음 24 | - 함수를 결과로 반환 25 | 26 | - 자바 8의 예시 27 | 28 | ```java 29 | Comparator c = comparing(Apple::getWeight); 30 | ``` 31 | 32 | ```java 33 | Function transformationPipeline = 34 | addHeader.andThen(Letter::checkSpelling) 35 | .andThen(Letter::addFooter); 36 | ``` 37 | 38 | 39 | 40 | #### 14.1.2 커링 41 | 42 | - 커링 43 | 44 | - 함수를 모듈화하고 코드를 재사용하는 데 도움을 주는 기법 45 | - (이론적으로) x와 y라는 두 인수를 받는 함수 f를 한 개의 인수를 받는 g라는 함수로 대체하는 기법 46 | 47 | - 섭씨 -> 화씨를 변환하는 패턴을 메서드로 표현할 때 48 | 49 | - 변환하려는 값 x, 변환 요소 f, 기준치 조정 요소 b를 모두 인수로 갖는 함수 만들기 50 | 51 | - ```java 52 | static double converter(double x, double f, double b) { 53 | return x * f + b; 54 | } 55 | ``` 56 | 57 | - 두 개의 인수를 갖는 변환 함수를 생산하는 factory를 정의하기 58 | 59 | ```java 60 | static DoubleUnaryOperator curriedConverter(double f, double b) { 61 | return (double x) -> x * f + b; 62 | } 63 | ``` 64 | 65 | 66 | 67 | ### 14.2 영속 자료구조 68 | 69 | - 함수형 메서드에서는 전역 자료구조나 인수로 전달된 구조를 갱신할 수 없다 (참조 투명성 위배, 인수를 결과로 단순하게 매핑할 수 있는 능력 상실) 70 | - 자료구조를 고칠 수 없는 상황에서도 자료구조로 프로그램 구현하기 가능한가? 71 | 72 | #### 14.2.1 파괴적인 갱신과 함수형 73 | 74 | - 단방향 연결 리스트로 구현된 TrainJounrey 75 | 76 | ```java 77 | class TrainJourney { 78 | public int price; 79 | public TrainJourney onward; 80 | public TrainJourney(int p, TrainJourney t) { 81 | price = p; 82 | onward = t; 83 | } 84 | 85 | static TrainJourney link(TrainJourney a, TrainJourney b) { 86 | if(a == null) return b; 87 | TrainJourney t = a; 88 | while(t.ownard != null){ 89 | t = t.onward; 90 | } 91 | t.onward = b; 92 | return a; 93 | } 94 | } 95 | 96 | 97 | ``` 98 | 99 | - firstJourney (X -> Y), secondJourney (Y -> Z) 100 | 101 | - 기존 방법 102 | 103 | - link(firstJourney, secondJourney) 호출시 firstJourney가 secondJourney를 포함하며 파괴적 갱신(firstJourney를 변경시킴)이 일어남 104 | - firstJourney는 본래 의도와 달리 X->Z의 여정이 됨 105 | - 결과적으로 이러한 부작용을 발생시키지 않도록 주석이나 문서로 코드 사용법에 대해 부가 설명이 필요해짐 (그리고 유지보수의 고통) 106 | 107 | - 함수형에서는 108 | 109 | - 계산 결과를 표현할 자료구조가 필요하면 기존의 자료구조를 갱신하지 않도록 새로운 자료구조를 만들어야 함 110 | 111 | - ``` 112 | static TrainJourney append(TrainJourney a, TrainJourney b) { 113 | return a==null ? b : new TrainJourney(a.price, append(a.onward, b); 114 | } 115 | ``` 116 | 117 | - 118 | 119 | #### 14.2.2 트리를 사용한 다른 예제 120 | 121 | - 기존의 트리를 갱신한다. 122 | 123 | #### 14.2.3 함수형 접근법 사용 124 | 125 | - 인수로 가능한 많은 정보를 받아서 126 | - 새로운 트리를 만들어 반환한다. 127 | 128 | - 함수형 프로그래밍에서 요구하는 단 한 가지 조건 129 | - **결과 자료구조를 바꾸지 말라!** 130 | 131 | ### 14.3 스트림과 게으른 평가 132 | 133 | - 스트림은 단 한 번만 소비할 수 있다는 제약이 있어 재귀적으로 정의 불가 134 | 135 | #### 14.3.1 자기 정의 스트림 136 | 137 | - 소수 스트림 138 | 139 | - 소수로 나눌 수 있는 모든 수는 제외할 수 있다. 140 | 141 | - 1. 소수를 선택할 숫자 스트림 준비 142 | 143 | ```java 144 | static Intstream numbers() { 145 | return IntStream.iterate(2, n -> n+1); 146 | } 147 | ``` 148 | 149 | 2. 스트림에서 첫 번째 수(head) 가져옴 150 | 151 | ```java 152 | static int head(IntStream numbers) { 153 | return numbers.findFirst().getAsInt(); 154 | } 155 | 156 | ``` 157 | 158 | 3. 스트림의 꼬리에서 가져온 수로 나누어떨어지는 모든 수를 걸러 제외시킴 159 | 160 | ```java 161 | static IntStream tail(IntStream numbers) { 162 | return numbers.skip(1); 163 | } 164 | 165 | ``` 166 | 167 | 4. 남은 숫자만 포함하는 새로운 스트림에서 소수 찾기 (1번부터 반복) 168 | 169 | ```java 170 | static IntStream primes(IntStream numbers) { 171 | int head = head(numbers); 172 | return IntStream.concat( 173 | IntStream.of(head), 174 | primes(tail(numbers).filter(n -> n % head != 0)) 175 | ); 176 | } 177 | 178 | ``` 179 | 180 | - 단계 4의 코드를 실행하면 IllegalStateException 발생 181 | - 스트림을 머리와 꼬리로 분리하는 두 개의 최종연산을 사용했기 때문 (최종연산 호출시 스트림은 완전 소비된다) 182 | - 또한 IntStream.concat은 두 개의 스트림 인스턴스를 인수로 받음 183 | - 무한 재귀 184 | - 게으른 평가 lazy evaluation 185 | - 소수를 처리할 필요가 있을 때에만 스트림을 실제로 평가 186 | 187 | #### 14.3.2 게으른 리스트 만들기 188 | 189 | - 게으른 스트림 190 | - 스트림에 일련의 연산을 적용하면 연산이 수행되지 않고 일단 저장 191 | - 최종 연산을 적용해서 실제 계산을 해야 하는 상황에서만 실제 연산이 이루어짐 192 | - Supplier 활용 193 | - 성능 194 | - 게으른 실행으로 인한 오버헤드가 더 커질 수도 있다 195 | - 효율성이 떨어진다면 전통적인 방식을 사용하자 196 | 197 | ### 14.4 패턴 매칭 198 | 199 | - 자바에서는 지원하지 않는다. 200 | 201 | #### 14.4.1 방문자 디자인 패턴 202 | 203 | ### 14.5 기타 정보 204 | 205 | #### 14.5.1 캐싱 또는 기억화 206 | 207 | - 기억화 memorization 208 | - 매번 새로 탐색하고 계산을 반복해서 수행하면 계산 비용이 너무 비싸니 209 | - 메소드에 레퍼로 캐시를 추가하는 기법 210 | 211 | #### 14.5.3 콤비네이터 212 | 213 | - 함수를 조합하는 기능 214 | 215 | ```java 216 | static Function compose(Function g, Function f) { 217 | return x -> g.apply(f.apply(x)); 218 | } 219 | 220 | ``` 221 | 222 | 223 | 224 | -------------------------------------------------------------------------------- /Java8Action/chapter3/3장.md: -------------------------------------------------------------------------------- 1 | # 3장 람다 표현식 2 | ## 람다란 무엇인가? 3 | **메소드로 전달할 수 있는 익명 함수를 단순화한 것** 4 | - ### 특징 5 | * 익명 : 보통 메소드와 달리 **이름이 없음** 6 | * 함수 : 메소드처럼 **특정 클래스에 종속되지 않음** 7 | * 전달 : 람다 표현식을 **메소드 인수로 전달**하거나 **변수로 저장**할 수 있음 8 | * 간결성 : 익명 클래스처럼 많은 *자질구레한 코드*를 구현할 필요없음! 9 | 10 | ```java 11 | // 람다 적용 전 12 | Comparator byWeight = new Comparator () { 13 | public int compare (Apple a1, Apple a2) { 14 | return a1.getWeight().comapreTo(a2.getWeight()); 15 | } 16 | } 17 | ``` 18 | 19 | ```java 20 | // 람다 적용 후 21 | Comparator byWeight = 22 | (Apple a1, Apple a2) -> a2.getWeight().compareTo(a2.getWeight()); 23 | ``` 24 | 25 | ## 람다의 구성과 표현 26 | - ### 구성 27 | ![img](./lambda1.jpg) 28 | - ### 표현 29 | * ( Parameters ) -> expression 30 | * ( Parameters ) -> { statesments; } 31 | - expression, statesments 32 | - 어떤 값으로 떨어지면 expression, 일반적인 구문은 statesments 33 | - https://www.quora.com/Whats-the-difference-between-a-statement-and-an-expression-in-Python-Why-is-print-%E2%80%98hi%E2%80%99-a-statement-while-other-functions-are-expressions 34 | 35 | ## 어디에, 어떻게 람다를 사용할까? 36 | - ### 함수형 인터페이스 37 | * 정확히 하나의 추상 메소드를 지정하는 인터페이스 38 | * 람다 표현식으로 함수형 인터페이스의 추상 메소드 구현을 직접 전달 39 | - 전체 표현식을 함수형 인터페이스의 인스턴스로 취급 40 | ```java 41 | public class Main { 42 | public static void process(Runnable r){ 43 | r.run(); 44 | } 45 | public static void main(String[] args) 46 | { 47 | Runnable r1 = () -> System.out.println("Hello, r1"); 48 | Runnable r2 = new Runnable() { 49 | @Override 50 | public void run() { 51 | System.out.println("Hello, r2"); 52 | } 53 | }; 54 | 55 | r1.run(); // hello, r1 56 | r2.run(); // hello, r2 57 | process(()->System.out.println("Hello, r3")); // hello, r3 58 | } 59 | } 60 | ``` 61 | - ### 함수 디스크립터 62 | * 람다 표현식의 시그니처를 서술하는 메소드 63 | ```java 64 | @FunctionalInterface 65 | public interface FirstInterface { 66 | //Single abstract method 67 | public void singleMethod(String param); 68 | } 69 | // (String) -> void 70 | 71 | @FunctionalInterface 72 | public interface SecondInterface { 73 | //Single abstract method 74 | public long computeSum(int num1, int num2); 75 | } 76 | // (int, int) -> long 77 | 78 | @FunctionalInterface 79 | public interface Function { 80 | /** 81 | * Applies this function to the given argument. 82 | * @param t the function argument 83 | * @return the function result of type R 84 | */ 85 | R apply(T t); 86 | // T -> R 87 | ``` 88 | ## 함수형 인터페이스 사용 89 | - ### Predicate 90 | * T 형식의 객체를 파라미터로 받아 boolean 반환 91 | * T -> boolean 92 | - ### Consumer 93 | * T 형식의 객체를 파라미터로 받아 void 반환 94 | * T -> () 95 | - ### Supplier 96 | * void를 받아 제네릭 형식 R로 반환 97 | * () -> R 98 | - ### Function 99 | * T형식의 객체를 파라미터로 받아 R로 반환 100 | * T -> R 101 | - ### 기본형 특화 102 | * 자바의 모든 형식은 참조형 혹은 기본형 103 | * 하지만 제네릭은 내부 구현상 어쩔 수 없이 참조형만 사용 가능 104 | * 그래서 **박싱**(기본형 -> 참조형)과 **언박싱**(참조형->기본형) 제공 105 | - 오토박싱으로 저 과정은 자동으로 해주지만, 자원을 소모하게 됨. 106 | - 그래서 오토박싱을 피할 수 있는 버전의 함수형 인터페이스 제공 107 | - IntConsumer, LongConsumer... 엄청 많음! 108 | 109 | ## 형식 검사, 추론, 제약 110 | - ### 형식 검사 111 | * 람다 표현식 자체에는 람다가 어떤 함수형 인터페이스를 구현하는 지에 대한 정보가 없음 112 | * #### 대상형식 113 | - 어떤 컨텍스트에서 기대되는 람다 표현식의 형식 114 | - 람다 표현식이 예외를 던질 수 있다면, 추상 메소드도 같은 예외를 던질 수 있어야 함. 115 | * 형식 검사 과정 116 | ```java 117 | List heavyThan150g = filter(inventory, (Apple a1) -> a1.getWeight() > 150); 118 | ``` 119 | 1. filter 메소드의 선언 확인 120 | 2. filter 메소드는 2번째 파라미터로 Predicate 형식 기대 121 | 3. Predicate은 test라는 추상 메소드를 정의하는 함수형 인터페이스 122 | 4. test 메소드는 **Apple -> boolean** 이라는 함수 디스크립터 묘사 123 | 5. filter 메소드로 전달된 람다 표현식은 이 요구사항을 만족해야 함! 124 | 125 | * ### 같은 람다, 다른 함수형 인터페이스 126 | - 대상 형식이 같다면 같은 람다 표현식이더라도, 호환되는 추상 메소드를 가진 함수형 인터페이스로 사용 가능 127 | ```java 128 | // T, T -> int 129 | Comparator c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 130 | 131 | // T, U -> int 132 | ToIntBiFunction c2 =(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 133 | 134 | // T, U -> R 135 | BiFunction c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); 136 | ``` 137 | * ### 형식 추론 138 | - 자바 컴파일러는 람다 표현식이 사용된 컨텍스트를 이용하여 함수형 인터페이스를 추론 139 | - 즉, 대상 형식을 이용해서 함수의 디스크립터를 알 수 있으므로 컴파일러는 람다의 시그니처도 추론 가능 140 | - 파라미터의 타입 생략 가능 141 | - 파라미터가 하나라면 괄호 생략 가능 142 | * ### 지역 변수 사용 143 | - 람다 변수는 외부에서 정의된 변수를 사용할 수 있음 144 | - 단, final 혹은 **final처럼 취급되어야 함.** 145 | * effectively Final Variables이라고 함 146 | * http://ilkinulas.github.io/programming/java/2016/03/27/effectively-final-java.html 147 | 148 | ## 메소드 레퍼런스 149 | - 기존 메소드 정의를 재활용해서 람다처럼 전달할 수 있음 150 | - 새로운 기능이 아니라 하나의 메소드를 참조하는 람다를 편리하게 표현할 수 있는 문법 151 | - 만드는 방법 152 | - https://imcts.github.io/java-method-reference/ 153 | 154 | ## 람다 표현식을 조합할 수 있는 유용한 메소드 155 | - ### Comparator 조합 156 | * thenComparing 157 | * reversed 158 | - ### Predicate 조합 159 | * negate 160 | * and 161 | * or 162 | - ### Function 조합 163 | * andThen 164 | * Compose 165 | * x.andThen(y) == y.andThen(x)? 166 | - https://stackoverflow.com/questions/43849066/java-8-functions-compose-and-andthen 167 | 168 | 169 |
170 | 171 | - **읽어볼만한 글** 172 | - [Java 8 lambda expressions tips](https://www.baeldung.com/java-8-lambda-expressions-tips) 173 | - [람다가 이끌어 갈 모던 JAVA](https://d2.naver.com/helloworld/4911107) 174 | - [Java Lambda Introduction](http://www.java2s.com/Tutorials/Java/Java_Lambda/index.htm) -------------------------------------------------------------------------------- /Java8Action/chapter3/lambda1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/11STNEWBIE/java-8-study/14d97465189a5d4206341ed45915b3e09a487952/Java8Action/chapter3/lambda1.jpg -------------------------------------------------------------------------------- /Java8Action/chapter4/4장.md: -------------------------------------------------------------------------------- 1 | # 4장 Stream 소개 2 | ## 1. Stream이란? 3 | - YouTube 스트리밍과 비슷하게 생각… 다 다운받고 영상으로 보는 것이 아닌, 내려받는대로 재생한다는 느낌. 4 | - 데이터 반복문을 간단하게 처리? 
—> 개인적으로 이 기능이 우선적으로 와닿았음 5 | - 멀티스레드 코드를 개발자가 구현하지 않아도 데이터를 병렬로 처리할 수 있다? 6 | - 사전적 용어 : 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소 7 | - 연속된 요소 : Collection과 비슷하게 특정 요소 형식으로 연속된 값 집합(다른 점은, Collection은 데이터, Stream은 계산 중점) 8 | - 소스 : Collection, Array, File등의 데이터 제공 소스를 소비 9 | - 데이터 처리 연산 : DB와 비슷한 연산, Functional Language 지원 연산을 지원한다(?? Python을 모름… JS도 그냥 jQuery로 때려박기만 해봐서) 10 | 11 | ## 2. Stream과 Collection 12 | - Stream은 단 한번만 탐색 가능 —> Consume되면 끝 13 | - Collection은 사용자가 직접 요소를 반복(명시적으로 개발자 의지) 14 | 15 | 16 | ## 3. Stream 연산 17 | - 중간연산 : filter나 sorted와 같이 다른 Stream을 반환하는 연산(return 타입이 그냥 Stream 이라고 생각하면 되는 듯) 18 | - Stream 파이프라인에서 결과를 도출하는 연산(return 타입이 그냥 Stream이 아닌 것이라고 생각하면 될 듯) -------------------------------------------------------------------------------- /Java8Action/chapter5/5장.md: -------------------------------------------------------------------------------- 1 | # 스트림 활용 2 | 3 | ## 1 스트림 4 | * 스트림 개념 5 | > To perform a computation, stream operations are composed into a stream pipeline. A stream pipeline consists of a *source* (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more *intermediate operations* (which transform a stream into another stream, such as filter(Predicate)), and a *terminal operation* (which produces a result or side-effect, such as count() or forEach(Consumer)). Streams are **lazy**; computation on the source data is only performed **when the terminal operation is initiated**, and source elements are **consumed only as needed**. 6 | 7 | [JAVA8 Stream docs](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) 8 | 9 | 10 | 11 | * Stateful operation & stateless operation 12 | > Intermediate operations are further divided into stateless and stateful operations. 13 | Stateless operations, such as filter and map, retain no state from previously seen element when processing a new element -- each element can be processed independently of operations on other elements. Stateful operations, such as distinct and sorted, may incorporate state from previously seen elements when processing new elements. 14 | Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream. As a result, **under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or may need to buffer significant data.** Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, whether sequential or parallel, with minimal data buffering. 15 | 16 | [JAVA8 Stream-package docs](https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html) 17 | 18 | ## 2 필터링, 슬라이싱 19 | 스트림 요소를 선택하고, 크기를 축소하는 방법들을 스트림 API는 제공한다. 20 | 21 | * **filter** 22 | Predicate를 인수로 받아서 일치하는 요소를 포함하는 스트림 반환 23 | ex> 가격이 10,000원 이하인 메뉴를 고를 때 24 | ```java 25 | menus.stream() 26 | .filter(menu -> menu.getPrice() < 10000) 27 | ... 28 | ``` 29 | * **distinct** 30 | 고유 요소로 이루어진 스트림을 반환. 고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정. 31 | ex> 물건 항목의 이름을 중첩되지 않게 확인하고자 할 때 32 | ```java 33 | items.stream() 34 | .map(Item::getName) 35 | .distinct() 36 | ... 37 | ``` 38 | 39 | * **limit** 40 | 주어진 사이즈 이하의 크기를 갖는 새로운 스트림을 반환한다. 41 | ex> 10,000이하인 메뉴 3개까지만 필요할 때 42 | ```java 43 | menus.stream() 44 | .filter(menu -> menu.getPrice() < 10000) 45 | .limit(3) 46 | ... 47 | ``` 48 | 49 | * **skip** 50 | 처음 n개 요소를 제외한 스트림을 반환한다(skip(n)). 51 | ex> 필터를 적용한 메뉴에서 앞의 3개는 제외하고자 할 때 52 | ```java 53 | menus.stream() 54 | .filter(menu -> menu.getPrice() < 10000) 55 | .skip(3) 56 | ... 57 | ``` 58 | 59 | ## 3 매핑 60 | map과 flatMap메소드는 특정 데이터를 선택하는 기능을 제공한다. 61 | 함수 적용 결과 새로운 요소로 매핑할 때, 수정보다는 새로운 것을 만드는 개념에 가깝기에 '변환에 가까운 매핑'이라 할 수 있다. 62 | 63 | flatMap은 생성된 스트림을 하나의 스트림으로 평면화할 수 있다. 64 | 즉, 스트림의 각 값을 다른 스트림으로 만든 다음에 모든 스트림을 하나의 스트림으로 연결하는 기능을 수행한다. 65 | 66 | ## 4 검색 및 매칭 67 | * 쇼트서킷 : 모든 스트림 요소를 처리하지 않고도 결과 반환 가능. 68 | * anyMatch : 주어진 스트림에서 적어도 하나 프레디케이트와 일치하는지 검사 69 | * allMatch : 모든 요소가 프레디케이트와 일치여부 검사 70 | * noneMatch : allMatch와 반대 연산 71 | * findAny, findFirst 등 72 | 73 | * Optional 74 | 값의 존재여부 표현하는 컨테이너 클래스. 75 | 값이 존재하는지 확인하고, 값이 없을 때 어떻게 처리할 것인지 강제하는 기능을 제공. 76 | 77 | ## 5 리듀싱 78 | 리듀싱 연산 : 모든 스트림 요소를 처리해서 값으로 도출. 마치 종이를 작은 조각이 될 때까지 반복해서 접는 것과 같아, **폴드** 라고도 불림. 79 | 80 | ex> 81 | ```java 82 | int sum1 = numbers.stream().reduce(0, Integer::sum); 83 | 84 | Optional sum2 = numbers.stream().reduce((a,b) -> (a + b)); 85 | ``` 86 | 87 | cf) map과 reduce를 연결하는 기법을 *map-reduce 패턴* 이라고 하며, 쉽게 병렬화하는 특징 있음. 88 | 89 | ## 6 기본형 특화 스트림 90 | * 자바 8에서는 세 가지 기본형 특화 스트림(primitive stream specialization)을 제공. 91 | * IntStream, DoubleStream, LongStream이 그 세 가지며, 박싱 과정에서 일어나는 효율성과 관련 있음. 92 | * mapToInt, mapToDouble, mapToLong과 같은 메소드를 통해서 각각 IntStream, DoubleStream, LongStream 반환. 93 | * max, sum과 같은 숫자 관련 리듀싱 연산 수행 메소드 제공. 94 | * 기본형 특화 스트림에서는 .boxed() 메소드를 호출함으로 숫자 스트림 -> 스트림 변환 가능. 95 | * Stream에서 Optional있듯, IntStream에서 OptionalInt 존재. 96 | * .rangeClosed(1, 100), .range(0, 101)처럼 특정 범위 숫자 생성 가능. 97 | 98 | ## 7 무한 스트림 99 | 요청할 때마다 값을 생산할 수 있으며 끝이 없으므로, 무한 스트림을 만든다. 이러한 스트림을 언바운드 스트림이라고 표현한다. 100 | 무한한 크기의 스트림이기에, limit을 사용해서 명시적으로 스트림의 크기를 제한해야한다. 그렇지 않으면, 최종연산 수행 시 아무결과도 계산되지 않으며 정렬 및 리듀스를 수행할 수 없다. 101 | * iterate 102 | 초기값과 람다를 인수로 받아서, 새로운 값을 끊임없이 생산할 수 있다. 103 | * generate 104 | iterate와 달리 생산된 각 값을 연속적으로 계산하지는 않는다. 105 | 106 | ## 연습문제 107 | 108 | [풀어보기](./Ch5Practice.java) 109 | 110 | 111 | 112 | ## 추가사항 (19.01.24) 113 | 114 | 개요 : 115 | 아래의 코드에서 *map*이 아닌 *flatMap*을 사용하여'평면화된 스트림을 형성해야한다'는 관점에서, 잘못된 코드의 내부 작용을 분석하고자 함 116 | (왜 잘못된건지 파악 목표). 117 | 118 | 원하지 않는 결과를 반환하지만, 각 단계에서 반환하는 자료 형태를 파악하고자 함. 119 | 120 | 이해를 원하는 팀원의 요청으로 설명을 추가함. 121 | 122 | 123 | ```java 124 | 125 | List words = Arrays.asList("Hello", "World"); 126 | // List을 원하지만, 그렇지 않은 자료형태를 반환하는 코드 127 | List> collect = 128 | words.stream() 129 | .map(word -> word.split("")) 130 | .distinct() // 내부 동작 확인용으로 추가 131 | .map(Arrays::stream) 132 | .distinct() 133 | .collect(Collectors.toList()); 134 | 135 | ``` 136 | 137 | 이에 대한 설명 이미지는 다음과 같다. 138 | 139 | ![img](./ch5-img1.jpeg) 140 | 141 | 이를 IDE(IntelliJ)에서 확인한 자료구조 및 출력 형태는 다음과 같다. 142 | 143 | ![img](./ch5-img2.png) 144 | 145 | 146 | -------------------------------------------------------------------------------- /Java8Action/chapter5/Ch5Practice.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.Comparator; 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | public class Ch5StudyPractice { 7 | 8 | public static void main(String[] args) { 9 | 10 | // Q1 : 4개의 단어로부터 문자들을 추출하고, 이 문자들이 고유하게 정렬된 리스트로 만들기 11 | String[] strArr = {"Hello", "World", "Home", "Study"}; 12 | 13 | // TODO : 옆에 반환되는 자료구조를 써봅시다 14 | List result = Arrays.stream(strArr) // ??? 15 | .map(word -> word.split("")) // ??? 16 | .flatMap(Arrays::stream) // ??? 17 | .map(String::toLowerCase) 18 | .distinct() // ??? 19 | .sorted() 20 | .collect(Collectors.toList()); 21 | // output : d e h l m o r s t u w y 22 | 23 | 24 | // Q2, Q3, Q4 : 주어진 문제 풀기 25 | List newBies = Arrays.asList( 26 | new NewBie("jinwoo", "finding", 123), 27 | new NewBie("hyukjin", "finding", 153), 28 | new NewBie("jiwon-kang", "finding", 142), 29 | new NewBie("jiwon-kwon", "user-account", 125), 30 | new NewBie("dongmin", "SO", 221), 31 | new NewBie("kyubum", "rm", 266), 32 | new NewBie("juwon", "pdp", 177), 33 | new NewBie("hyejin", "escrow", 193) 34 | ); 35 | 36 | 37 | 38 | int ans2 = 0; // Q2 TODO : 사번 160이상인 사람들의 사번의 합 39 | System.out.println("Q2: " + ans2); 40 | 41 | 42 | 43 | String ans3 = ""; // Q3 TODO : 가장 높은 사번의 사람 이름 출력 44 | System.out.println("Q3: " + ans3); 45 | 46 | 47 | 48 | long ans4 = 0; // Q4 TODO : 부서가 finding이 아닌 사람들 카운트 49 | System.out.println("Q4: " + ans4); 50 | 51 | 52 | } 53 | } 54 | 55 | 56 | class NewBie { 57 | private String name; 58 | private String department; 59 | private int id; 60 | 61 | public NewBie(String name, String department, int id) { 62 | this.name = name; 63 | this.department = department; 64 | this.id = id; 65 | } 66 | 67 | public String getName() { 68 | return name; 69 | } 70 | 71 | public int getId() { 72 | return id; 73 | } 74 | 75 | public String getDepartment() { 76 | return department; 77 | } 78 | 79 | @Override 80 | public String toString() { 81 | return String.format("name : %s - dep : %s - id : %d", getName(), getDepartment(), 82 | getId()); 83 | } 84 | } 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | /** 95 | * 96 | * 97 | * 아래에는 해답이 있습니다. 다 푸신 경우만 확인! 98 | * 99 | * 100 | * 101 | * 102 | * */ 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | /* 113 | * ANSWER 114 | * 115 | * Q1 : Stream / Stream / Stream / Stream 116 | * 117 | * Q2 : 118 | * 119 | int ans2 = newBies.stream() 120 | .filter(a -> a.getId() >= 160) 121 | .map(NewBie::getId) 122 | .reduce(0, Integer::sum); 123 | * 124 | * ans: 857 125 | * 126 | * 127 | * Q3 : 128 | String ans3 = newBies.stream() 129 | .max(Comparator.comparing(NewBie::getId)) 130 | .map(NewBie::getName) 131 | .get(); 132 | * 133 | * ans: kyubum 134 | * 135 | * 136 | * Q4 : 137 | long ans4 = newBies.stream() 138 | .filter(newbie -> !"finding".equals(newbie.getDepartment())) 139 | .count(); 140 | * 141 | * ans : 5 142 | * 143 | * */ 144 | -------------------------------------------------------------------------------- /Java8Action/chapter5/ch5-img1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/11STNEWBIE/java-8-study/14d97465189a5d4206341ed45915b3e09a487952/Java8Action/chapter5/ch5-img1.jpeg -------------------------------------------------------------------------------- /Java8Action/chapter5/ch5-img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/11STNEWBIE/java-8-study/14d97465189a5d4206341ed45915b3e09a487952/Java8Action/chapter5/ch5-img2.png -------------------------------------------------------------------------------- /Java8Action/chapter6/chapter6.md: -------------------------------------------------------------------------------- 1 | # chapter 6 : 스트림으로 데이터 수집 2 | 3 | *이 장의 목표* 4 | 5 | “ Collectors 클래스로 Collection을 만들고 사용하기 ” 6 | 7 | “ 데이터 스트림을 하나의 값으로 reduce(스트림 합치기) 하기 ” 8 | 9 | “ 특별한 Reducing 요약 연산 ” 10 | 11 | “ 데이터의 그룹화(groupingBy)와 분할(프레디케이트) ” 12 | 13 | “ 자신만의 Custom Collector 개발 ” 14 | 15 | **reduce 연산(하나로 수렴)을 통해 스트림을 다양한 형태로 collect 하는 방법에 대한 내용을 담고 있는 챕터 입니다.** 16 | 17 | --- 18 | 19 | ### 이전 챕터들과의 차이점 20 | 21 | * filter, map과 같은 중간 연산은 Stream을 반환하며 파이프라인을 계속해서 구성한다. 따라서 Stream의 요소를 소비하지 않는다. 22 | * 하지만, count, reduce 같은 최종 연산은 **Stream의 요소들을 소비**해서 결과를 도출한다. 23 | * 앞선 Chapter에서는 최종 연산 collect를 사용했다. 하지만 우리는 **항상 toList만 사용**하여 Stream 요소를 리스트로만 변환했다. 24 | * Chapter 6은 collect에 **다양한 요소 누적 방식을 파라미터로 받아**서 Stream을 최종 결과로 도출하는 **reducing 연산을 수행**할 수 있음을 설명한다. 25 | 26 | #### 통화별로 트랜잭션을 그룹화한 코드 27 | 28 | ![image](https://user-images.githubusercontent.com/14870706/51952555-498c8000-247c-11e9-82ae-3215a954199a.png) 29 | 30 | * 기존 명령형 버전 코드는 다음과 같이 직접 객체에 ask 해서 데이터를 가지고 온 뒤, 로직을 짜는 형태다. 따라서 로직이 복잡하고 가독성이 떨어진다. 31 | 32 | ![image](https://user-images.githubusercontent.com/14870706/51952571-54dfab80-247c-11e9-87a8-6c8dbac619cb.png) 33 | 34 | * 함수형 버전 코드는 다음과 같이 **Tell Don't Ask** 를 잘 따르는 형태다. 객체에 메시지만 전달하고 데이터를 요청하지 않는다. 모든 로직은 해당 객체의 스트림에서 처리된다. 가독성이 뛰어나다. 35 | 36 | > Stream에 toList를 사용하는 대신 더 범용적인 **Collector** 파라미터를 collect 메서드에 전달함으로써 원하는 연산을 간결하게 구현할 수 있게 되었다. 또한 가독성도 뛰어나졌다. 그렇다면 **Collector**란 무엇인가? 37 | 38 | ## Collector 39 | 40 | > **Collector의 사전적 의미** : 수집가; 징수원 41 | 42 | 자바 8에서 Collector는 Stream 요소들을 어떻게 분류하고 모을지(reducing)를 담당하는 인터페이스이다. 즉, Collector 인터페이스를 implement하고 구현하여 스트림의 요소를 어떤 식으로 도출할지 지정한다. 43 | 44 | ![image](https://user-images.githubusercontent.com/14870706/51952586-62953100-247c-11e9-9188-aad15895baeb.png) 45 | 46 | * 예를 들어, 아까 본 groupingBy는 **Collectors**라는 클래스에 미리 구현된 **‘팩토리 메서드 함수’**다. 특정 키값을 기준으로 Map을 도출하는 역할을 하고 Collector를 반환한다. 47 | 48 | *이제부터 우리가 알아볼 **Collectors** 클래스에서 기본적으로 제공하는 메서드의 기능* 49 | 50 | * 스트림 요소를 하나의 값으로 reduce하고 요약 51 | * 스트림 요소들의 그룹화 52 | * 스트림 요소들의 분할 (이것도 결국 bool 그룹화) 53 | 54 | --- 55 | 56 | ### 첫번째, Reducing과 요약 57 | 58 | 1. 스트림값에서 최댓값 최솟값 찾기 59 | 60 | ![image](https://user-images.githubusercontent.com/14870706/51952607-80629600-247c-11e9-9bd1-f9f31216ec41.png) 61 | 62 | 2. 요약 연산 (필드의 합계나 평균 등) 63 | 64 | ![image](https://user-images.githubusercontent.com/14870706/51952619-8b1d2b00-247c-11e9-9e2f-c23326c06c10.png) 65 | 66 | ![image](https://user-images.githubusercontent.com/14870706/51952628-95d7c000-247c-11e9-9ac6-c9f4bc1bfea2.png) 67 | 68 | ![image](https://user-images.githubusercontent.com/14870706/51952645-a5ef9f80-247c-11e9-8be6-be3b1e06b350.png) 69 | 70 | * 두 개 이상의 연산(ex 합계, 평균 동시에 필요)을 한번에 수행하는 역할 71 | 72 | 3. 문자열 연결 73 | 74 | ![image](https://user-images.githubusercontent.com/14870706/51952657-af790780-247c-11e9-9dc7-25177e43edd1.png) 75 | 76 | 77 | 78 | > 이러한 모든 Collector는 reducing 팩토리 메서드로도 정의할 수 있다. (가독성이나 편리성 측면에서 권장하지 않는다) 79 | 80 | ![image](https://user-images.githubusercontent.com/14870706/51949993-31176800-2472-11e9-8ef6-0f9312729edd.png) 81 | 82 | * 첫 번째 인수 : reducing 연산의 시작값. 스트림에 인수가 없을 때는 반환값. 83 | * 두 번째 인수 : 요리를 칼로리 정수로 변환하는 변환함수. 84 | * 세 번째 인수 : 어떠한 연산을 할건지를 명시하는 BinaryOperator. 85 | 86 | 87 | 88 | ![image](https://user-images.githubusercontent.com/14870706/51950049-5906cb80-2472-11e9-9862-131a2c8dcb08.png) 89 | 90 | ![image](https://user-images.githubusercontent.com/14870706/51950071-6623ba80-2472-11e9-9fe0-a8a8c518e072.png) 91 | 92 | **구현 방법에는 여러가지가 있다. 상황에 맞는 최적의 해법을 선택하여 가독성과 성능을 만족시키자.** 93 | 94 | 95 | 96 | > **퀴즈 : Reducing으로 문자열 연결하기** 97 | 98 | joining Collector를 reducing Collector로 올바르게 바꾼 코드를 모두 선택하시오. 99 | 100 | ![image](https://user-images.githubusercontent.com/14870706/51950122-894e6a00-2472-11e9-915c-ed79c135a1fc.png) 101 | 102 | **정답** : 1, 3 103 | 104 | **2번이 오답인 이유** : reducing은 **BinaryOperator**, 즉 **BiFunction**를 인수로 받음. 제네릭에 따라서 3가지 형식이 다 통일되어야함. 하지만 2번은 menu와 String 두가지 형식이다.**그냥 reducing으로 joining을 구현할 수 있다는걸 보여주는 예제다. 실무에선 joining 쓰자.** 105 | 106 | 107 | 108 | ### 두번째, 그룹화 109 | 110 | 1. **타입별로 그룹화하기** 111 | 112 | ![image](https://user-images.githubusercontent.com/14870706/51950213-e5b18980-2472-11e9-9245-47e0c1d975d2.png) 113 | 114 | 2. **더 복잡한 기준으로 분류하기** 115 | 116 | ![image](https://user-images.githubusercontent.com/14870706/51950232-04178500-2473-11e9-875d-71e696ff8e67.png) 117 | 118 | 3. **다수준으로 그룹화하기** 119 | 120 | ![image](https://user-images.githubusercontent.com/14870706/51950251-1691be80-2473-11e9-8ea9-d17a045fe4bc.png) 121 | 122 | 4. **서브그룹으로 데이터 수집** 123 | 124 | ![image](https://user-images.githubusercontent.com/14870706/51950270-24474400-2473-11e9-9477-00134613ce59.png) 125 | 126 | 5. **Collector 결과를 다른 형식에 적용하기** 127 | 128 | ![image](https://user-images.githubusercontent.com/14870706/51950297-46d95d00-2473-11e9-9c8b-be17bd496cb9.png) 129 | 130 | ​ 의미없는 Optional이 붙어서 반환될 경우, 이렇게 변환시킨 형태로 반환할 수 있다. 131 | 132 | ![image](https://user-images.githubusercontent.com/14870706/51950520-32499480-2474-11e9-8e85-4af8886fa915.png) 133 | 134 | ### 세번째, 분할 135 | 136 | > 분할은 프레디케이트(true, false 반환함수)를 분류 함수로 사용하는 특수한 그룹화다 137 | 138 | ![image](https://user-images.githubusercontent.com/14870706/51950588-6c1a9b00-2474-11e9-8c3b-db5cd2b8da57.png) 139 | 140 | **의문점** : filter를 사용해서도 간단히 원하는 내용을 얻을 수 있는데 왜 사용해야하는가? 141 | 142 | **답변** : 참, 거짓 두가지 요소의 스트림 리스트를 모두 유지한다는 것이 장점이다. 143 | 144 | ![image](https://user-images.githubusercontent.com/14870706/51950604-7dfc3e00-2474-11e9-9957-d9515a84ffa2.png) 145 | 146 | **위처럼 참이냐 거짓이냐로 쉽게 그룹화를 할 수 있다. filter는 말그대로 필터링하여 보여주는 것이고, 분할은 데이터를 분할하는 용도로 사용하는 것이다.** 147 | 148 | --- 149 | 150 | > 지금까지 살펴본 모든 **Collector**는 Collector 인터페이스를 구현한다. Collector 인터페이스를 자세히 살펴보자. 151 | 152 | ### Collector 인터페이스란? 153 | 154 | : reducing 연산(Collector)를 어떻게 구현할지 제공하는 메서드 집합으로 구성된 인터페이스. Collector 인터페이스를 직접 구현하면 문제를 더 효율적으로 해결할 수 있다. 155 | 156 | ![image](https://user-images.githubusercontent.com/14870706/51950660-b4d25400-2474-11e9-885e-1fa3badc5dee.png) 157 | 158 | ![image](https://user-images.githubusercontent.com/14870706/51950682-cd426e80-2474-11e9-8ad3-509c14902364.png) 159 | 160 | **구현해야될 메서드** 161 | 162 | 1. supplier 163 | 2. accumulator 164 | 3. finisher 165 | 4. combiner 166 | 5. characteristics 167 | 168 | ![image](https://user-images.githubusercontent.com/14870706/51950716-ec410080-2474-11e9-9358-b4692c1a3eb2.png) 169 | 170 | 171 | 172 | #### Characterristics 메서드 173 | 174 | * **UNORDERED :** reducing 결과는 스트림 요소의 방문 순서나 누적 순서에 영향을 받지 않는다. 175 | * **CONCURRENT :** 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있으며 이 컬렉터는 스트림의 병렬 reducing을 수행할 수 있다. UNORDERED를 함께 설정하지 않았다면 데이터 소스가 정렬되어 있지 않은 상황에서만 병렬 reducing을 수행할 수 있다. 176 | * **IDENTITY_FINISH :** finisher 메서드가 반환하는 함수는 단순히 identity를 적용할 뿐 이므로 이를 생략할 수 있게 한다. reducing 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있다. 177 | 178 | ![image](https://user-images.githubusercontent.com/14870706/51950660-b4d25400-2474-11e9-885e-1fa3badc5dee.png) 179 | 180 | \- accumulator를 통해 누적하는 데에 사용한 리스트가 최종 결과이므로 **IDENTITY_FINISH** 181 | 182 | **-** 리스트의 순서는 상관 없으므로 **UNORDERED** 183 | 184 | **-** UNORDERED해서 병렬 처리가 가능하므로 **CONCURRENT** 185 | 186 | -------------------------------------------------------------------------------- /Java8Action/chapter7/ForkJoin.md: -------------------------------------------------------------------------------- 1 | 포크/조인 프레임워크에 대한 간략한 추가 자료 2 | 3 | 4 | 5 | > 참고자료 (읽어보면 좋아용) 6 | 7 | - https://homes.cs.washington.edu/~djg/teachingMaterials/grossmanSPAC_forkJoinFramework.html 8 | - https://www.baeldung.com/java-fork-join 9 | - 10 | - http://blog.naver.com/PostView.nhn?blogId=tmondev&logNo=220945933678 11 | 12 | 13 | 14 | # 자바 포크/조인 프레임워크 15 | 16 | - C/C++에는 Intel의 Thread Building Blocks(TBB), C#에는 Task Parallel Library와 같이 비슷한 기능을하는 언어/라이브러리 있음 17 | 18 | - 자바7부터 스탠다드 라이브러리에 포함되었음 19 | 20 | - 모든 클래스가 `java.util.concurrent`에 있음. 다음과 같이 임포트 21 | 22 | ```java 23 | import java.util.concurrent.ForkJoinPool; 24 | import java.util.concurrent.RecursiveTask; 25 | ``` 26 | 27 | 28 | 29 | ## ForkJoinPool 인스턴스 만들기 30 | 31 | - **자바8**: `ForkJoinPool` 의 정적 메서드 `commonPool()` 사용 32 | 33 | - ```java 34 | ForkJoinPool commonPool = ForkJoinPool.commonPool(); 35 | ``` 36 | 37 | - 모든 `ForkJoinTask` 의 디폴트 스레드 풀에 대한 레퍼런스 제공 38 | - 오라클 문서에 따르면, 미리 정의된 커먼 풀을 사용하는 게 자원 소비를 줄일 수 있음 39 | - 태스크 당 분리된 스레드 풀을 만들지 않으니까 40 | 41 | - **자바7**: `ForkJoinPool` 만든다 42 | 43 | - `new ForkJoinPool()`을 통해 풀을 만들어야 함. 그러나 딱 한번만 이걸 수행하여 스태틱 필드에 결과를 저장해야 함. 전체 프로그램이 그걸 사용할 수 있게~ 44 | - `ForkJoinPool` 생산자로 커스텀 스레드 풀 만들 수 있음 45 | 46 | 47 | 48 | ## ForkJoinTask 49 | 50 | - `ForkJoinPool` 안에서 수행되는 태스크를 위한 기본 타입 51 | - 다음 둘 중 하나 상속 52 | - 리턴값 없는 태스크에는 `RecursiveAction`, 있는 태스크에는 `RecursiveTask` 53 | - 둘 다 태스크의 로직이 정의된 추상 메서드인 `compute()` 를 가짐 54 | 55 | 56 | 57 | ## 잘 쓰려면? 58 | 59 | - 가능한 한 최소한의 스레드 풀 사용 60 | - 하나의 애플리케이션 혹은 시스템에 하나의 스레드 풀 쓰는 게 보통 최선 61 | - 디폴트 커먼 스레드 풀 사용. 특별한 튜닝 필요하지 않다면~ 62 | - 서브태스크로 분할할 때, 적절한 임계값 사용 63 | - `ForkJoingTask` 블록킹 피하기 -------------------------------------------------------------------------------- /Java8Action/chapter7/ch7.md: -------------------------------------------------------------------------------- 1 | # CHAPTER7 병렬 데이터 처리와 성능 2 | 3 | - 자바7 등장 전, 데이터 컬랙션 병렬 처리 과정(어렵당..) 4 | 1. 데이터를 서브파트로 분할 5 | 2. 분할된 서브파트를 각각의 스레드로 할당 6 | 3. 레이스 컨디션 발생하지 않도록 동기화 추가 7 | 4. 부분결과를 합침 8 | - 자바7에 추가된 **포크/조인 프레임워크** 기능! => 더 쉬운 병렬화 수행과 에러 최소화 가능 9 | 10 | 11 | 12 | 13 | 14 | ## 7.1. 병렬 스트림 15 | 16 | - **병렬 스트림 parallel stream** 17 | - 각각의 스레드에서 처리할 수 있도록 스트림 요소를 여러 청크로 분할한 스트림 18 | - => 모든 멀티코어 프로세서가 각각의 청크 처리하도록 할당 19 | - 컬렉션에 `parallelStream` 을 호출하면 병렬 스트림 생성됨. 병렬 처리 쉽게 가능~ 20 | 21 | 22 | 23 | ### 1. 순차 스트림을 병렬 스트림으로 변환하기 24 | 25 | - 순차 스트림에 `parallel` 메서드 호출하면 기존의 함수형 리듀싱 연산이 병렬로 처리됨 26 | 27 | ```java 28 | puvlic static long parallelSum(long n) { 29 | return Stream.iterate(1L, i -> i + 1) 30 | .limit(n) 31 | .parallel() // 스트림을 병렬 스트림으로 변환 32 | .reduce(0L, Long::sum); 33 | } 34 | ``` 35 | 36 | - 스트림 자체에는 아무 변화도 일어나지 않음 37 | - 호출하면 이후 연산이 병렬로 수행되어야 함을 의미하는 불린 플래그 생성됨 38 | 39 | - `sequential` 메서드: 병렬 스트림 -> 순차 스트림 40 | - 한 스트림이 `parallel` 과 `sequential`둘 다 호출한 경우 41 | - 최종적으로 호출된 메서드가 전체 파이프라인에 영향 미침 42 | - 병렬 스트림은 내부적으로 `ForkJoinPool` 사용. 기본값 그대로 사용할 것 권장 43 | 44 | 45 | 46 | ### 2. 스트림 성능 측정 47 | 48 | - 병렬화 한다고 무조건 성능이 좋아지지는 않는다 49 | - ex. `iterate` 연산을 병렬화 하는 경우 50 | - 이전 연산의 결과에 따라 다음 함수의 입력 달라지기 때문에 `iterate` 연산을 청크로 분할하기 힘듦 51 | - 따라서, 순차처리 방식과 결과적으로 크게 다른 점 없음 => 스레드 할당하는 오버헤드만 발생 52 | 53 | #### **더 특화된 메서드 사용** 54 | 55 | - `LongStream.rangeClosed`- 합계 연산 효과적으로 병렬로 실행하는 데 적합 56 | - 기본형 `long` 을 직접 사용하기 때문에 박싱/언박싱 오버헤드 없음 57 | - 쉽게 청크로 분할할 수 있는 숫자 범위 생산 58 | - 적절한 자료구조 사용해야 성능 개선 가능하다! 59 | 60 | 61 | 62 | - 병렬화에 따른 치러야 할 대가들.. 공짜 아니다! 63 | - 스트림을 재귀적으로 분할하고, 64 | - 각 서브스트림을 서로 다른 스레드의 리듀싱 연산으로 할당하고, 65 | - 결과를 하나의 값으로 합쳐야 함 66 | 67 | 68 | - 멀티코어 간의 데이터 이동은 비싸다! 69 | - 코어 간 데이터 전송 시간보다 오래 걸리는 작업만 병렬로 다른 코어에서 수행해야 함 70 | 71 | 72 | 73 | ### 3. 병렬 스트림의 올바른 사용법 74 | 75 | - 공유된 상태를 바꾸는 알고리즘에서 병렬 스트림 사용하면 데이터 레이스 문제 발생 76 | 77 | 78 | 79 | ### 4. 병렬 스트림 효과적으로 사용하기 80 | 81 | - 직접 성능 측정해 비교 82 | - 박싱 주의 83 | - 자동 박싱과 언박싱은 성능 저하시킬 수 있음 84 | - 되도록 기본형 특화 스트림(`IntStream`, `LongStream`, `DoubleStream`) 사용 85 | - 순차 스트림보다 병렬 스트림에서 성능 떨어지는 연산 주의 86 | - `limit`, `findFirst` 처럼 요소의 순서에 의존하는 연산에선 병렬 스트림 비싸다 87 | - 전체 파이프라인 연산 비용 고려 88 | - 소량의 데이터에서는 병렬 스트림 NO 89 | - 스트림을 구성하는 자료구조 적절한지 확인 90 | - 예. 분할 위해 모든 요소 탐색해야 하는 `LinkedList`보다는, 요소 탐색 없이 리스트 분할 가능한 `ArrayList`가 효율적 분할 가능 91 | - 스트림의 특성와 파이프라인의 중간 연산 확인 92 | - 예. `SIZED` 스트림은 정확히 같은 크기의 두 스트림으로 분할 => 효과적으로 병렬 처리 가능. 반면, 필터 연산은 그렇지 않음 93 | - 최종 연산의 병합과정 비용 94 | - 비싸면 병렬 스트림으로 얻은 이익이 상쇄될 수 있음 95 | 96 | 97 | 98 | **<스트림 소스와 분해성>** 99 | 100 | | 소스 | 분해성 | 101 | | --------------- | ------ | 102 | | ArrayList | 훌륭함 | 103 | | LinkedList | 나쁨 | 104 | | IntStream.range | 훌륭함 | 105 | | Stream.iterate | 나쁨 | 106 | | HashSet | 좋음 | 107 | | TreeSet | 좋음 | 108 | 109 | 110 | 111 | - 병렬 스트림 수행되는 내부 인프라구조 살펴본다 112 | - 포크/조인 프레임워크로 처리됨! 113 | 114 | 115 | 116 | ## 7.2 포크/조인 프레임워크 117 | 118 | - 병렬화 가능한 작업을 재귀적으로 작게 분할, 서브태스크 각각의 결과 합쳐 전체 결과 만들어줌 119 | - 서브태스크를 스래드 풀(`ForkJoinPool`)의 작업자 스레드에 분산 할당하는 `ExecutorService` 인터페이스 구현 120 | - `ExecutorService`: 워커 스레드를 관리하고 스레드 풀과 성능에 대한 정보 얻을 수 있는 툴을 제공 121 | 122 | 123 | ![img](./img-forkjoin-1.png) 124 | 125 | ```java 126 | import java.util.concurrent.ForkJoinPool; 127 | import java.util.concurrent.RecursiveTask; 128 | ``` 129 | 130 | 131 | 132 | ### 1. RecursiveTask 활용 133 | 134 | - `RecursiveTask` 의 서브클래스 생성 135 | 136 | - `R` 은 병렬화된 태스크가 생성하는 결과 형식 또는 결과가 없을 때는 `RecursiveAction` 형식 137 | - `RecursiveTask` 정의하려면 추상메서드 `compute` 구현해야 함 138 | 139 | ```java 140 | protected abstract R compute(); 141 | ``` 142 | 143 | - `compute` 메서드의 구성(`RecursiveTask`의 서브클래스 내에서 오버라이드) 144 | 1. (태스크 충분히 작다면)개별 서브태스크의 결과를 생산하는 부분 145 | 2. 태스크를 서브태스크로 분할하는 부분 146 | 1. 태스크를 두 서브태스크로 분할 147 | 2. 메서드 재귀 호출 => 태스크를 다시 서브태스크로 분할 148 | 3. 모든 서브태스크 연산 완료될 때까지 기다림 149 | 4. 각 서브태스크 결과 합침 150 | 151 | - 태스크 분할 임계값 설정 기준? => 뒤에 설명 152 | 153 | - 일반적으로 애플리케이션에서 둘 이상의 `ForkJoinPool` 사용하지 않음 154 | - 한 번만 인스턴스화해서 정적 필드에 싱글턴으로 저장 155 | 156 | - 분할 후 정복 divide-and conquer 알고리즘의 병렬화 버전 157 | 158 | 159 | 160 | **ForkJoinSumCalculator 실행** 161 | 162 | - 그냥 코드 설명 163 | 164 | 165 | 166 | ### 2. 포크/조인 프레임워크를 제대로 사용하는 방법 167 | 168 | - `join` 메서드 호출은 두 서브태스크가 모두 시작된 다음에 한다 169 | - 메서드 호출하면 태스크가 만들어내는 결과 준비될 때까지 호출자 블록 시키기 때문에 170 | - `RecursiveTask` 내에서는 `ForkJoinPool`의 `invoke` 메서드 호출하지 말아야 함 171 | - 순차코드에서 병렬 계산 시작할 때만 사용 172 | - 서브태스크에 `fork` 메서드 호출해 `ForkJoinPool` 의 일정 조절 가능 173 | - 한쪽은 `fork` 메서드, 다른 한쪽은 `compute` 호출하는 것이 효율적 => 한 태스크에는 같은 스레드 재사용할 수 있으므로 174 | - 디버깅 힘들다 175 | - 순차처리보다 무조건 빠르지는 않다 176 | - 각 서브태스크의 실행시간이 새로운 태스크를 포킹하는 데 드는 시간보다 길다면, 병렬 처리로 성능 개선 가능 177 | - 그러나 고려해야 할 다른 요소들도 많다.. 178 | 179 | 180 | 181 | ### 3. 작업 훔치기 182 | 183 | - 태스크 분할 기준에 대한 설명: 적절한 크기로 많은 태스크 포킹하는 게 바람직하다! 184 | - **작업 훔치기 work stealing** 185 | - 풀에 있는 작업자 스레드의 태스크를 재분배하고 균형 맞출 때까지 사용하는 알고리즘 186 | - `ForkJoinPool` 이 모든 스레드를 거의 공정하게 분할 187 | - 각각의 스레드는 이중 연결 리스트를 참조 188 | - 이중 연결 리스트에는 스레드 자신에게 할당된 태스크 포함돼 있음 189 | - 작업 끝날 때마다 큐의 헤드에서 다른 태스크 가져와 작업 처리 190 | - 다른 스레드보다 일찍 일이 끝난 스레드는 다른 스레드 큐의 꼬리에서 작업 훔쳐옴 191 | - 모든 큐가 빌 때까지 반복 192 | - => 태스크 크기 작게 나누어야 작업자 스레드 간의 작업부하를 비슷한 수준으로 유지 가능 193 | 194 | ![img](./img-forkjoin-2.png) 195 | 196 | 197 | 198 | ## 7.3. Spliterator 199 | 200 | - `Spliterator` 201 | - '분할할 수 있는 반복자 splitable iterator'. 자바8에서 제공하는 새로운 인터페이스~ 자동으로 스트림 분할하는 기법 202 | - `Iterator` 처럼 소스의 요소 탐색 기능 제공 but 병렬 작업에 특화 203 | - 탐색하려는 데이터를 포함하는 스트림을 어떻게 병렬화할 것 인지 정의 204 | 205 | ```java 206 | public interface Spliterator { 207 | boolean tryAdvance(Consumer action); 208 | Spliterator trySplit(); 209 | long estimateSize(); 210 | int characteristics(); 211 | } 212 | ``` 213 | 214 | - 메서드 215 | - `tryAdvance`: 요소 하나씩 순처적으로 소비하며 탐색할 요소 남았으면 참 리턴 216 | - `trySplit`: 일부 요소 분할해서 두번째 `Spliterator` 생성 217 | - `estimateSize`: 탐색해야 할 요소 수 정보 제공할 때 사용 218 | 219 | 220 | -------------------------------------------------------------------------------- /Java8Action/chapter7/img-forkjoin-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/11STNEWBIE/java-8-study/14d97465189a5d4206341ed45915b3e09a487952/Java8Action/chapter7/img-forkjoin-1.png -------------------------------------------------------------------------------- /Java8Action/chapter7/img-forkjoin-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/11STNEWBIE/java-8-study/14d97465189a5d4206341ed45915b3e09a487952/Java8Action/chapter7/img-forkjoin-2.png -------------------------------------------------------------------------------- /Java8Action/chapter8/ch8.md: -------------------------------------------------------------------------------- 1 | # 리팩토링, 테스트, 디버깅 2 | 3 | ## 8.1 리팩토링 4 | 5 | - **리팩토링?** 6 | 7 | ​ - 코드를 보다 쉽게 이해할 수 있도록 만드는 작업 8 | 9 | - 가독성 : 좀 더 간결하게 10 | - 확장성 : 좀 더 유연하게 11 | 12 | ### 8.1.1 코드 가독성 개선 13 | 14 | - 코드 가독성이 좋다? 15 | - 어떤 코드를 다른 사람도 쉽게 이해할 수 있음 16 | - 코드의 가독성이 높아지면 다른 사람이 유지보수하기 쉬워짐 17 | - **in JAVA 8** 18 | - 람다식으로 코드의 길이를 줄일 수 있음 19 | - 메소드 레퍼런스, 스트림 API로 **코드의 의도**를 주석을 사용하지 않고 표현 가능 20 | 21 | ### 8.1.2 익명 클래스 >> 람다 22 | 23 | - 코드 길이는 당연히 줄어드니까 좋은데... 24 | 25 | - 익명 클래스에서 사용한 this, super는 람다의 this, super와 다름 26 | 27 | - 익명 클래스의 this는 **자기 자신** 28 | - 람다의 this는 **람다를 감싸는 클래스** 29 | 30 | - 익명 클래스는 shadow variable 사용 가능 31 | 32 | ```java 33 | int a = 10; 34 | Runnable r1 = () -> { 35 | int a = 2; 36 | System.out.println(a); 37 | } // compile error 38 | 39 | Runnable r2 = new Runnable() { 40 | @Override 41 | public void run() { 42 | int a = 2; 43 | System.out.println(a); 44 | } 45 | } 46 | ``` 47 | 48 | 49 | 50 | - 콘텍스트 오버로딩에 따른 모호함을 초래할 수 있음 51 | 52 | - 명시적 형 변환을 통해서 해결 가능 53 | 54 | 55 | 56 | 위와 같은 문제를 잘 생각하고 바꾸도록 합시다. 57 | 58 | 59 | 60 | ### 8.1.3 람다 >> 메소드 레퍼런스 61 | 62 | #### ex1 63 | 64 | ```java 65 | Map> dishesByCaloricLevel = menu.stream() 66 | .collect( 67 | groupingBy(dish -> { 68 | if (dish.getCalories() <= 400) return CaloricLevel.DIET; 69 | else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL; 70 | else return CaloricLevel.FAT; 71 | }) 72 | ) 73 | ``` 74 | 75 | ```java 76 | Map> dishesByCaloricLevel = menu.stream().collect(groupingBy(Dish::getCaloricLevel)); 77 | ``` 78 | 79 | #### ex2 80 | 81 | ```java 82 | int totalCalories = menu.stream().map(Dish::getCalories) 83 | .reduce(0, (c1, c2) -> c1 + c2); 84 | ``` 85 | 86 | ```java 87 | int totalCarories = menu.stream().collect(summingInt(Dish::getCalories)); 88 | ``` 89 | 90 | ### 8.1.4 명령형 >> 스트림 91 | 92 | - **이론적으로는** 반복자를 이용한 기존의 모든 컬렉션 처리 코드를 스트림 API로 바꿔야 함 93 | 94 | ```java 95 | List dishNames = new ArrayList<>(); 96 | 97 | for (Dish dish: menu) { 98 | if(dish.getCalories() > 300) { 99 | dishNames.add(dish.getName()); 100 | } 101 | } 102 | ``` 103 | 104 | ```java 105 | menu.parallelStream() 106 | .filter(dish -> dish.getCalories() > 300) 107 | .map(Dish::getName) 108 | .collect(toList()); 109 | 110 | ``` 111 | 112 | ### 8.1.5 코드 유연성 개선 113 | 114 | ## 8.2 디자인 패턴 리팩토링 115 | 116 | ### 전략 패턴 117 | 118 | ```java 119 | public interface ValidationStrategy { 120 | boolean execute(String s); 121 | } 122 | 123 | public class IsAllLowerCase implements ValidationStrategy { 124 | public boolean execute(String s) { 125 | return s.matches("[a-z]+"); 126 | } 127 | } 128 | 129 | public class IsNumeric implements ValidationStrategy { 130 | 131 | public boolean execute(String s) { 132 | return s.matches("d+"); 133 | } 134 | } 135 | 136 | public class Validator { 137 | private final ValidationStrategy strategy; 138 | 139 | public Validator(ValidationStrategy v) { 140 | this.strategy = v; 141 | } 142 | public boolean validate (String s) { 143 | return strategy.execute(s); 144 | } 145 | 146 | } 147 | 148 | Validator numericValidator = new Validator(new IsNumeric()); 149 | boolean b = numericValidator.validate("aaaa"); 150 | 151 | ``` 152 | 153 | ```java 154 | Validator numericValidator = new Validator((String s) -> s.matches("[a-z]+")); 155 | boolean b = numericValidator.validate("aaaa"); 156 | 157 | ``` 158 | 159 | ## 8.3 테스트 160 | 161 | - 람다 표현식 자체를 테스트할 수 없음 (익명함수라 테스트 케이스 내부에서 호출 불가능) 162 | - 람다 표현식은 함수형 인터페이스의 인스턴스를 생성, 그러므로 생성된 인스턴스의 동작으로 테스트 가능 163 | - 람다를 사용하는 메소드의 동작을 테스트 164 | - 복잡한 람다식은 개별 메소드로 분할해서 테스트 (메소드 레퍼런스) 165 | 166 | ## 8.4 디버깅 167 | 168 | ### 8.4.1 스택 트레이스 확인 169 | 170 | - 람다 표현식은 이름이 없으므로 스택 트레이스가 많이... 복잡함... 171 | - 메소드 레퍼런스를 사용해서 스택 트레이스에는 표현X 172 | - 방법 없음... 173 | 174 | ### 8.4.2 정보 로깅 175 | 176 | - 스트림의 파이프라인 연산을 디버깅하고 싶을 때 177 | - peek 178 | -------------------------------------------------------------------------------- /Java8Action/chapter9/ch9.md: -------------------------------------------------------------------------------- 1 | ### 9장 디폴트 메서드 2 | 3 | #### 배경 4 | 5 | ##### 문제 상황 : 인터페이스를 바꾸고 싶을 때 6 | 7 | - 인터페이스를 구현하는 클래스는 인터페이스에서 정의하는 메서드를 모두 구현해야 함 8 | - 만약 인터페이스에 새로운 메서드를 추가한다면, 해당 인터페이스를 구현했던 모든 클래스의 구현이 수정되어야 함 9 | 10 | ##### 자바 8에서의 해결방법 11 | 12 | 1. 인터페이스 내부에 스태틱 메서드 사용 13 | 2. 인터페이스에서 디폴트 메서드 사용 14 | 15 | ##### 디폴트 메서드 16 | 17 | - 구현을 포함하는 메서드 18 | - 해당 인터페이스를 구현한 클래스는 디폴트 메서드의 구현을 그대로 상속받음 19 | 20 | #### 9.1. 변화하는 API 21 | 22 | ##### API 버전 1 23 | 24 | ```java 25 | public interface Resizable extends Drawable{ 26 | public int getWidth(); 27 | public int getHeight(); 28 | public void setWidth(int width); 29 | public void setHeight(int height); 30 | public void setAbsoluteSize(int width, int height); 31 | } 32 | ``` 33 | 34 | - 사용자 구현 : Resizable을 구현하는 Ellipse 클래스 35 | 36 | ##### API 버전 2 37 | 38 | ```java 39 | public interface Resizable extends Drawable{ 40 | public int getWidth(); 41 | public int getHeight(); 42 | public void setWidth(int width); 43 | public void setHeight(int height); 44 | public void setAbsoluteSize(int width, int height); 45 | 46 | public void setRelativeSize(int widthFactor, int heightFactor); 47 | } 48 | ``` 49 | 50 | - 사용자가 겪는 문제 51 | 52 | 1) 런타임 에러 53 | 54 | 2) 컴파일 에러 (Ellipse를 포함하는 전체 애플리케이션 재빌드 시) 55 | 56 | cf) 바이너리 호환성은 유지(새로 추가된 메서드를 호출하지만 않으면 기존 클래스 파일 구현이 잘 동작한다) 57 | 58 | 59 | 60 | #### 9.2. 디폴트 메서드란 무엇인가? 61 | 62 | - 인터페이스에 메서드의 구현을 포함할 수 있는 방법 63 | - default 키워드로 시작함 64 | - 결과적으로 다중 상속이 가능해진다. 65 | 66 | ```java 67 | default void setRelativeSize(int wFactor, int hFactor){ 68 | setAbsoluteSize(getWidth() / wFactor, getHeight() / hFactor); 69 | } 70 | ``` 71 | 72 | ##### 추상 클래스와 인터페이스의 차이 73 | 74 | 1. 클래스는 하나의 추상 클래스만 상속받을 수 있지만 인터페이스는 여러 개 구현 가능 75 | 2. 추상 클래스는 인스턴스 변수(필드)로 공통 상태를 가질 수 있찌만 인터페이스는 인스턴스 변수를 가질 수 없음 76 | 77 | #### 9.3. 디폴트 메서드 활용 패턴 78 | 79 | ##### 9.3.1. 선택형 메서드 80 | 81 | - Iterator 인터페이스 : hasNext, next, remove 메서드 정의 82 | - 하지만 사용자들이 remove 메서드를 잘 사용하지 않음 83 | - 많은 Iterator를 정의한 클래스들에서 remove에 빈 구현을 제공함 84 | - 하지만 디폴트 메서드를 이용하면 그럴 필요가 없어짐 85 | 86 | ##### 9.3.2. 동작 다중 상속 87 | 88 | - 인터페이스는 한 클래스에서 여러 개 구현할 수 있으므로 *동작 다중 상속*이 가능해졌다. 89 | - 예시) Rotatable, Moveable, Resizable 인터페이스의 조합을 통해 게임에 필요한 다양한 클래스들 구현 가능 90 | - Monster : Rotatable, Moveable, Resizable 91 | - Sun : Moveable, Rotatable 92 | - 그리고 기본 클래스를 따로 구현할 필요가 없음 93 | 94 | #### 9.4. 해석 규칙 95 | 96 | 1. 클래스가 항상 이긴다. (클래스 또는 슈퍼클래스에서 정의한 메서드 > 디폴트 메서드) 97 | 2. 1번을 제외한 상황에서는 서브인터페이스가 이긴다. (B가 A를 상속받는다면 B가 A를 이긴다) 98 | 3. 여전히 우선순위가 결정되지 않았다면 여러 인터페이스를 상속받는 클래스가 명시적으로 오버라이드 & 호출해야 한다. 99 | 100 | ##### 9.4.2. 디폴트 메서드를 제공하는 서브인터페이스가 이긴다 101 | 102 | ```java 103 | public interface A { 104 | default void hello() { 105 | System.out.println("Hello From A"); 106 | } 107 | } 108 | 109 | public interface B extends A { 110 | default void hello() { 111 | System.out.println("Hello From B"); 112 | } 113 | } 114 | 115 | public class C implements B, A { 116 | public static void main(String args[]){ 117 | new C().hello(); 118 | } 119 | } 120 | ``` 121 | 122 | ``` 123 | Hello from B 124 | ``` 125 | 126 | 127 | 128 | ```java 129 | public class D implements A { 130 | void hello(){ 131 | System.out.println("Hello from D"); 132 | } 133 | } 134 | public class C extends D implements B, A { 135 | public static void main (String args[]){ 136 | new C().hello(); 137 | } 138 | } 139 | ``` 140 | 141 | ``` 142 | Hello from D 143 | ``` 144 | 145 | 146 | 147 | ##### 9.4.3. 충돌 그리고 명시적인 문제 해결 148 | 149 | ```java 150 | public interface A{ 151 | default void hello() { 152 | System.out.println("Hello from A"); 153 | } 154 | } 155 | 156 | public interface B { 157 | default void hello() { 158 | System.out.println("Hello from B"); 159 | } 160 | } 161 | 162 | public class C implements B, A {} 163 | ``` 164 | 165 | ``` 166 | Error: class C inherits unrelated defaults for hello() from types B and A 167 | 168 | ``` 169 | 170 | ```java 171 | public class C implements B, A { 172 | void hello(){ 173 | B.super.hello(); 174 | } 175 | } 176 | 177 | ``` 178 | 179 | 180 | 181 | ##### 9.4.4. 다이아몬드 문제 182 | 183 | ```java 184 | public interface A { 185 | default void hello() { 186 | System.out.println("Hello from A"); 187 | } 188 | } 189 | 190 | public interface B extends A {} 191 | public interface C extends A {} 192 | 193 | public class D implements B, C { 194 | public static void main(String args[]){ 195 | new D().hello(); 196 | } 197 | } 198 | 199 | ``` 200 | 201 | ``` 202 | Hello from A 203 | 204 | ``` 205 | 206 | - B에 같은 시그너쳐의 디폴트 메서드 hello가 있다면 : B가 선택됨 207 | - B와 C가 모두 디폴트 메서드 hello를 정의한다면: 충돌 발생 208 | - 인터페이스 C에 *추상* 메서드 hello가 추가된다면 : 컴파일 에러 209 | 210 | 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-8-study 2 | 11번가 신입사원들의 *'자바 8 인 액션'* 스터디를 위한 Repo 입니다. 3 | 4 | > 자료를 추가하실 분들은 직접적인 Push를 삼가해주시고, Pull Request를 날려주시길 바랍니다. 5 | 6 | * [chapter 1 - 자바 8을 눈여겨봐야 하는 이유](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter1/1%EC%9E%A5.md) 7 | * [chapter 3 - 람다 표현식](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter3/3%EC%9E%A5.md) 8 | * [chapter 4 - 스트림 소개](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter4/4%EC%9E%A5.md) 9 | * [chapter 5 - 스트림 활용](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter5/5%EC%9E%A5.md) 10 | * [chapter 6 - 스트림으로 데이터 수집](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter6/chapter6.md) 11 | * [chapter 7 - 병렬 데이터 처리와 성능](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter7/ch7.md) 12 | * [chapter 8 - 리팩토링, 테스트, 디버깅](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter8/ch8.md) 13 | * [chapter 9 - 디폴트 메서드](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter9/ch9.md) 14 | * [chapter 9 - Optional](https://github.com/11STNEWBIE/java-8-study/blob/master/Java8Action/chapter10/java8-study-optional.md) 15 | --- 16 | #### Study Meeting Logs 17 | * [Meeting Log 1](https://github.com/11STNEWBIE/java-8-study/blob/master/study-meeting-log/meeting-log-1(none).md) 18 | * [Meeting Log 2](https://github.com/11STNEWBIE/java-8-study/blob/master/study-meeting-log/meeting-log-2(chapater3).md) 19 | * [Meeting Log 3](https://github.com/11STNEWBIE/java-8-study/blob/master/study-meeting-log/meeting-log-3(chapter4%2C5).md) 20 | * [Meeting Log 4](https://github.com/11STNEWBIE/java-8-study/blob/master/study-meeting-log/meeting-log-4(chapter5%2C6).md) 21 | 22 | -------------------------------------------------------------------------------- /study-meeting-log/meeting-log-1(none).md: -------------------------------------------------------------------------------- 1 | # 스터디 1차 회의 2 | 3 | > 자바8 stream, lamda 공부를 하고 스프링으로 들어가는 어떻겠냐 4 | 5 | * **진우님** : 일주일이면 금방 팔 수 있다. 그래서 java 8을 먼저 보고 스프링을 하고 스프링 부트를 하는게 나을 것 같다. 이번주는 자바 stream하고 다음주부터 스프링. 6 | 7 | * **혜진님** : 스터디 진행은 어떤식으로 할까? 강의를 들을까, 책을 뗄까? 8 | 9 | * **혁진님** : 강의는 혼자 들을 수 있는데 책은 완독하기가 힘드니 책으로 스터디를 하자. 10 | 11 | * **진우님** : 책은 뭘로 할까? 난 최범균님 스프링 책을 가지고 있다. 12 | 13 | * **혁진님** : 스프링 예제 많은 책이 있는데 그것도 괜찮을듯(스프링 5 레시피). 아니면 부트부터 거꾸로 하는 것도 괜찮을 것 같다. 14 | 15 | * **동민님** : 그럼 프로젝트 하면서 리뷰하는 식으로 하는게 낫겠다. 16 | 17 | > 책을 고르지 못함 18 | > 19 | > ... 20 | 21 | * --- 22 | 23 | **동민님, 진우님** : 자바 8 먼저 생각하고 스프링 책은 나중에 생각하자. 24 | 25 | * **동민님** : 자바 8 인 액션이라는 책 괜찮다. 26 | 27 | > *결론 : 자바 8 인 액션 책을 사서 매주 2회 스터디를 진행. 각자 맡은 부분을 다른 사람에게 강의해주는 식으로 스터디 진행* 28 | 29 | -------------------------------------------------------------------------------- /study-meeting-log/meeting-log-2(chapater3).md: -------------------------------------------------------------------------------- 1 | # 3장 람다 표현식 2 | 3 | > [Github 레포지토리](https://github.com/11STNEWBIE/JAVA8) 를 통해 정리된 문서를 확인할 수 있다. 4 | 5 | --- 6 | 7 | * **진우님** : 규원님 지원님은 자바 처음일텐데 어땠나? 8 | * **지원님** : 자바스크립트랑 비슷한 것 같아서 괜찮았다. 9 | * **진우님** : 자바스크립트도 똑같은 기능인가? 10 | * **지원님** : ES6 도입되면서 애로우 펑션이 생겼다. 11 | * **진우님** : 동작파라미터화는 메소드를 파라미터로 넘긴다는 것으로 이해하면 되나? 12 | * **동민님** : 맞다. (말씀 길게 하셨는데 못 적었다) 13 | * **혁진님** : 발표하실때 static이 heap이라고 하셨는데 static 영역이 따로 있는 것 같다. 14 | * **주원님** : perm 영역에 따로 할당되는 것으로 알고있다. 15 | * **진우님** : callable이라는게 뭔지 모르겠다. 16 | * **주원님** : Callback 같은 개념인 것 같다. 17 | * **진우님** : Callable은 Runnable의 확장인 셈이다. exception 가능. runnable은 리턴이 없고 exception 불가 18 | 19 | --- 20 | 21 | **동민님 제안** : 해보다가 변수가 생기면 점심시간 활용해서 지원금으로 샌드위치 사먹으면서 해도 좋을 것 같다. 8퍼센트라는 기업은 깃헙에 공부한 것을 올리더라. 22 | 23 | * **전체 제안** : [Github 레포지토리](https://github.com/11STNEWBIE/JAVA8) 를 파서 같이 공부한 내용을 올리자. 24 | 25 | > ***오늘 스터디 처음 하면서 솔직히 까놓고 이야기해서 바라는 점이나 고쳤으면 하는 점 이야기 해보자*** 26 | 27 | * **진우님 제안** : 예제 같은 것들 혼자 보면 답을 그냥 생각도 안해보고 넘어가는데 다 같이 예제를 보면서 생각해보는 시간을 가지면 좋겠다. 28 | * **지원님** : 뭔가 책 떼는거 말고 다른 목표가 있었으면 좋겠다. 29 | * 발표자는 발표자료와는 별개로 마크다운으로 만들어서 우리의 Organization으로 만들어주면 좋겠다. -------------------------------------------------------------------------------- /study-meeting-log/meeting-log-3(chapter4,5).md: -------------------------------------------------------------------------------- 1 | # Meeting Log 3 2 | 3 | ### 4장 스트림 소개 - 토론 4 | 5 | * **혁진님** : Stream과 ParallelStream의 차이점이 무엇인가? 6 | * **동민님** : Stream은 한 파이프라인의 동작이 끝나면 그 다음 데이터를 처리하는 싱글 스레드 형식이고, ParallelStream은 병렬프로세싱을 해서 Stream 데이터들이 CPU 코어에 맞게 동시 다발적으로 파이프라인에 들어가서 가공되고 처리되게 된다. 하지만 stream으로 제대로 된 코드를 짰으면 parallelStream에서도 잘 동작해야 맞다. 7 | * **혁진님** : 책의 예제나 실제 사용되는 것들을 보면 대부분이 stream이고 parallelStream은 보이지 않는다. 이 책의 초반부터 강조하는게 자바 8부터 병렬프로세싱 코딩이 가능해졌다는건데 이렇게 쓸거면 그 장점이 무의미해지지 않는가? 이럴거면 왜 쓰는건가? 8 | * **동민님, 진우님** : 팀원분께서도 같은 말을 하셨다. 무작정 다 Stream, Lambda를 사용할게 아니라 적재적소에 써야한다. stream으로 쓰면 나중에 데이터가 커졌을 때 parallelStream으로 쉽게 바꿔서 대응할 수 있다. 9 | * **주원님** : 여태 쓰면서 그다지 성능상의 장점은 느껴보지 못했다. 10 | * **동민님** : 하둡으로 빅데이터 다룰 때 써보면 확연한 차이를 느낄 수 있다. 11 | 12 | 13 | 14 | ### 5장 스트림 활용 - 토론 15 | 16 | * **혁진님** : 154p 맨 위 예제를 시각화한 자료가 없어서 이해하기 어려웠다. 한번 그려보면 좋을 것 같다. 17 | 18 | ```java 19 | words.stream() 20 | .map(word -> word.split("")) 21 | .map(Array::stream) 22 | .distinct() 23 | .collect(toList()); 24 | ``` 25 | 26 | * **진우님, 주원님** : 순차적으로 그려보겠다. 반환형으로 보면, `Stream`이 첫줄에 의해 반환되고, 두번째 줄의 `map`에 의해 `Stream`가 반환되고, 세번째 줄의 `map`에 의해 `Stream>` 가 반환되고, distinct에 의해 `Stream>`이 그대로 반환되고, `collect(toList())`에 의해 Stream 한겹이 벗겨지고 List가 붙는다. 결과적으로 `List>` 이 반환된다. *잘못된 결과가 나온다* 27 | 28 | * **동민님** : parallelStream이랑 stream의 차이점을 방금 검색해봤는데, stream은 한 사이클이 끝나야 다음 데이터가 들어간다. 29 | 30 | * 혁진님 : 그럼 일반적인 for문이랑 똑같지 않나? 31 | 32 | * 진우님 : for 문은 어느부분에서 걸리면 끝나지만, stream은 끝까지 다 수행한다. 33 | 34 | *[외부 참고 사이트 - parallel Stream관련](http://blog.naver.com/PostView.nhn?blogId=tmondev&logNo=220945933678)* -------------------------------------------------------------------------------- /study-meeting-log/meeting-log-4(chapter6,7).md: -------------------------------------------------------------------------------- 1 | # Meeting Log 4 2 | 3 | ### 6장 스트림으로 데이터 수집 - 토론 4 | 5 | * **공통의견 :** 스트림의 많은 기능들이 있는데, 임시 기억에 머물고 까먹는 것 같다. 이 책을 모두 다 보더라도, 다시 책을 한 번 더 봐야할 것 같다. 6 | 7 | 8 | 9 | ### 7장 병렬 데이터 처리와 성능 - 토론 10 | 11 | * **주원, 동민님 : ** 우리 실무에서 본 코드에서는 병렬 처리가 아닌 sequential한 스트림에 대한 처리만 본 것 같다. 그래서 잘 와닿지 않는 것 같다. 12 | * **진우님 : **ForkJoinPool을 비롯하여 쓰레드풀에 대해 공부해봐야겠다. 13 | 14 | 15 | 16 | ### 스터디 진행 논의 17 | 18 | * 우리가 1주에 4챕터씩 진행했는데, 너무 빠르게 나갔던 것 같다. 발표자를 제외하면 제대로 공부하고 따라가는 것이 힘든 것 같다. 진행 방식을 수정해보자. 19 | * **주원님 :** 팀에서 스터디하는 것을 지켜보면, 주 2회를 진행하고 하루는 모여서 공부하고 하루는 발표한다. 그런데 모인 날에는, 예제 코드를 모두 타이핑하지 않으면 스터디룸에서 나가지 않는 것을 규칙으로 하더라. 20 | 21 | [결정사항] 22 | 23 | * 주 2회를 유지, 2 챕터씩 진행하자. 그리고 하루는 같이 모여 공부하는 시간을 갖고, 다른 하루는 8명을 두 그룹으로 나누고 각 그룹에서 발표자 한 명씩을 뽑자. 24 | * 월, 목 저녁에 진행하던 것을 -> 월요일 저녁에 발표 진행, 수요일 아침에 공부를 하면 좋을 것 같다. 25 | * ''자바 8인 액션'' 책을 마무리 하고 스프링으로 넘어갔다가, 다시 이 책으로 돌아오는 것도 좋겠다. --------------------------------------------------------------------------------