diff --git a/dongwon/READ.md "b/dongwon/1\354\236\245 \354\236\220\353\260\224 \353\254\264\354\212\250 \354\235\274\354\235\264 \354\235\274\354\226\264\353\202\230\352\263\240 \354\236\210\353\212\224\352\260\200 .md" similarity index 99% rename from dongwon/READ.md rename to "dongwon/1\354\236\245 \354\236\220\353\260\224 \353\254\264\354\212\250 \354\235\274\354\235\264 \354\235\274\354\226\264\353\202\230\352\263\240 \354\236\210\353\212\224\352\260\200 .md" index 6d72070..e2ae663 100644 --- a/dongwon/READ.md +++ "b/dongwon/1\354\236\245 \354\236\220\353\260\224 \353\254\264\354\212\250 \354\235\274\354\235\264 \354\235\274\354\226\264\353\202\230\352\263\240 \354\236\210\353\212\224\352\260\200 .md" @@ -1,4 +1,4 @@ -## 자바 8, 9, 10, 11 +# 자바 8, 9, 10, 11
diff --git "a/dongwon/2\354\236\245 \353\217\231\354\236\221 \355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\275\224\353\223\234 \354\240\204\353\213\254\355\225\230\352\270\260.md" "b/dongwon/2\354\236\245 \353\217\231\354\236\221 \355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\275\224\353\223\234 \354\240\204\353\213\254\355\225\230\352\270\260.md" new file mode 100644 index 0000000..90d4cbe --- /dev/null +++ "b/dongwon/2\354\236\245 \353\217\231\354\236\221 \355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\275\224\353\223\234 \354\240\204\353\213\254\355\225\230\352\270\260.md" @@ -0,0 +1,382 @@ +# 동작 파라미터화 + +
+ +동작 파라미터화 - 아직 어떻게 실행할지 정해지지 않은 코드 블록(코드 블록은 나중에 프로그램에서 호출되어 실행이 미뤄져, 나중에 실행될 메서드의 인스로 코드 블록을 전달할수 있음) + +동작 파라미터화를 통해서 원하는 동작을 메서드의 인수로 전달할수 있지만 쓸데없는 코드가 늘어난다 + +
+ +첫번째 시도 : 녹색 사과 필터링 + +```java +사과 색을 정의하는 Color num + +euum Color {Red, GREEN} + +public static List filterGreenAppples(List inventory) { + List result = new ArrayList<>(); <- 사과 누적 리스트 + for(Apple apple : inventory) { + if (GREEN.equals(apple.getColor()) { <- 녹색 사과만 선택 + result.add(apple); + } + } + return result; +} +``` + +`GREEN.equals(apple.getColor**()` 녹색사과 필요한 조건에서 만약 빨간 사과도 필터링한다면 새로운 filterRedApple 메서드를 만들어서 해야한다 + +
+
+두번째 시도 : 색 파라미터화 + +filterGreenApples 코드 반복하지 않고 filterRedApple 구현하기 위해서 색을 파라미터에 추가 + +```java +public static List filterApplesByColor(List inventory, **Color color**) +{ + List result = new ArrayList<>(); + for (Apple apple : inventory) { + if (**apple.getColor().equals(color)** ) { + result.add(apple); + } + } + return result; +} + +다음과 같이 메서드 호출 가능해짐 + +List greenApples = filterApplesByColor(inventory, GREEN); +List redApples = filterApplesByColor(inventory, RED); +``` + +여기에 더해 더 많은 조건이 추가 된다면 메서드에 파라미터를 추가하면 되겠지만 각 필터링 코드가 중복이 되어진다. 그리하여 코드를 뜯어고치게 된다면 비용소모가 클 것이다 + +필터링 코드를 하나로 묶어 filter 메서드로 합치게 된다면 어떤 필터 기준으로 구분하는지 플래그 추가 할 수 있을거다(but 이 방법은 추천하지 않는다) + +
+
+ +세번째 시도 : 가능한 모든 속성 필터링 + +```java +public static List filterApples(List inventory, Color color, int weight, booean flag) { + List result = new ArrayList<>(); + for (Apple apple : inventory) { + if (**(flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > we ight)) { // <- 색과 무게를 선택하는 방법** + result.add(apple); + } + } + return result; +} +``` +메서드를 호출할때는 + +List greenApples = filterApples(inventory, GREEN, 0, true); +List heavyApples = filterApples(inventory, null, 150, false); + +각각의 값들이 어떤것을 가리키는지 알수없으며, 바뀌어질 요구사항에 유연하게 대처할수가 없다 +문제가 잘 정의되어 있다면 좋겠지만 요구사항은 바뀔수다 있다는것 또한 어떠한 기준으로 필터링 할 것인지 효과적으로 전달하면 더 좋은 코드가 될것이다 + +
+
+ +## 동작 파라미터화를 통한 유연성 얻기 + +ex) 선택조건을 사과의 어떠한 속성에 따라 boolean값을 반환할수 있을것 + +이러한 참, 거짓을 반환하는 함수를 프레디케이트(Predicate) 라 함 + +선택조건을 결정하는 인터페이스 + +```java +public interface ApplePredicate { + boolean test (Apple apple); +} + +위와 같이 다양한 선택 조건 ApplePredicate 정의할 수 있을 거다 +------------------------------------------------------------------------------------- +public class AppleHeav_WeightPredicate implement ApplePredicate { <- 무거운 사과 조건 + public boolean test(Apple apple) { + return apple.getWeight() > 150; + } +} + +public class AppleGreen_CcolorPredicate implements ApplePredicate { <- 녹색사과만 + public boolean test(Apple apple) { + return GREEN.equals(apple.getColor()); + } +} +``` + +
+ +![파라미터유연화](./2%EC%9E%A5%20image/%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0%ED%99%94%20%EC%9C%A0%EC%97%B0%EC%84%B1.png) + +
+ +조건에 따라 filter 메서드가 다르게 동작하게 되는 것이다. 이를 **디자인 패턴** 이라 부른다 + +(알고리즘 패밀리를 정의 해둔뒤 런타임에 알고리즘을 선택하는 기법 `Apple_Predicate` 알고리즘 패밀리, `Apple_HeavyWeightPredicate` , `Apple_GreenColorPredicate` 전략) + +
+ +`Apple_Predicate`가 다양한 동작을 수행하기 위해 filter_Apples 에서 `Apple_Predicate` 객체를 받아 애플의 조건을 검사하도록 메서드 고쳐야 함 + +이것을 **동작파라미터화** 라고 부르며 풀어서 메서드가 다양한 동작을 **받아서** 내부적으로 다양한 동작을 **수행**하는 것이다. 위의 예에서는 + +컬랙션을 반복하는 로직 / 컬렉션의 각 요소에 적용할 동작(Predicate) 을 분리할수 있다 + +
+
+ +네번째 시도 : 추상적 조건 필터링 + +```java +Apple_Predicate 이용한 필터 메서드 + +public static List filterApples(List inventory, ApplePredicate p) { + List result = new ArrayList<>(); + for(Apple apple: inventory) { + if(**p.test(apple)**) { <- Predicate 객체로 사과 검사 조건을 캡슐화 + result.add(apple); + } + } + return result; +} + +유연하고 가독성이 좋아짐 +``` +
+ +(조건 150g 넘는 빨간 사과 검색) + +```java +ApplePredicate 구현하는 클래스 만들기(Apple의 모든 속성 변화에 대응이 가능해짐) + +public class AppleRed_HeavyPredicate implements ApplePredicate { + public boolean test(Apple apple) { + return RED.equals(apple.getColor()) && apple.getWeight() > 150; + } +} + +List red_HeavyApples = filterApples(inventory, new AppleRedAndHeavyPredicate()); + +ApplePredicate 객체에 의해 filterApples 메서드의 동작이 결정됨 +``` +
+ +![추상적 조건 필터링](./2%EC%9E%A5%20image/%EC%B6%94%EC%83%81%EC%A0%81%20%EC%A1%B0%EA%B1%B4%EC%9C%BC%EB%A1%9C%20%ED%95%84%ED%84%B0%EB%A7%81.png) + +핵심은 test 메서드인데 ApplePredicate 객체로 감싸서 전달해야 해서 코드를 전달하는 것과 같다 + +**동작 파라미터화**는 이와같이 각 항목에 적용할 동작을 분리할 수 있다는 것이 강점이다, 그래서 메서드가 다른 동작을 다시 활용할수 있다(→ 유연한 API를 만들 때 동작 파라미터화가 중요한 역할을 함) + +
+
+ +**복잡한 과정 간소화** + +
+ +> 아 이제는 여러 클래스 구현해서 인스턴스화 과정도 거추장스럽다. 어떻게 개선 가능한가? +> + +
+ +filterApples 메서드로 새로운 동작 전달하려면 ApplePredicate 인터페이스를 구현하는 여러 클래스 정의해서 인스턴스화 해야한다 + +```java +public class { 무거운 사과 선택 클래스} +public class { 녹색 사과 선택 클래스} + +List heavyApples = filterApples(무거운 사과 선택 결과)->리스트는 155 사과한개 포함 +List heavyApples = filterApples(녹색 사과 선택 결과)->리스트는 녹색 사과두개 포함 +``` + +개선하기 위해서 클래스의 선언과 동시에 인스턴스화를 할수 있는 **익명 클래스**가 있다 + +즉, 즉석에서 필요한 것을 바로 만들어 사용이 가능하다 + +
+
+ +다섯 번째 시도 : 익명 클래스 사용 + +```java +List redApples = filterApples(inventory, new ApplePredicate() { <- filterApples 메서드의 동작 직접 파라미터화 + + public boolean test(Apple apple) { + return RED.equals(apple.getColor()); + } +}); + +GUI 애플리케이션 이벤트 핸들러 객체 구현할 때 익명 클래스 종종 사용함 +하지만 익명 클래스는 여전히 많은 공간을 차지함(노란 글씨) + +**List redApples = filterApples(inventory, new ApplePredicaate() { + public boolean test(Apple a)** { + return RED.equals(a.getColor()); + } +}); + +**button.setOnAction(new EventHandler() { + public void handle(ActionEvent event)** { + System.out.println("Whoooo a click!!")); + } +} +``` +
+
+ +여섯 번째 시도 : 람다 표현식 사용 + +```java +List result = filterApples(inventory, (Apple apple) -> RED.equals(apple.getColor())); + +매우 간결하면서 유연해짐 +``` +
+
+ +일곱 번째 시도 : 리스트 형식으로 추상화 + +```java +public interface Preicate { + boolean test(T t); +} + +public static List filter(List list, Predicate p) { <- 형식 파타미터 T + List result = new ArrayList<>(); + for(T e: list) { + if(p.test(e)) { + result.add(e); + } + } + return result; +} + +람다식으로 +List redApples = filter(inventory, (Apple apple) -> RED.equals(apple.getColor())); + +List evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0); +``` + +
+ + + +
+
+ +**Comparator로 정렬** + +sort 동작을 파라미터화 + +```java +public interface Comparator { + int compare(T o1, T o2); +} + +Comparator를 구현해서 sort 메서드의 동작 다양화 가능 + +****익명클래스 이용 +inventory.sort(new Comparator() { + public int compare(Apple a1, Apple a2) { + return a1.getWeight().compareTo(a2.getWeight()); + } +}); + +람다 이용 +inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); +``` + +
+
+ +Runnable로 코드 블록 실행 + +자바 스레드를 이용해 병렬로 코드 블록 실행가능, 나중에 실행할 코드 구현 방법 필요(자바 8까지 Thread 생성자에 객체만 전달가능해서 결과를 반환하지 않는 void 메소드 포함 익명 클래스가 Runnable 인터페이스 구현하는 것이 일반적) + +```java +// java.lang.Runnable +public interface Runnable { + void run(); +} + +Runnable이용 +Thread t = new Thread(new Runnable() { + public void run() { + System.out.println("Hello world"); + } +}); + +람다로 표현 +Thread t = new Thread(() -> System.out.println("Hello world")); +``` + +
+
+ +Callable 결과로 반환 + +자바 5부터 있는 실행서비스(ExecutorService)는 태스크 제출과 실행 과정의 연관성 끊어줌ExecutorService를 이용하면 태스크를 스레드 풀로 보내고 결과를 Future로 저장 가능. 이 점이 Runnable과 다르다. + +```java +Callable 인터페이스를 이용한 결과 반환 태스크 + +// java.util.concurrent.Callable +public interface Callable { + V call(); +} + +(실행서비스) +ExecutorService service = Executors.newCached_ThreadPool(); +Future threadName = service.submit(new Callable() { + @Override + public String call() throws Exception { + return Thread.currentThread().getName(); + } +}); // 실행결과 태스크 실행하는 스레드의 이름을 반환 + +람다 이용 +Future threadName = service.submit( () -> Thread.currentThread().getName()); +``` + +
+
+ +GUI이벤틑 처리 + +사용자의 전송 버튼 클릭 → 동작 로그 파일 저장, 팝업표시 + +**변화에 대응할수 있는 유연한 코드 필요(모든 동작에 반응)** + +ex ) 자바 FX → setOnAction 메서드에 EventHandler 전달해 이벤트에 반응 + +```java +Button button = new Button("Send"); +button.setOnAction(new EventHandler() { + public void handle(ActionEvent event) { + label.setText("Sent!!"); + } +}); + +EventHandler는 setOnAction 메서드의 동작을 파라미터화 함 +람다 표현 +button.setOnAction((ActionEvent event) -> label.setText("Sent!!")); +``` +
+
+ +정리 + +- **동작 파라미터화**는 메서드 내부적으로 다양한 동작을 위한 코드를 **메서드 인수로 전달** +- 변화하는 요구사항에 유연한 대처를해 비용줄임 +- 코드 전달 기법을 통해 익명 클래스 사용가능 +- (정렬, 스레드, GUI 처리) 자바 API 는 다양한 동작으로 파라미터화 가능 \ No newline at end of file diff --git "a/dongwon/3\354\236\245 \353\236\214\353\213\244 \355\221\234\355\230\204\354\213\235.md" "b/dongwon/3\354\236\245 \353\236\214\353\213\244 \355\221\234\355\230\204\354\213\235.md" new file mode 100644 index 0000000..4a71362 --- /dev/null +++ "b/dongwon/3\354\236\245 \353\236\214\353\213\244 \355\221\234\355\230\204\354\213\235.md" @@ -0,0 +1,837 @@ +# 람다표현식 + +
+ +- 람다란 무엇인가? + +어떤 함수의 매개변수로 다른 함수를 넣고싶을 때 사용. + +선언부가 없어 일회성의 특성이 있으며 지연 실행 또는 지연 연산으로 메모리상의 불필요한 연산을 줄인다 + +
+ +**람다 표현식** + +익명 함수를 단수화 한 것 + +파라미터 리스트, 바디, 반환 형식, 발생할 수 있는 예외 리스트 + +익명 - 이름이 없어 **익명**이라 표현 + +함수 - 특정 클래스에 종속되지 않아서 함수라 함. but 메서드와 같이 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트 포함 + +전달 - 메서드 인수로 전달이나 변수로 저장 + +간결성 - 많은 코드 필요없음 + +
+ + +```java +람다 맛보기 + +Compator byWeight = new Comparator() { + public int compare(Apple a1, Apple a2) { + reuturn a1.getWeight().compareTo(a2.getWeight()); + } +}; + +-> + +Compare a1.getWeight().compareTo(a2.getWeight()); +``` + +
+ +- 어디에, 어떻게 람다를 사용하는가? + +
+ +함수 디스크립터 + +추상메서드 시그니처는 람다 표현식의 시그니처를 가리킴 + +람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터라 부름 + +람다는 표현식을 변수에 할당하거나 함수형 인터페이스를 인수로 받는 메서드로 전달할 수 있으며 + +함수형 인터페이스의 추상 메서드와 같은 시그니처를 갖는다는 사실을 기억하는 것으로 충분 + +
+ + + +
+
+ +- 실행 어라운드 패턴 + +실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태를 **실행 어라운드 패턴** 이라고 함 + +```java +public String processFile() throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { + return br.readLine(); <- 실제 필요한 작업을 행하는 행 + } +} +``` + +
+ +![람다활용 실행어라운드 패턴](https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsSLey%2FbtqXb1vSkma%2FMW2qzBtm4T64kGmLD1ZtK1%2Fimg.png) + +출처 : [https://dev-kani.tistory.com/38](https://dev-kani.tistory.com/38) + +
+ +1단계 : 동작 파라미터 기억하기 + +기존 설정, 정리 과정은 재사용, processFile 메서드만 다른 동작 수행하도록 명령 + +processFile 동작 파라미터화 하기 + +```java +String result = processFile((BufferedReader by) -> br.readLine() + br.readLine()); +``` + +
+ +2단계 : 함수형 인터페이스 이용해 동작 전달 + +함수형 인터페이스 자리에 람다 사용가능. + +BufferedReader → String, IOException 던질 수 있는 시그니처와 일치하는 함수형 인터페이스 만들어야 함. 이 인터페이스를 BufferedReaderProcessor 라 정의 + +```java +@FunctionalInterface +public interface BufferedReaderProcessor { + String process(BufferedReader b) throws IOException; +} + +정의한 인터페이스를 processFile 메서드의 인수로 전달 + +public String processFile(BufferedReaderProcessor **p**) throws IOException { + ...... +} +``` + +
+ +3단계 : 동작 실행 + +람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할수 있었으며, 전달된 코드는 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리되었음 + +```java +public String processFile(BufferedReaderProcessor p) throws IOException { + try (BufferedReader br = new BufferedReader(new FileReader("data.txt")) { + return **p.process(br)**; <- BufferedReader 객체 처리 + // 여기가 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식 + } +} +``` + +
+ +4단게 : 람다 전달 + +다양한 동작 processFile 메서드로 전달가능 + +```java +String oneLine = processFile((BufferedReader br) -> br.readLine()); // 한 행 처리 +String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine()); +// 두 행 처리 +``` + +
+ +- 함수형 인터페이스 + +함수형 인터페이스는 오직 하나의 추상 메서드를 지정한다. + +함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사함 + +함수형 인터페이스의 추상 메서드 시그니처를 **함수 디스크립터**라 함 + +다양한 람다 표현식 사용위해서 공통의 함수 디스크립터 기술하는 함수형 인터페이스 집합 필요 + +(이미 자바 API는 Comparable, Runnable, Callable 등의 다양한 함수형 인터페이스 포함하고 있음) + +
+
+ +**Predicate** + +`java.util.function.Predicate` 인터페이스는 test라는 추상 메서드 정의 + +test는 제네릭 형식 T의 객체 인수로 받아 boolean반환 + +만들었던 인터페이스와 같은 형태이지만 따로 정의할 필요없이 사용할 수 있다는 것이 특징이다 + +(and 와 or 과 같은 메서드도 있음) + +T 형식 객체 사용하는 boolean 표현식 필요한 상황에서 Predicate 인터페이스 사용가능 + +ex) String 객체 인수로 받는 람다 정의 + +```java +@FunctionalInterface +public interface Predicate { + boolean test(T t); +} +// 함수형 인터페이스 만들기 + +public List filter(List list, Predicate p) { + List result = new ArrayList<>(); + for(T t: list) { + if(**p.test(t)** { + **result.add(t);** + } + } + return results; +} +// 동작 전달 + +Predicate nonEmptyStringPredicate = (String s) -> !s.isEmpty(); +List nonEmpty = filter(listOfStrings, nonEmptyStringPredicate); +// 람다 전달 +``` +
+ +**Consumer** + +`java.util.function.Consumer` 제네릭 형식 T 객체를 받아서 void를 반환해 accept 라는 추상 메서드 정의 + +T 형식 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스 사용가능 + +```java +@FunctionalInterface +public interface **Consumer** { + void accept(T t); +} + +public void forEach(List list, Consumer c) { + for(T t: list) { + **c.accept(t);** + } +} + +forEach( + Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i) +// Consumer의 accept 메서드를 구현하는 람다 +``` + +
+
+ +**Function** + +`java.util.function.Function ` 제네릭 형식 T를 인수로 받아 제네릭 형식 R 객체를 반환하는 추상 메서드 apply 정의 + +입력을 출력으로 매핑하는 람다를 정의할 때 Function 인터페이스 활용 가능 + +ex ) 사과의 무게 정보 추출, 문자열 길이 매핑 + +```java +@FunctionalInterface +public interface **Function** { + **R apply(T t);** +} + +public List map(List list, **Function f**) { + List result = new ArrayList<>(); + for(**T t: list**) { + **result.add**(**f.apply(t)**); + } + return result; +} + +// [7, 2, 6] +List l = map(Arrays.asList("lambdas", "in", "action"), (String s) -> s.length()); +// Function의 apply 메서드를 구현하는 람다 +``` + +
+ +기본형, 특화형 + +현제까지 본 3가지가 기본형이며 특화된 형식의 함수형 인터페이스도 있다 + +자바의 형식은 그게 참조형(Byte, Integer, Object, List)과 기본형으로 나뉘는데(int, double, byte, char) 제네릭 파라미터(기본형 Consumber 에서 T )는 참조형만 사용가능 +⇒ 제네릭 내부 구현 때문에 어쩔수가 없다 + +자바에서는 형식변환 기능을 제공한다 + +[ 기본형 → 참조형 ] : 박싱 + +[ 참조형 → 기본형 ] : 언박싱 + +[ 자동으로 형변환 이루어짐 ] : 오토박싱 + +하지망 이러한 형변환은 비용이 소모된다. 박싱한 값은 기본형을 감싸는 래퍼로 힙에 저장 + +박싱한 값은 메모리를 더 소비한다, 또한 기본형을 가져올때 메모리를 탐색하는 과정이 필요하다 + +자바 8에서 기본형을 입출력으로 사용할 때 오토박싱 동작을 피할수 있는 특별한 버전의 함수형 인터페이스를 제공한다. + +
+ +```java +IntPredicate -> 박싱하지 않는다 +Predicate -> Integer 객체로 박싱한다 + +public interface IntPredicate { + boolean test(int t); +} + +IntPredicate evenNumbers = (int i) -> i % 2 == 0; +evenNumbers.test(1000); <- 박싱 없음 + +Predicate oddNumbers = (Integer i) -> i % 2 != 0; +oddNumbers.test(1000); <- 박싱 +``` + +
+ +특정 형식을 입력으로 받는 함수형 인터페이스의 이름 앞에는 Double, Int, Long 과 같은 형식명이 붙음, `ToIntFunction`, `IntToDoubleFunction` 등의 다양한 출력 형식 파라미터 제공 + +
+ +자바 8에 추가된 함수형 인터페이스 + +| 함수형 인터페이스 | 함수 디스크립터 | +| --- | --- | +| Predicate | T → boolean | +| Consumber | T → void | +| Function | T → R | +| Supplier | () → T | +| UnaryOperator | T → T | +| BinaryOperator | (T, T) → T | +| BiPredicate | (T, U) → boolean | +| BiConsumer | (T, U) → void | +| BiFunction | (T, U) → R | + +
+ + + +
+ +- 형식 추론, 검사, 제약 + +> 컴파일러가 어떻게 람다의 형식을 확인할수 있나? + + +피해야 할 사항은 뭐지? +( 람다 표현식에서 바디 안에 있는 지역 변수를 참조하지 않아야 한다, void 호환 람다는 멀리해야 한다) +> + +람다가 사용되는 콘텍스트를 이용해 람다의 형식 추론가능 + +어떠한 콘텍스트(ex. 람다가 전달될 메서드 파라미터나 람다가 할당되는 변수)에서 기대되는 람다 표현식의 형식을 **대상 형식**이라 부름 + +```java +람다 표현식 사용할 때 실제 어떠한 일이 일어나나 보여줌 + +List heavierThan150g = filter(inventory, (Apple apple) -> apple.getWeight()>150); + +1. filter 메서드 선언 확인 + **filter**(inventory, (Apple a) -> a.getWeight() > 150); + +2. filter 메서드 두 번째 파라미터로 Predicate 형식(대상 형식) 기대 + filter(Listinventory, **Predicate** p) + +3. Predicate는 test라는 **한 개의 추상 메서드 정의하는 함수형 인터페이스** + +4. test 메서드는 Apple을 받아 boolean 을 반환하는 함수 디스크립터 묘사 + boolean test(Apple apple) + +5. filter 메서드로 전달된 인수는 이와 같은 요구사항 만족 + Apple -> boolean + +람다표현식이 예외를 던질수 있다면 추상 메서드로 같은 예외를 던질수 있도록 throws로 선언해야함 +``` + +
+ +대상 형식의 특징으로 같은 람다 표현식이어도 호환되는 추상 메서드 가진 다른 함수형 인터페이스로 사용 가능 + +```java +Callable 과 PrivilegedAction 은 인수를 받지않으며 제네릭 형식 T를 반환하는 함수를 정의함 + +Callable c = () -> 42; +PrivilegedAction p = () -> 42; + +하나의 람다 표현식을 다양한 함수형 인터페이스에 사용할수 있다 + +Comparator c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); + +ToIntBiFunction c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); + +BiFunction c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); +``` + + + + + +
+ +형식 추론 + +자바 컴파일러는 람다표현식이 사용된 콘텍스트(대상 형식)을 이용해 람다 표현식과 관련된 함수형 인터페이스를 추론함 + +→ 대상 형식을 이용, 람다 표현식과 관련된 함수형 인터페이스를 추론 / 함수 디스크립터를 알 수 있으니 컴파일러는 람다의 시그니처도 추론할수가 있는 것 + +결과적으로 컴파일러는 람다 표현식의 라파미터 형식에 접근할 수 있어서 람다 문법에서 생략할수가 있다 + +```java +List greenApples = **filter**(inventory, apple -> GREEN.equals(apple.getColor())); +// 파타미터 a 에는 형식을 명식적으로 지정하지 않음 + +여러 파라미터를 포함하는 람다 표현식은 가독성이 더 좋아짐 + +Comparator c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); +// 형식 추론하지 않음 + +Compatator c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); +// 형식을 추론 + +상황에 따라 명시적으로 형식 포합하는 것이 좋을수도 있다 +정해진 규칙은 없으니 개발자가 스스로 어떻게 가독성을 향상시킬수 있는지 판단해야 한다 +``` + +
+ +지역 변수 사용 + +람다 표현식은 자신의 바디 안에서만 사용하는 함수 뿐만 아니라 **자유 변수**(파라미터가 넘겨진 변수가 아닌 외부에서 정의된 변수)도 활용할 수 있음. 이와 같은 동작을 **람다 캡처링**이라 함 + +```java +ex) int portNumber = 1337; + Runnable r = () -> System.out.println(portNumber); + **portNumber = 31337;** // 값을 변경해서는 안된다 + +but 지역변수는 final 과 같이 사용되어야 한다 + +인스턴스 변수와 지역변수는 저장되는 위치가 다르다(태생이 다르다!!) +인스턴스 변수는 **힙**에 지역 변수는 **스택**에 위치한다. + +---------------------------------------------------------------------------------------- + +람다에서 지역변수 바로 접근 -> 람다가 스레드에서 실행 -> 변수를 할당한 스레드가 사라져 변수 할당이 해제 -> 람다 실행 스레드에서는 해당 변수에 접근 할수도 있음 => 이때 자바에서는 원래 변수에 접근 허용이 아닌 자유 지역 변수 복사본을 제공함 => 복사본의 값이 바껴선 안되기에 지역 변수에 한번만 값 할당 해야함 +``` + +
+ + + +
+ +- 메서드 참조 + +기존의 메서드 정의를 재활용해 람다와 같이 전달할 수 있음 + +```jsx +imventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); + + ↓ +inventory.sort(comparing(Apple::getWeihgt)); // 첫 번째 메서드 참조 +``` + +
+ +람다가 메서드를 직접 호출하라 명령하면. 어떻게 호출하는지 설명 참조보단 메서드명 직접 참조가 편리하다. + +실제 메서드 참조 이용시, 기존 메서드 구현으로 람다 표현식 만들 수 있다. → 이때 명시적 메서드명 참조함으로써 **가독성 높일수가 있다**. (메서드명 앞에 구분자(::) 붙여서 메서드 참조 활용가능 + +ex) Apple::getWeight 는 Apple 클래스에 정의된 getWeight 의 메서드 참조다 + +(Apple a) → a.getWeight() 축약한 것 + +| 람다 | 메서드 참조 단축 표현 | +| --- | --- | +| (Apple apple) → apple.getWeight() | Apple::getWeight | +| () → | Thread.currentThread()::dumpStack | +| Thread.currentThread().dumpStack() | | +| (str, i) → str.substring(i) | String::substring | +| (String s) → System.out.println(s) (String s) | System.out::println | +| → this.isValidName(s) | this::isValidName | + +
+ +메서드 참조 만들기 + +1. 정적 메서드 참조 + + Integer 의 parseInt 메서드는 Integer::parseInt 로 표현 가능 + +2. 다양한 형식의 인스턴스 메서드 참조 + + String의 length 메서드는 String::length 로 표현 가능 + +3. 기존 객체의 인스턴스 메서드 참조 + +ex) Transaction 객체를 할당받은 expensiveTransaction 지역 변수, Transaction 객체의 getValue 메서드 + +→ 이를 expensiveTransaction::getValue + +```jsx + +class Transaction { + int value; + + getValue(int value) { + this.value = value; + } +} + +Transaction expensiveTransaction = new Transaction(); +---------------------------------------------------------------------------------------- +expensiveTransaction::getValue 가능! +``` +
+ +```jsx +람다 표현식 바꿔보기 + +List str = Arrays.asList("a", "b", "A", "B"); +str.sort((s1, s2) -> s1.compareToIgnoreCase(s2)); + +1. +[람다] (args) -> ClassName.staticMethod(args) + ↓ ↓ 정적 메서드 참조 +[메서드 참조] ClassName::staticMethod + +2. +[람다] (arg0, rest) -> arg0.instanceMethod(rest) + ↓ arg0은 ClassName 형식 +[메서드 참조] ClassName::instanceMethod + +3. +[람다] (args) -> expr.instanceMethod(args) + ↓ ↓ 기존 객체 인스턴스 참조 +[메서드 참조] expr::instanceMethod + +======================================================================================== + +List str = Arrays.asList("a", "b", "A", "B"); +str.sort(String::compareToIgnoreCase); + +컴파일러는, 람다 표현식의 형식을 검사하던 방식과 비슷한 과정으로 메서드 참조가 주어진 함수형 인터페이스와 호환하는지 확인해야함 + +즉 메서드 참조는 콘테스트의 형식과 일치해야 한다 +``` + +
+ +예제문제 + +```jsx +1. ToIntFunction stringToInt = (String s) -> Integer.parseInt(s); +2. BiPredicate, String> contains = (list, element) -> list.contains(element); +3. Predicate startsWithNumber = (String string) -> this.startsWithNumber(string); +---------------------------------------------------------------------------------------- + +1. ToIntFunction stringToInt = Integer::parseInt; +2. BiPredicate, String>contains = List::contains; +3. Predicate startsWithNumber = this::startsWithNumber +``` + +
+
+생성자 참조 + +기존 생성자 참조해 만들수 있다 + +```jsx +Supplier c1 = () -> new Apple(); +Apple a1 = c1.get(); + +Apple 시그니처 생성자는 Function 인터페이스의 시그니처와 같아서 +Fuction c2 = Apple::new; +Apple a2 = c2.apply(110); 과 같다 + += + +Function c2 = (weight) -> new Apple(weight); +Apple a2 = c2.apply(110); +``` + +
+ +람다 만들기 + +람다, 메서드 참조 활용하기 + +1. 코드 전달 + +sort 메서드에 정렬 전략 전달하는 방법 + +```jsx +void sort(Comparator c) + +Comparator 객체를 인수로 받아 두 사과를 비교함. 객체 안에 동작을 포함시키는 방식으로 다양한 전략 전달가능. 'sort의 동작은 파라미터화 되었다' 고 할수 있다 + +(sort에 전달된 정렬 전략에 따라 sort의 동작이 달라질 것) + +[코드 전달] +public class AppleComparator implements Comparator { + public int compare(Apple a1, Apple a2) { + return a1.getWeight().compareTo(a2.getWeight()); + } +} +inventory.sort(new AppleComparator()); +``` + +
+ +2. 익명 클래스 사용 + +```jsx +Comparator를 구현하는 것보다 익명 클래스 이용이 좋음 + +inventory.sort(new Comparator() { + public int compare(Apple a1, Apple a2) { + return a1.getWeight().compareTo(a2.getWeight()); + } +}); +``` + +
+ +3. 람다 표현식 사용 + +함수형 인터페이스를 기대하는 어떤 곳에서든 람다 표현식을 사용할수 있다 + +> “함수형 인터페이스는 오직 하나의 추상 메서드를 정의하는 인터페이스를 말했었다” +> + +
+ +추상 메서드의 시그니처(함수 디스크립터)는 람다 표현식의 시그니처를 정의한다. + +Comparator 함수의 디스크립터는 (T, T) → int 다 + +ex) 사과를 사용한다고 할때 (Apple, Apple) → int 와 같이 표현할수 있을 거다. + +```java +익명클래스 코드 개선 + +inventory.sort((Apple a2, Apple a2) -> a1.getWeight().compareTo(a2.getWeight())); + +자바의 컴파일러는 람다 표현식 사용된 콘텍스트 활용해 람다의 파라미터 형식을 추론하니 +inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight())); +와 같이 줄일 수 있다. + +---------------------------------------------------------------------------------------- + +Comparator는 Comparable 키를 추출해 +Comparator 객체로 만드는 Function 함수를 인수로 받는 정적 메서드 comparing을 포함함 + +Comparator c = Comparator.comapring((Apple a) -> a.getWeight()); + +=> + +inventory.sort(comparing(apple -> apple.getWeight())); + +``` + +
+ +4. 메서드 참조 사용 + +메서드 참조를 사용해 람다 표현식의 인수를 더 깔끔히 전달할 수 있다 + +```java +inventory.sort(comparing(apple -> apple.getWeight())); + +=> + +inventory.sort((comparing(Apple::getWeight)); +``` + +
+ +람다 표현식 조합할 수 있는 유용한 메서드 + +Comparator, Function, Predicate 와 같은 함수형 인터페이스는 람다 표현식 조합할 수 있게 유틸리티 메서드를 제공함. (여러개의 람다 표현식을 조합해서 복잡한 람다 표현식 만들수 있는 것) + +Predicate (연산) Predicate 와 같은 연산을 하는 Predicate를 만들수도, 다른 함수의 입력이 되도록 조합 할수도 있음 + +❓ 함수형 인터페이스에서 추가 메서드를 제공한다면 함수형 인터페에스의 정의에 어긋나게 된다. +(함수형 인터페이스는 하나의 일만 할수 있다) + +여기서 디폴트 메서드로(추상 메서드가 아님) 존재해 있다 + +
+ +Comparator 조합 + +정적 메서드 Comparator.comapring 을 이용해 비교에 사용할 키를 추출하는 Funciton 기반의 Comparator를 반환 가능 + +```java +Comparator c = Comparator.comparing(Apple::getWeight); +``` + +
+ +역정렬 + +내림차순를 하려 한다면, reverse 라는 디폴트 메서드를 제공하여서, 사용하면 된다 + +```java +inventory.sort(comapring(Apple::getweight).reversed()); // 무게 내림차순 정렬 +``` + +
+ +Comperator 연결 + +무게가 같은 사과가 2개라면 다음 비교군을 통해 나열할 수 있다 + +thenComparing은 comparing메서드와 같지만 첫 번째 비교자에서 같은 결과를 가지면 2번째 비교자로 사용한다 + +```java +inventory.sort(comparing(Apple::getWeight) + .resersed() /// 무게 내림차순 정렬 + .thenComparing(Apple::getCountry)); // 두 사과 무게 같으면 국가별로 정렬 +``` + +
+ +Predicate 조합 + +Predicate 는 복잡한 인터페이스를 만들수 있도록 negate, and, or 메서드를 제공함 + +```java +반전의 negate + +Predicate notRedApple = redApple.negate(); +// 기존 Predigate 객체의 결과를 반전시킨 객체 만듬 + +조합 and +Predicate redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150); +// 두 Predicate 연결해 새로운 Predicate 만듬 + +또는 or +Predicate redAndHeavyAppleOrGreen = + redApple.and(apple -> apple.getWeight() > 150) + .or(apple -> GREEN.equals(a.getColor()); +// 더 복잡한 Predicate 사용함 +``` + +Function 조합 + +Function 인터페이스 반환하는 andThen, compose 제공 + +addThen 메서드는 주어진 함수를 먼저 적용한 결과를, 다른 함수의 입력으로 전달하는 함수 반환 + +```java +Function f = x -> x + 1; +Function g = x -> x * 2; +Function h = f.andThen(g); + +int result = h.apply(1); // 4 반환 +// g(f(x)) 와 같음 +``` + +compose 메서드는 주어진 함수를 먼저 실행후 결과를 외부 함수의 인수로 제공함 + +```java +Function f = x -> x + 1; +Function g = x -> x * 2; +Function h = f.compose(g); + +int result = h.apply(1); // 3 반환 +// f(g(x)) 와 같 +``` + +정리 + +- **람다 표현식**은 익명 함수의 일종으로 이름은 없지만, 파라미터 리스트, 바디, 반환 형식 가지며 예외를 던짐 +- **함수형 인터페이스**는 하나의 추상 메서드만 정의하는 인터페이스, 함수형 인터페이스 기대하는 곳에서 람다 표현식 사용할 수 있다 +- 람다 표현식 이용해 함수형 인터페이스의 추상 메서드를 즉석으로 제공할 수 있으며, +**람다 표현식 전체가 함수형 인터페이스의 인스턴스로 취급**됨 +- Predicate, Function, Supplier, Consumer, BinaryOperator 등의 함수형 인터페이스 제공함 +- Predicate, Function 와 같은 제네릭 함수형 인터페이스와 관련된 박싱 동작 피하는 IntPredicate, IntToLongFunction 같은 기본형 특화 인터페이스 제공됨 +- 실행 어라운드 패턴(자원 할당, 자원 정리등의 코드 중간 실행하는 코드)을 람다화 활용하면 유연성과 재사용성을 추가로 얻을수 있음 +- 람다 표현식의 ‘기대 형식’ 을 ‘대상 형식’ 이라 함 +- 메서드 참조를 이용하면 기존의 메서드 구현을 재사용하고 직접 전달할 수 있음 +- Comparator, Predicate, Function 과 같은 함수형 인터페이스는 람다 표현식을 조합할 수 있는 다양한 디폴트 메서드를 제공함 \ No newline at end of file diff --git "a/dongwon/4\354\236\245 \354\212\244\355\212\270\353\246\274 \354\206\214\352\260\234.md" "b/dongwon/4\354\236\245 \354\212\244\355\212\270\353\246\274 \354\206\214\352\260\234.md" new file mode 100644 index 0000000..31cb784 --- /dev/null +++ "b/dongwon/4\354\236\245 \354\212\244\355\212\270\353\246\274 \354\206\214\352\260\234.md" @@ -0,0 +1,213 @@ +# 스트림 소개 + +스트림을 설명하기에 앞서 자바는 프로그래밍 작업하는데 연산을 해 작업을 한다. 이러한 작업과 반대되는 SQL 질의 언어가 있는데 이 같은 경우 선언형으로, 기대하는 것이 무엇이든 **직접 표현**할수 있다, 어떻게 표현해야 할지 명시할 필요없고 구현은 자동으로 된다. + +이와 같은 동작을 컬렉션으로 만들기 위해서 스트림이 나오게 되었다 + +**스트림**은 선언형(데이터를 처리하는 임시 구현 코드 대신 질의로 표현하는것)으로 컬렉션 데이터를 처리할 수 있다 간단하게 생각하면 ‘스트림은 데이터 컬렉션 반복을 처리하는 기능’ 으로 멀티스테드 코드 구현 없이 데이터 **투명하게** 병렬 처리 가능하다 + +
+ +filter, sorted, map, collect 와 같은 **고수준 빌딩 블록**으로 이루어져 특정 스레딩 모델에 제한 없이 자유롭게 사용할 수 있다 ( 내부적론 단일 스레드 모델에 사용할 수 있지만 멀티코어 아키텍처 최대한 투명하게 활용할 수 있게 구현되어 있다) + +⇒ 결론적으로 데이터 처리 과정을 병렬화 하면서 스레드와 락을 걱정할 필요가 없어짐 + +```java +Map> dishesByType = menu.stream().collect(groupingBy(Dish::getType)); + +Map 의 결과 +{ + FISH=[prawns, salmon], + OTHER=[french fries, rice, season fruit, pizza], + MEAT=[pork, beef, chicken] +} +``` + +
+ +자바8 스트림 API의 특징 + +- 선언형 : 더 간결하고 가독성 좋아짐 +- 조립 가능 : 유연성 좋아짐 +- 병렬화 : 성능 좋아짐 + +**스트림은 ‘데이터 처리 연산을 지원하도록 추출된 연속으로 구성된 요소’ 라 볼수 있다** + +- 연속된 요소 : 특정 요소 형식 이루어진 연속된 집합값 인터페이스 제공 + + Collection + + ArrayList 사용과 LinkedList 사용할것인가. 시간과 공간의 복잡성과 관련된 요소 저장과 접근 연산이 주를 이룸 + + Stream + + filter, sorted, map 과 같은 표현 계산식이 주를 이룸 + + +- 소스 : 데이터 제공 소스로부터 데이터를 소비, 정렬된 컬렉션으로 스트림 생성시 정렬 그대로 유지 → 리스트로 스트림 만들시 순서 유지됨! + +- 데이터 처리 연산 : 데이터베이스와 비슷한 연산 지원 ( filter, amp, reduce, find, match, sort 로 데이터 조작), 순차적으로 병렬 실행 + +- 파이프라이닝 : 스트림연산은 연산끼리 연결해 커다란 파이프와 같이 구성하고 스트림 자신을 반환 하도록 함. **게으름, 쇼트서킷**과 같은 최적화도 얻을수 있었음’ + +- 내부 반복 : 컬렉션 같은 경우 반복자를 이용해 명시적으로 내용을 반복하지만 스트림은 내부 반복을 지원한다 + +```java +import static java.util.stram.Collectors.toList; +List threeHighCaloricDishNames = + menu.stram() // <- 메뉴(요리 리스트)에서 스트림을 얻는다 + .filter(dish -> dish.getCalories() > 300 ) + .map(Dish::getName) // <- 요리명 추출 + .limit(3) // <- 선착순 세 개만 선택 + .collect(toList()); // <- 결과를 다른 리스트로 저장 +System.out.println(threeHighCaloricDishNames); // <- 결과는 [port, beef, chicken] +``` + +
+ +데이터 소스 menu(연속된 요소)를 filter, map, limit 와 같은 연산으로 파이프 라인 형성할수 있도록 만듬 collect 연산으로 파이프라인 처리에 결과를 반환(collect 는 스트림이 아닌 List를 반환함) + +마지막 collect 호출 되기 전까지는 menu에서 그 무엇도 선택되지도 않으며, 출력결과도 없다!! + +→ collect가 호출되기 전까지 메서드 호출은 모두 저장이 되는것이다! + +Collect : 스트림을 다른 형석으로 변환한다. (예제 에서는 List로) 변환 방법을 인수로 받아 스트림에 누적된 요소를 특정 결과로 반환하는 기능을 수행한다. (예제의 toList()는 스트림을 리스트로 변환하라 지시하는 인수다) + +스트림 라이브러리에서 filter, map, limit 과 같은 기능을 제공하므로 파이프라인을 더 최적화 하는 유연성을 제공한다 + +
+ +### 스트림과 컬렉션의 차이 + +ex) DVD같은 경우 어떠한 영화가 저장되어 있기에 **컬렉션**이다, +인터넷 스트리밍 같은 경우 **스트림**으로 시청하는 부분의 몇 프레임만 미리 내려 받으며 대부분의 값을 처리하지 않은 상태로 미리 내려받은 프레임부터 재생한다 + +
+ +데이터를 **언제** 계산하느냐? + +**컬렉션**은 현재 자료구조 포함하는 모든 값을 메모리에 저장하므로 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산되어야 한다. ( 이러한 연산 수행할 때마다 모든 요소를 메모리에 저장하며 추가요소는 미리 계산해야 한다 ) + +**스트림**은 요청할 때만 요소를 계산해 고정된 자료구조다. (스트림에 요소 추가나 제거를 할수가 없다) 사용자가 요청하는 값만 스트림에서 추출하는것이 핵심이다. 결과적으로 볼대는 생산자와 소비자 같이 관계가 된다. 또한 게으르게 만들어져 사용자가 데이터 요청할 때만 값을 계산 한다. + +컬렉션은 적극적으로 생산하므로 + +소수를 구할때 무한 루프를 돌며 끝없는 소수를 포함하려 해, 결과 값을 알지 못하게 될거다. + +ex) 브라우저 인터넷 검색할 때 구글에서 검색하면 모든 결과 나오기 전에 10개, 20개 결과요소를 스트림으로 받아볼 수 있다. (다음을 누르면 요청을 받아 계산해서 브라우저에 표시할거다) + +스트림은 단 한번만 탐색할 수 있다. 즉 탐색된 스트림요소는 소비된다. 만약에 I / O 로 받게되면 새로운 스트림을 만들수 없어 반복할 수도 없다 + +
+
+ +### 외부 반복, 내부 반복 + +컬렉션은 인터페이스 사용을 위해 사용자가 ‘직접 반복’ 해야한다. (for - each) + +스트림은 반복을 알아서 처리하며 결과값을 어딘가에 저장하는 ‘내부 반복’ 한다 +- 함수에 어떤 작업 수행할짐나 지정하면 모든 것 알아서 해준다 + +```java +순차(외부적 반복) + +List names = new ArrayList<>(); +for(Dish dish: menu) { // 메뉴 리스트 명시적으로 **순차 반복** + names.add(dish.getName()); // <- 이름 추출해 리스트 추가 +} + +내부 +List names = menu.stram().map(Dish::getName) +// <- map 메서드 getName 메서드로 파라미터화, 요리명 추출 + .collect(toList()); + // <- 파이프라인 실행, 반복 필요없음 + +내부 반복을 함으로서 +1. 한가지 일을 하며 다른 일을 하는 **동시처리가 가능하다** +2. 모든관련 자료를 한곳에 모아 처리할수 있다. **(최적화가 가능하다)** + +이와 같이 내부반복은 데이터 표현과 하드웨어 활용 병렬성 구현을 자동 선택한다 +``` + +
+ +![https://velog.velcdn.com/images/adam2/post/5ecab89a-4c60-4ba6-bc36-3a58915d8b1b/image.png](https://velog.velcdn.com/images/adam2/post/5ecab89a-4c60-4ba6-bc36-3a58915d8b1b/image.png) + +이로서 반복과정을 사용자가(바로 내가?) 신경 쓰지 않아도 된다. 그런데 그러기 위해 사전적으로 filter, map 과 같은 반복 숨겨주는 연산리스트가 미리 정의되어 있어야 한다 + +
+ +### 스트림 연산 + +중간 연산과 최종 연산으로 나뉘게 된다. + +중간 연산 : 단말 연산을 스트림 파이프라인에 실행하기 전까지 아무 연산도 수행하지 않는다. (게으르다! 모든 연산을 합쳐 최종 연산으로 한번에 처리한다) + +```java +중간 연산에서 출력코드 출력은 좋지 않지만 (공부하기에는 참좋은거 같다) + +List names = + menu.stram() + .filter(dish -> { + System.out.println("filtering:" + dishgetName()); + return dish.getCalories() > 300; +}) // 필터링한 요리명 출력 + .map(dish -> { + System.out.println("mapping:" + dish.getName()); + return dish.getName(); +}) // 추출한 요리명 출력 + .limit(3) + .collect(toList()); +System.out.println(names); + +여기서 limit 과 관련된 **쇼트서킷**과 filter, map 을 한과정으로 병합한 **루프 퓨전**이있다. (알고만 있자) +``` + +
+ +최종 연산 : List, Integer, void 와 같은 스트림 이외 결과가 반환된다. + +스트림을 요악하잠녀 + +질의 수행하는 (컬렉션과 같은) 데이터 소스 + +스트림 파이프라인 구성하는 중간 연산 연결 + +스트림 파이프라인 실행하고 결과 만드는 최종 연산이 있다 + +
+ +중간 연산 + +| 연산 | 형식 | 반환 형식 | 연산의 인수 | 함수 디스트립터 | +| --- | --- | --- | --- | --- | +| filter | 중간 연산 | Stram | Predicate | T → boolean | +| map | 중간 연산 | Stram | Funciton | T → R | +| limit | 중간 연산 | Stram | | | +| sorted | 중간 연산 | Stram | Comparator | (T, T) → int | +| distinct | 중간 연산 | Stram | | | + +
+ +최종 연산 + +| 연산 | 형식 | 반환 형식 | 목적 | +| --- | --- | --- | --- | +| forEach | 최종 연산 | void | 스트림의 각 요소를 소비하며 람다 적용 | +| count | 최종 연산 | long(generic) | 스트림의 요소 개수 반환 | +| collect | 최종 연산 | | 스트림을 리듀스해 리스트, 맵, 정수 형식의 컬렉션 만듬 | + +
+
+ +정리 + +스트림은 추출된 연속 요소의 데이터 처리 연산을 한다 + +내부 반복을 지원하며 filter, map, sorted 등의 연산으로 반복을 추상화 한다 + +중간 연산과 최종 연산을 나눠, 중간 연산은 파이프라인을 구성 하지만 어떤 결가도 생성하지 않는다 + +forEach, count 와 같이 파이프라인은 처리, 스트림 아닌 결과를 반환하는 최종 연산이 있다 + +스트림은 나같이. 게으르게 계산된다 \ No newline at end of file diff --git "a/dongwon/5\354\236\245 \354\212\244\355\212\270\353\246\274 \355\231\234\354\232\251.md" "b/dongwon/5\354\236\245 \354\212\244\355\212\270\353\246\274 \355\231\234\354\232\251.md" new file mode 100644 index 0000000..87b8fdd --- /dev/null +++ "b/dongwon/5\354\236\245 \354\212\244\355\212\270\353\246\274 \355\231\234\354\232\251.md" @@ -0,0 +1,384 @@ +스트림을 통해서 외부 반복을 내부 반복으로 바꿀수 있었다 + +그 과정에서 스트림은 데이터를 API가 관리하여 편리하게 데이터 관련 작업을 할 수 있다. + +스트림 API 내부적으로 다양한 최적화가 이루어질 수 있으며. 스트림 API 내부 반복 뿐 아니라 코드를 병렬로 실행할지 여부도 결정할 수 있다 + +### 필터링 + +1) 프레디케이트(Predicate) 필터링 + +filter 메서드는 **프레디케이트**(boolean을 반환하는 함수)를 인수로 받아 Predicate와 일치하는 모든 요소를 포함하는 스트림을 반환한다 + +```java +List vegetarianMenu = menu.stram() + .filter(Dish::isVegetarian) + // ↑ 채식 요리인지 확인 메서드 + .collect(toList()); +``` + +2) 고유 요소 필터링 + +고유 요소로 이루어진 스트림 반환하는 distinct 메서드 지원( 고유 여부는 스트림에서 만든 객체 hashCode, equals 로 결정) **중복을 필터링** 한다 + +```java +List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); +numbers.stream() + .filter(i -> i % 2 == 0) + .distinct() + .forEach(System.out::println); +``` + +### 스트림 슬라이싱 + +스트림 요소를 선택하거나 스킵하여 효율적인 작업 수행 방법들 + +TAKEWHILE 활용 + +```java +List specialMenu = Arrays.asList( + new Dish("seasonal fruit", true, 120, Dish.Type.OTHER), + new Dish("prawns", false, 300, Dish.Type,FISH), + new Dish("rice", true, 350, Dish.Type.OTHER), + new Dish("chicken", false, 400, Dish.Type,MEAT), + new Dish("french fries", true, 530, Dish.Type.OTHER)); + + ↓ + +List filteredMenu = specialMenu.stream() + .filter(dish -> dish.getCalories() < 320 ) + .collect(toList()); + +filter 사용시 전체 스트림을 반복하며 각 요소에 Predicate를 적용하게 된다 +따라서 리스트가 이미 정렬 되어 있다는 사실을 이용해 필터 조건에 따라 반복 작업을 중단할 수 있다. 작은 스트림에선 별거 아닐수 있지만 많은 요소를 포함하는 스트림에선 큰 차이가 날 수 있다 + +이를 takeWhile 연산을 이용해 스트림을 슬라이스해 간단히 할 수 있다 + +List slicedMenu1 = specialMenu.stream() + **.takeWhile(dish -> dish.getCalories() < 320)** + .coleect(toList()); +``` + + + +DROPWHILE 활용 + +나머지 요소를 선택 + +```java +List slicedMenu2 = specialMenu.stream() + .dropWhile(dish -> dish.getCalories() < 320) + .collect(toList()); + +takeWhile과 정반대의 작업으로 Predicate가 처음으로 거짓이 되는 지점까지 요소를 버리고 남은 요소를 반환. 무한한 요소를 가진 무한 스트림에서도 동작한다 +``` + +스트림 축소 + +주어진 값 이하의 크기를 갖는, 새로운 스트림 반환하는 limit(n) 메서드 지원 + +```java +List dishes = specialMenu.stream() + .filter(dish -> dish.getCalories() > 300) + .limit(3) + .collect(toList()); + +정렬되지 않은 스트림(ex. set) 에도 limit 사용 가능 +``` + +요소 건너뛰기 + +n개 이하 요소를 포함하는 스트림에 skip(n) 호출하면 빈 스트림 반환됨 + +```java +List dishes = menu.stream() + .filter(d -> d.getCalories() > 300) + .skip(2) + .collect(toList()); +``` + +### 매핑 + +데이터 처리 과정에서 자주 수행되는 연산 map 과 flatMap 메서드는 특정 데이터 선택하는 기능 제공함 + +스트림 각 요소에 함수 적용 + +스트림은 map 메서드 지원함. 새로운 요소로 매핑되며 (이 과정을 기존 값을 고친다기 보다 ‘새로운 버전을 만든다’ 라 봄) + +```java +List dishNames = menu.stream() + .map(Dish::getName) + .collect(toList()); +스트림의 요리명을 추출함 +getName 같은 경우 문자열을 반환해서 map 메서드의 출력 스트림은 Stream 형식을 가짐 + +ex) 각 단어가 포함하는 글자 수의 리스트 반환 + +List words = Arrays.asList("Modern", "Java", "In", "Action"); +List wordLengths = words.stream() + .map(String::length) + .collect(toList()); +``` + +스트림 평면화 + +[”Hello”, “World”] 리스트를 map 형식으로 만들게 되면 각 단어의 String[ ] 문자열 배열을 반환 하게 될것이다. ( map 메소드 반환한 스트림 형식은 Stream 이다 만들어야 할 형식은 Stream 이다 + +이러한 문제를 flatMap 메서드를 통해 해결할 수 있다 + +Arrays.stream + +문자열을 받아 스트림을 만드는 Arrays.stream() 메서드가 있다 + +```java +String arrayOfWords[] = {”Goobye”, “World” }; +Stream streamOfwords = Arrays.stream(arrayOfWords); + +(Arrays.stream() 메서드 적용) + +words.stream() + .map(word -> word.split("")) <- 각 단어를 개별 문자열 배열로 변환 + .map(Arrays::stream) <- 각 배열을 별도의 스트림으로 생성 + .distinct() + .collect(toList()); + +결론적으로는 List> 이 만들어져서 문제가 해결된 건 아니다 +해결 하기 위해서는 각 단어를 개별 문자열로 만들고 별도의 스트림으로 만들어야 한다 +``` + +flatMap 사용 + +위의 문제를 해결하기 위해 flatMap 을 사용하는데 + +```java +List uniqueCharacters = words.stream() + .map(word -> word.split("")) + // 각 단어를 개별문자를 포함하는 배열로 변환 + .flatMap(Arrays::stream) + // 생성된 스트림을 하나의 스트림으로 평면화 + .distinct() + .collect(toList()); + +각 배열을 스트림이 아닌 스트림의 콘텐츠로 매핑하는 것. map 과 달리 하나의 평면화된 스트림을 반환하는 것이다 + +"Hello”, “World" // Stream +map(s -> s.split("")) => [”Hello”, “World”] // Stream +flatMap(Arrays::stream) => H, e, l, l, l, o, W, o, r, l, d // Stream +distinct() => H, e, l, o, w, r, d // Stream +collect(toList()) => [H, e, l, o, W, r, d] // List +``` + +검색과 매칭 + +allMatch, anyMatch, noneMatch, findFirst, findAny 등의 다양한 메서드가 있음 + +Predicate 가 적어도 한 요소와 일치하는지 확인 + +anyMatch 메서드 이용 + +```java +if(menu.stream().anyMatch(Dish::isVegetarian)) { + System.out.println("The menu is (somewhat) vegetarian friendly!!"); +} + +anyMatch는 boolean을 반환하므로 최종 연산에 들어간다 +``` + +Predicate 가 모든 요소와 일치하는지 검사 + +allMatch + +```java +boolean isHealthy = menu.stream() + .allMatch(dish -> dish.getCalories() < 1000); +``` + +noneMatch + +allMatch 와 반대의 연산을 수행, 주어진 Predicate와 일치하는 요소가 없는지 확인 + +```java +이전 예제를 다음과 같이 구현가능 +boolean isHealthy = menu.stream() + .noneMatch(d -> d.getCalories() >= 1000); +``` + +anyMatch, allMatch, noneMatch 세 메서드는 스트림 **쇼트서킷** 기법 이라 부른다 (자바의 &&, || 와 같은 연산) + + + +### 요소 검색 + +findAny 는 현재 스트림에서 임의의 요소를 반환한다. 다른 스트림 연산과 연결해 사용할 수 있다 + +```java +Optional dish = menu.stream() + .filter(Dish::isVegetarian) + .findAny(); + +스트림 파이프라인은 내부적으로 단일 과정으로 할수 있도록 최석화 한다. 이 말은 곧 쇼트서킷을 이용해서 결과를 찾는 즉시 실행을 종료하는 것이다. + +여기서 **Optional** 을 사용했다.(뭘까) +``` + +**Optional** + +Optional 클래스는 값의 존재 부재 여부를 표현하는 컨테이너 클래스로, 이전 예제의 findAny 는 아무 요소도 반환하지 않을 수 있다. 그때 null은 쉽게 에러를 일으킬 수가 있어서 Optional 를 만들었다. + +> 요약하자면 Optional 은 값이 존재하는지 확인하고 값이 없을 때 어떻게 처리할지 강제할 수 가 있다 +> +- isPresent( ) 는 Optional이 값을 포함하면 참(true), 포함하지 않으면 거짓(false)를 반환 +- isPresent(Consumer block) 값이 있으면 주어진 블록 실행 +Consumer 함수형 인터페이스 같은 경우 T 형식의 인수를 받으며 void를 반환하는 람다 전달 할수 있다 +- T get( ) 값이 존재하면 값을 반환하고, 없으면 NoSuchElementException 일으킨다 +- T orElse(T other) 값이 있으면 반환하고, 없으면 기본값을 반환한다 + +```java +Optional 을 사용하지 않았을때 + +menu.stream() + .filter(Dish::isVegetarian) + .findAny() <- Optional 반환 + **.ifPresent**(dish -> System.out.println(dish.getName()); + // 값이 있으면 출력, 없으면 아무 일도 일어나지 않음 +``` + +### 첫 번째 요소 찾기 + +일부 스트림에선 논리적인 아이템 순서 있을수 있음 + +첫 번째 요소 찾는 방법은? + +```java +List someNumbers = Arrays.asList(1, 2, 3, 4, 5); +Optional firstSquareDivisibleByThree = someNumbers.stream() + .map(n -> n * n) + .filter(n -> n % 3 == 0) + .findFirst(); // 9 +``` + + + +### 리듀싱 + +스트림 요소를 조합해 더 복잡한 질의를 표현하는 방법 설명 + +→ 이러한 질의 수행을 위해서는 같은 결과 나올 때 까지 스트림 모든 요소를 반복적으로 처리해야 한다. 이를 **리듀싱 연산** 이라 한다. + +(함수형 프로그래밍 언어 용어론 **폴드** 라고 부른다) + +### 요소의 합 + +```java +fin sum = 0; +for (int x : numbers) { + sum += x; +} + +대신에 reduce 를 사용하면 애플리케이션 반복 패턴을 추상화 할 수 있다 + +int sum = numbers.stream().reduce(0, (a, b) -> a + b); + +reduce 는 2 개의 인수를 가짐 +- 초깃값 0 +- 두 요소 조합해 새로운 값 만드는 BinaryOperator + (예제에서는 람다 표현식 (a, b) -> a + b 사용) + +스트림이 하나의 값으로 줄어들 때 까지 람다는 각 요소를 반복해 조합한다 + +자바 8에는 정적메서드 sum 제공한다 +int sum = numbers.stream().reduce(0, Integer::sum); +``` + +**초깃값 없음** + +초깃값 받지 않은 reduce 일 경우 Optional 객체 반환함 + +```java +Optional sum = numbers.stream().reduce((a, b) -> (a + b)); + +스트림에 아무 요소가 없다면 reduce는 초깃값이 없어서 합계를 반환할 수 없다 +합계가 없음을 가리키는 Optional 객체로 감산 결과를 반환한다 +``` + +**최대값 & 최솟값** + +```java +Optional max = numbers.stream().reduce(Integer::max); +Optional min = numbers.stream().reduce(Integer::min); +``` + + + + + +| 연산 | 형식 | 반환 형식 | 사용된 함수형 인터페이스 형식 | 함수 디스크립터 | +| --- | --- | --- | --- | --- | +| filter | 중간연산 | Stream | Predicate | T → boolean | +| distinct | 중간연산 | Stream | | | +| takeWhile | 중간연산 | Stream | Predicate | T → boolean | +| dropWhile | 중간연산 | Stream | Predicate | T → boolean | +| skip | 중간연산(상태 있는 바운드) | Stream | long | | +| limit | 중간 연산(상태 있는 바운드) | Stream | long | | +| map | 중간연산 | Stream | Function | T → R | +| flatMap | 중간 연산 | Stream | Function> | T → Stream | +| sorted | 중간 연산(상태 있는 언바운드) | Stream | Comparator | (T, T) → int | +| anyMatch | 최종 연산 | boolean | Predicate | T → boolean | +| noneMatch | 최종 연산 | boolean | Pridicate | T → boolean | +| allMatch | 최종 연산 | boolean | Pridicate | T → boolean | +| findAny | 최종 연산 | Optioinal | | | +| findFirst | 최종 연산 | Optional | | | +| forEach | 최종 연산 | void | Consumer | T → void | +| collect | 최종 연산 | R | Collector | | +| reduce | 최종 연산(상태 있는 바운드) | Optional | BinaryOperator | (T, T) → T | +| count | 최종 연산 | long | | | + +### 문제 + +```java + +``` + +숫자형 스트림 \ No newline at end of file diff --git "a/dongwon/6\354\236\245 \354\212\244\355\212\270\353\246\274\354\234\274\353\241\234 \353\215\260\354\235\264\355\204\260 \354\210\230\354\247\221.md" "b/dongwon/6\354\236\245 \354\212\244\355\212\270\353\246\274\354\234\274\353\241\234 \353\215\260\354\235\264\355\204\260 \354\210\230\354\247\221.md" new file mode 100644 index 0000000..ca66f89 --- /dev/null +++ "b/dongwon/6\354\236\245 \354\212\244\355\212\270\353\246\274\354\234\274\353\241\234 \353\215\260\354\235\264\355\204\260 \354\210\230\354\247\221.md" @@ -0,0 +1,195 @@ +스트림은 filter, map 과 같은 중간 연산과 count, findFirst, forEach, reduce등의 최종 연산으로 구성되어 있음. + +중간 연산은 스트림 파이프라인을 구성하여 스트림의 요소를 소비하지 않지만! 최종 연산은 스트림의 요소를 소비해 최종 결과를 도출했음 + +Collector 컬렉터, Collection 컬렉션, collect 구별 ⭐ + +collect 와 컬렉터로 구현할 수 있는 질의 + +- 통화별 트랜잭션 그룹화한 후 모든 트랜잭션 합계 +(Map) +- 비싼 트랜잭션과 저렴한 트랜잭션 두 그룹으로 분류 +(Map>) +- 트랜잭션 도시 등 다수준으로 그룹화 and 각 트랜잭션 비싼지 저렴한지 구분 +(Map>> 반환) + +```java +Map> '현재 트랜잭션' = new HashMap<>(); +// 그룹화한 트랜잭션 저장할 맵 생성 + +for(Transaction transaction : transactions) { +// 트랜잭션 리스트 반복 + Currency currency = transaction.getCurrency(); + List transactionsForCurrency = '현재 트랜잭션'.get(currency); + if (transactionsForCurrency == null) { + transactionsForCurrency = new ArrayList<>(); + transactionsForCurrencies.put(currency, transactionsForCurrency); + } + transactionsForCurrency.add(transaction); +} + + ↓ + +Map> transactionsByCurrencies = transactions.stream().collect(groupingBy(Transaction::getCurrency)); +``` + +컬렉터란 무엇인가? + +함수형 프로그래밍에서는 ‘무엇’을 원하는지 직접 명시할 수 있어 어떤 방법으로 이를 얻을지는 신경 쓸 필요 없다. (예제에서는 collect 메서드로 Collector 인터페이스 구현을 전달함) + +Collector 인터페이스 구현은 스트림의 요소를 어떤 식으로 도출할지 지정함 +(toList를 5장에서 Collector 인터페이스의 구현으로 사용했었음) + +고급 리듀싱 기능 수행하는 컬렉터 + +높은 수준의 조합성과 재사용성을 가질수 있으며 collect 결과를 수집하는 과정을 간단하면서 유연한 방식으로 정의할 수 있음. 스트림에 collect 호출시 스트림 요소에 (컬렉터로 파라미터화된) 리듀싱 연산 수행됨. + +리듀싱 연산의 과정 + +![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/39274b66-c08f-4c49-ab04-c95a80f3e67c/Untitled.png) + +컬렉터를 적용하며 최종 결과를 저장하는 자료구조에 값을 누적 + +Collector 인터페이스의 메서드를 어떻게 구현하느냐에 따라 스트림에 어떤 리듀싱 연산을 수행할지 결정된다 + +```java +List transactions = trnsactionStream.collect(Collectors.toList()); +``` + +미리 정의된 컬렉터 + +groupingBy 와 같이 Collectors 클래스에서 제공되는 메서드 기능 3가지 + +- 스트림 요소를 하나의 값으로 리듀스하고 요약 +- 요소 그룹화 +- 요소 분할 + +### 리듀싱과 요약 + +컬렉터로 스트림의 항목을 컬렉션으로 재수성(모든 항목 하나의 결과로 합침) + +```java +counting() 요리수 계산 + +long howManyDishes = menu.stream().collect(Collectors.counting()); + +=> long howManyDishes = menu.stream().count(); +``` + +스트림에서 최댓값 최솟값 검색 + +`Collectors.maxBy`, `Collectors.minBy` 두 컬렉터는 스트림 요소 비교하는 Comparator 인수로 받음 + + + +```java +Comparator 구현후 Collectors.maxBy 로 전달 + +Comparator dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories); + +Optional mostCalorieDish = menu.stream().collect(maxBy(dishCaloriesComparator)); +// Opntional 통해서 값을 포함하는지 안하는지 확인 +``` + +이러한 연산에 리듀싱 기능이 사용되며 **요약**연산이라 부름 + +요약 연산 + +Collectors 클래스의 `Collectors.summingInt` 특별 요약 팩토리 메서드 제공함 + +객체를 int로 매핑하는 인수 받아서 객체를 int로 매핑한 컬렉터 반환함 + +```java +summingInt가 collect 메서드로 전달시 요약 작업 수행 +int totalCalories = menu.stream().collect(summingInt(Dish::getCalories)); + +// summingLong, summingDouble 도 같은 방식 동작함 +double avgCalories = menu.stream().collect(averagingInt(Dish::getCalories)); +``` + +문자열 연결 + +joining 팩토리 메서드 이용해 스트림 각 객체에 toSring 메서드 호출해 추출한 모든 문자열 하나의 문자열로 연결해 반환 + +```java +String showrtMen = menu.stream().map(Dish::getName).collect(joining()); +``` + +joining 메서드는 내부적으로 StringBuilder 이용해 문자열 하나로 만듬. Dish 클래스가 toString 메서드 포함시 각 요리 이름 추출하는 과정 생략가능 + +```java +String shortMenu = menu.stream().collect(joining()); + +but 결과 해석할 수 없음, 구분자 넣어서 구분해 줘야함 + +String shortMenu = menu.stream().map(Dish::getName).collect(joining(", ")); +``` + +범용 리듀싱 요약 연산 + +모든 컬렉터 reducing 팩토리 메서드로 정의할수 있지만 특화된 컬렉터 사용이유는 프로그래밍적 편의정 때문(+ 가독성도 중요) + +```java +reducing 메서드로 만들어진 컬렉터 +똑같이 모든 칼로리 합계 계산할 수 있음 +int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories,(i, j) -> i + j; + +// reducing은 인수를 3개나 받는다 +``` + +- 첫 번째 인수는 리듀싱 연산의 시작값이거나 스트림에 인수가 없을 때는 반환값이다(숫자 합계에서는 인수가 없을 때 반환값으로 0이 적합함) +- 두 번째 인수는 요리를 칼로리 정수로 변환할 때 사용한 변환 함수다 +- 세 번째 인수는 같은 종류의 두 항목을 하나의 값으로 더하는 BinaryOperator 다(예제에서는 두 개의 int 사용) + +```java +한 개의 인수가진 reducing 버전 이용해 찾을수도 있음 +Optional mostCalorieDish = menu.stream().collect(reducing(d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)); +``` + +한 개의 인수 갖는 reducing은 3개의 인수 갖는 reducing 에서 스트림 첫 번째 요소를 시작요소, (첫번째 인수로 받고), 자신을 그대로 반환하는 **항등 함수**를 두 번째 인수로 받는 상황에 해당 + +⇒ 한 개의 인수 갖는 reducing은 시작값이 없어서 빈 스트림 넘겨지면 시작값이 설정되지 않은 상황이 벌어짐 + +> Collect와 reduce + +Stream 인터페이스의 Collect와 reduce메서드의 차이는 1) 의미론적 문제와, 2) 실용성 문제가 있다 + +collect 메서드는 도출 결과를 누적하는 컨테이너를 바꾸도록 설계된 메서드인 반면 reduce는 두 값을 하나로 도출하는 불변형 연산이다. 그래서 누적된 리스트를 reduce로 변환시키며 진행되면 reduce를 잘못 활용한것이 된다. + +더불어 reduce를 잘못 사용함으로 여러 스레드가 동시에 데이터 구조체를 고칠시 리스트 자체가 망가져서 리듀싱 연산을 병렬로 수행할 수 없게된다. +⇒ 매번 새로운 리스트 할당하고, 객체 할당해서 성능이 저하될 것이다 +( 병렬성 확보를 위해 collect 메서드로 리듀싱 연산을 구현하자) +> + +컬렉션 프레임워크 유연성 : 같은 연산도 다양한 방식으로 + +```java +int totalCalories = menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum)); + +public static Collector conting() { + return reducing(0L, e -> 1L, Long::sum); +} +``` + +> 제네릭 와일드 카드 ‘?’ 사용법 + +? 는 컬렉터의 누적자 형식이 알려지지 않아서 누적자의 형식이 자유로움을 의미한다. 위 예제에서는 Collectors 클래스에서 원래 정의된 메서드 시그니처를 그대로 사용했을 뿐. 이후 오해의 소지가 생겨 사용하지 않을 것. +> + +```java +컬렉터 사용하지 않고 같은 연산 수행하기(요리 스트림을 요리의 칼로리로 매핑해서 다음의 이전 버전 예제에서 사용된 메서드 참조로 결과 스트림을 리듀싱) + +int totalCalories = menu.stream().map(Dish::getCalories).reduce(Integer::sum).get +``` + +한개의 인수 갖는 reduce (Integer::sum) 도 빈 스트림 Null 피할수 있도록 Optional를 반환 + +일반적으로 orElse, orElseGet 등의 기본값 제공가능한 것을 이용해 Optional 값 얻어오는것이 좋다 + +```java +IntStream으로 매핑하고 sum 메서드 호출하는 방법으로 결과 얻을수도 있음 + +int totalCalories = menu.stream().mapToInt(Dish::getCalories).sum(); +``` + +자신의 상황에 맞는 해법 선택 \ No newline at end of file diff --git "a/dongwon/7\354\236\245 \353\263\221\353\240\254 \353\215\260\354\235\264\355\204\260 \354\262\230\353\246\254\354\231\200 \354\204\261\353\212\245.md" "b/dongwon/7\354\236\245 \353\263\221\353\240\254 \353\215\260\354\235\264\355\204\260 \354\262\230\353\246\254\354\231\200 \354\204\261\353\212\245.md" new file mode 100644 index 0000000..e69de29 diff --git "a/dongwon/8\354\236\245 \354\273\254\353\240\211\354\205\230 API \352\260\234\354\204\240.md" "b/dongwon/8\354\236\245 \354\273\254\353\240\211\354\205\230 API \352\260\234\354\204\240.md" new file mode 100644 index 0000000..730ec5e --- /dev/null +++ "b/dongwon/8\354\236\245 \354\273\254\353\240\211\354\205\230 API \352\260\234\354\204\240.md" @@ -0,0 +1,585 @@ +# 컬렉션 API 개선 + +## 기존 형식의 자바 코드 리팩터링 하는 기법들 + +
+ +자바 개발을 도와주는 컬렉션 API. 자바 9에서 컬렉션 API의 단점을 보완하고, 추가된 새로운 컬렉션 API 의 기능들이 있다. + +크게 3가지의 기능으로 나누어 설명한다 + +1. 작은 리스트, 집합, 맵을 쉽게 만들수 있도록 한 컬렉션 팩토리 +2. 리스트와 집합에서 요소를 삭제하거나 바꾸는 관용 패턴을 적용하는 방법 +3. 맵 작업과 관련하여 추가된 편리한 새로운 기능 + +
+ +## 컬렉션 팩토리 + +작은 컬렉션 객체를 쉽게 만들 수 있는 방법 + +```java +기존 + +List friends = new ArrayList<>(); +friends.add("Raphael"); +friends.add("Olivia"); +friends.add("Thibaut"); + +Arrays.asList() 팩토리 메서드 사용하기 + +List friends = Arrrays.asList("Raphael", "Olivia", "Thibaut"); +``` + +고정 크기의 리스트를 만들어서 요소를 갱신할 순 있지만 새 요소를 추가하거나 삭제할 수는 없다 + +
+ +요소를 추가하려 하면 `UnsupportedOperationException`이 발생한다 + +```java +List friends = Arrrays.asList("Raphael", "Olivia"); +friends.set(0, "Richard"); +friends.add("Thibaut"); +``` + + + +
+ +### UnsupportedOperationException 예외 발생 + +내부적으로 고정된 크기의 변환할 수 있는 배열로 구현되어서 예외가 발생하는 것 + +집합에서는 `Arrays.asSet()` 의 팩토리 메서드가 없어서 `Hashset` 생성자를 사용할 수 있다 + +```java +HashSet 생성자 사용 + +Set friends = new HashSet<>(Arrays.asList("Raphael", "Olivia", Thibaut")); + +스트림 API 사용 + +Set friends = Stream.of("Raphael", "Olivia", "Thibaut") + .coolect(Collectors.toSet()); +``` + +두 방법 모두 매끄럽지 못하며 내부적으로 불필요한 객체 할당을 필요로 한다. 또한 결과는 변환할 수 있는 집합이다 + +맵은 작은 맵 만드는 메서드 따로 없지만 자바9에서는 작은 리스트, 집합, 맵 쉽게 만드는 팩토리 메서들르 제공한다 + +
+
+ +
+
+ +### 리스트 팩토리 + +List.of 팩토리 메소드 사용해 간단한 리스트 만들기 + +```java +List friends = List.of("Raphael", "Olivia", "Thibaut"); +System.out.println(friends); // [Raphael, Olivia, Thibaut] + +여기 friends 리스트에 요소를 추가하면 +friends.add("Chih_chun"); +java.lang.UnsupportedOperationException 에러가 발생한다 + +set() 메서드로 아이템 바꾸려 해도 비슷한 예외가 발생한다 -> set 메서드로도 리스트를 바꿀 수 없다 +``` + +바꿀수 없다는 것이 단점이 될 수도 있지만. 컬렉션이 의도치 않게 변하는 것을 막을 수도 있다. [쓰기 나름이라는 것] + +하지만?! 요소 자체가 변하는 것을 막을 수 있는 방법은 없다. 리스트를 바꿔야 한다면 직접 리스트를 만들면 된다 + +또한 null 요소를 금지하여 의도치 않은 버그를 방지하고, 조금 더 간결한 내부 구현을 달성했다 + +> 278.p 요소 자체 변하는 경우??? + + + +
+ +
+ + + +
+새롭게 컬렉션 팩토리 메서드를 배워봤는데 언제 팩토리 메서드 대신 스트림 API를 사용해 리스트를 만들어야 하나? + +
+ + 요소 변환 또는 필터링 할때, 복잡한 데이터 처리할때, 지연 평가, 기존 코드와 통합할때 스트림API를 이용하고 + + 데이터 처리 형식을 설정하거나, 데이터를 변환할 필요 없으면 사용하기 간편한 팩토리 메서드를 이용하기 권장한다. 팩토리 메서드 구현이 더 단순하고 목적 달성에 충분하다 + + ### Collectors.toList()를 사용하여 스트림을 목록으로 변환하면 목록 인터페이스에서 제공하는 다양한 메서드에 액세스할 수 있습니다 + +
+ +### 집합 팩토리 + + List.of 와 같이 바꿀 수 없는 집합 만들기 + +```java +Set friends = Set.of("Raphael", "Olivia", "Thibaut"); +System.out.println(friends); // [Raphael, Olivia, Thibaut] + +중복된 요소 제공해 집합 만들려 하면 Olivia 요소가 중복되어 있다는 말과 함께 IllegalArgumentExcption이 발생한다.(집합은 고유의 요소 포함하는 특성으로 그렇다) +``` +
+ +### 맵 팩토리 + +맵을 만들때는 키와 값이 필요해서 조금 더 복잡하다. +두가지 방법으로 바꿀 수 없는 맵을 초기화 할수 있는데 Map.of 팩토리 메서드의 키와 값을 번갈아 제공하는 방법으로 맵을 만들수 있다 + +```java +Map ageOfFriends = Map.of("Raphael", 30, "Olivia", 25, "Thibaut", 26); +System.out.println(ageOfFriends); // <- {Ollivia=25, Raphael=30, Thibaut=26} + +10개 이하의 키와 값 쌍을 가진 작은 맵 만들 때는 이 메소드가 유용하다. +그 이상의 맵에서는 Map.Entry 객채를 인수로 받으며 가변 인수로 구현된 Map.ofEntries 팩토리 메서드를 이용하는 것이 좋다. 이 메서드는 키와 값을 감쌀 추가 객체 할당을 필요로 한다. + +10개 이상의 값을 가질때 +Map ageOfFreinds = Map.ofEntres(entry("Raphael", 30), + entry("Olivia", 25), + entry("Thibaut", 26)); +System.out.println(ageOfFriends); // <- {Olivia=25, Raphael=30, Thibaut=26} + +Map.entry는 Map.Entry 객체를 만드는 새로운 팩토리 메서드다 +``` + +팩토리 메서드를 이용해서 컬렉션을 좀더 쉽게 만드는 방법을 봤는데. 자바 9가 있기전에는 컬렉션을 만들때 어떤 문제가 있었나??? + + 문제점 + 1. 컬렉션을 초기화 해야하는데 요소가 많을시 코드가 길어져 가독성을 해쳤다. + 2. 안전성과 스레드의 안정성을 보장하는 수정 불가능 컬렉션 만들기가 어려웠다 + +
+ +### 리스트와 집합 처리 + +자바8 에서는 List, Set 인터페이스에 다음과 같은 메서드를 추가했다 + +- removeIf : 프리티케이트를 만족하는 요소를 제거한다. List나 Set을 구현하거나 그 구현을 상속받은 모든 클래스에서 이용할 수 있다 +- replaceAll : 리스트에서 이용할 수 있는 기능으로 UnaryOperator 함수를 이용해 요소를 바꾼다 +- sort : List 인터페이스에서 제공하는 기능으로 리스트를 정렬한다 + +이 메서드들은 호출한 컬렉션 자체를 바꾼다. 새로운 결과를 만드는 스트림 동작과 달리 이들 메서드는 기존 컬렉션을 바꾼다. 컬렉션을 바꾸는 동작은 에러를 유발하며 복잡함을 더해서, 자바8에 removeIf와 replaceAll을 추가한 이유다 + +
+ +### removeIf 메서드 + +숫자로 시작되는 참조 코드 가진 트랜잭션 삭제 코드 + +```java +for (Transaction transaction : transactions) { + if(Character.isdDigit(transaction.getReferenceCode().charAt(0))) { + transactions.remove(transaction); + } +} +``` +
+ +위의 코드는 `ConcurrentMOdificationException` 을 일으키는데 내부적으로 for-each 루프는 Iterator 객체를 사용해서 위의 코드를 풀어서 보면 + + +```java +for (Iterator iterator = transactions.iterator(); + iterator.hasNext(); ) { + Transaction transaction = iterator.next(); + if(Character.isDigit(transaction.getReferenceCode().charAt(0))) { + transactions.remove(transaction); + // 반복하면서 별도의 두 객체를 통해 컬렉션을 바꾸고 있는 문제가 있다 + } +} +``` + +두 개의 개별 객체가 컬렉션을 관리하게 된다 +- Iterator 객체, next(), hasNext() 를 이용해서 소스를 질의한다 +- Collection 객체 자체, remove() 를 호출해 요소를 삭제한다 + +**결론적으로 반복자의 상태는 컬렉션의 상태와 서로 동기화되지 않는다. Iterator 객체를 명시적으로 사용하고 그 객체의 remove() 메서드를 호출함으로 문제를 해결할 수 있다** + +
+ +```java +for (Iterator iterator = transactions.iterator(); + iterator.hasNext(); ) { + Transaction transaction = iterator.next(); + if(Character.isDigit(transaction.getReferenceCode().charAt(0))) { + transactions.remove(); + // 반복하면서 별도의 두 객체를 통해 컬렉션을 바꾸고 있는 문제가 있다 + } +} +``` + +좀 복잡한 코드를 자바8의 removeIf 메서드로 바꿀 수 있다. removeIf 메서드는 삭제할 요소를 가리키는 프레디케이트를 인수로 받는다 + +```java +transactions.removeIf(transaction -> + Character.isDigit(transaction.getReferenceCode().charAt(0))); +``` + +요소를 제거하는것이 아닌 바꿔야 하는 상황에서는 repalceAll을 사용하면 된다 + +
+ +### repalceAll 메서드 + +리스트의 각 요소를 새로운 요소로 바꿀 때 + +```java +referencecodes.stram() <- [a12, c14, b13] + .map(code -> Character.toUpperCase(code.charAt(0)) + code.substring(1)) + .collect(Collectors.toList()) + .forEach(System.out::println); <- outputs A12, C14, B13 +``` + +스트림API 는 요소를 바꾸긴 하지만 새 문자열 컬렉션을 만든다. 원하는 바는 기존 컬렉션을 바꾸는 것이니 + +ListIterator 객체(요소를 바꾸는 set() 메서드 지원)를 이용할 수 있다 + +```java +for (ListIterator iterator = referenceCodes.listIterator(); + iterator.hasNext(); ) { + String code = iterator.next(); + iterator.set(Character.toUpperCase(code.charAt(0)) + code.substring(1)); +} +``` + +코드가 좀 복잡해 졌으며, 컬렉션 객체를 Iterator 객체와 혼용하면 반복과 컬렉션 변경이 동시에 이루어지며 쉽게 문제가 일어난다. 자바 8의 기능 repalceAll 이용하면 좀더 보기 좋다 + +```java +referenceCodes.repalceAll(code -> Character.toUpperCase(code.charAt(0)) + code.substring(1)); +``` +
+ +### 맵 처리 + +자바8 에서 Map 인터페이스에 몇 가지 디폴트 메서드가 추가되었다. 간단히 기본적인 구현을 인터페이스에 제공하는 기능 정도로 생각하자 + +
+ +### forEach 메서드 + +맵에서 키와 값을 반복하며 확인하는 작업. 실제 Map.Entry 반복자 이용해 맵의 항목 집합을 반복할 수 있다 + +```java +for(Map.Entry entry: ageOfFriends.entrySet()) { + String friend = entry.getKey(); + Integer age = entry.getValue(); + System.out.println(friend + " is " + age + " years old"); +} +``` + +자바8 부터는 Map 인터페이스는 BigConsumer(키와 값을 인수로 받는)를 인수로 받는 forEach 메서드를 지원해서 코드를 좀더 간단히 구현할 수 있다 + +```java +ageOfFriends.forEach((friend, age) -> System.out.println(friend + " is " + age + " years old")); +``` + +자바 8에서 맵의 항목을 쉽게 비교할 수 있는 방법들 + +
+ +**정렬 메서드** + +맵의 항목을 값 또는 키 기준으로 정렬 + +- Entry.comparingByValue +- Entry.comparingByKey + +```java +Map favoriteMovies = +Map.ofEntries(entry("Raphael", "Star Wars"), + entry("Cristina", "Matrix"), + entry("Olivia", "James Bond")); + +favouriteMovies + .entrySet() + .stream() + .sorted(Entry.comparingByKey()) + .forEachOrdered(System.out::println); + // 사람의 이름을 알파벳 순으로 스트림 요소를 처리한다 + +-------------------------------------------------------------------------------- + +결과 +Cristina = Matrix +Olivia = James Bond +Raphael = Star Wars +``` + + +
+ +
+ +### getOrDefault 메서드 + +기존에는 찾으려는 키가 없을시 null 이 반환되어. NullpointerException을 방지하기 위해서 요청결과가 null인지 확인해야 했다. 이를 기본값 반환하는 방식으로 해결할수 있었는데 + +getOrDefault 메서드를 이용하면 쉽게 문제를 해결할 수 있었다. 이 메서드는 첫 번째 인수로 키를, 두 번째 인수로 기본값을 받으며 맵에 키가 존재하지 않으면 두 번째 인수로 받은 기본값을 반환한다. + +```java +Map favouriteMovies = Map.ofEntries(entry("Raphael", "Star Wars"), entry("Oliivia", "James Bond")); + +System.out.println(favouriteMovies.getOrDefault("Olivia", "Matrix")); +// 키가 있을 때 : James Bonde 출력 +System.out.println(favouriteMovies.getOrDefault("Thibaut", "Matrix")); +// 키가 없을 때 : Matrix 출력 +``` + +키가 존재하더라도 값이 널인 상황에서는 getOrDefault가 null을 반환할 수 있다. + +
+ +### 계산 패턴 + +키의 존재 여부에 따라 동작을 실행하고 결과를 저장하는 상황에 도움을 주는 연산 + +- computeIfAbsent : 제공된 키에 해당하는 값이 없으면(값이 없거나 null), 키를 이용해 새 값을 게산하고 맵에 추가한다 +- computeIfPresent : 제공된 키가 존재하면 새 값을 계산하고 맵에 추가한다 +- compute : 제공된 키로 새 값을 계산하고 맵에 저장한다 + +정보를 캐시할 때 computeIfAbsent활용가능, 기존 데이터를 처리했다면 다시 계산할 필요 없다 + +
+ +맵 이용해 캐시 구현 + +```java +Map dataToHash = new HashMap<>(); +MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); + +lines.forEach(line -> + dataToHash.computeIfAbsent(line, this::calculateDigest)); + // 키가 존재하지 않으면 동작을 실행한다 + // line은 맵에서 찾을 키다 + +private byte[] calculateDigest(String key) { + return messageDigest.digest(key.getBytes(StandardCharsets.UTF_8)); +} +``` +
+ +여러 값 저장하는 맵 처리할 때도 유용. Map>에서 요소를 추가하려면 항목이 초기화 되어 있는지 확인해야 한다 + +```java +String friend = "Raphael"; +List movies = friendsToMovies.get(friend); +if(movies == null) { // 리스트가 초기화 되어 있는지 확인 + movies = new ArrayLisst<>(); + friendsToMovies.put(friend, movies); +} + +movies.add("Star Wars"); // 영화 추가 + +System.out.println("Star Wars"); // <- {Raphael:[Star Wars]} + +computeIfAbsent 활용 + +friendstoMovies.computeIfAbsent("rphael", name -> new ArrayList<>()) + .add("Star Wars"); <- {Raphael:[Star Wars]} +``` + +`computeIfPresent` 메서드는 현재 키와 관련된 값이 맵에 존재하며 null이 아닐 때만 새 값을 계산. 이 메서드의 실행과정은. 값을 만드는 함수가 null을 반환하면 현제 매핑을 맵에서 제거. but 매핑을 제거할 때 remove 메서드를 오버라이드 하는게 더 적합하다 + +
+ +### 삭제 패턴 + +remove 메서드를 통해 삭제하는데. 자바 8에서는 키가 특정한 값과 연관되었을 때만 항목을 제거하는 오버로드 버전 메서드를 제공한다. + +```java +기존코드 + +String key = "Raphael"; +String value = "Jack Reacher 2"; +if (favouriteMovies.containsKey(key) && Objects.equals(favouriteMovies.get(key), value)) { + favouriteMovies.remove(key); + return true; +} +else { + return false; +} + +간결하게 구현 +favouriteMovies.remove(key, value); +``` +
+ +### 교체 패턴 + +맵 항목 바꾸는 데 사용할 수 있는 메서드 2개 추가됨 + +- replaceall : BiFunction을 적용한 결과 각 항목의 값을 교체한다. 이 메서드는 이전에 살펴본 List의 repalce와 비슷한 동작을 한다 +- Replace : 키가 존재하면 맵의 값을 바꾼다. 키가 특정 값으로 매핑되었을 때만 값을 교체하는 오버로드 버전도 있다 + +```java +favorutieMovies.replaceAll((friend, movie) -> movie.toUpperCase()); +``` + +replace는 한개의 맵에만 적용할 수 있다. 두 개이상의 맵에서 값을 합치거나 바꾸려 한다면 merge 메서드를 사용하면 된다 + +
+ +### 합침 + +putAll을 사용해서 합치면 된다 + +```java +Map everyone = new HashMap<>(family); +everyone.putAll(friends); // friends의 모든 항목을 everyone으로 복사 +``` + +중복된 키가 없다면 키, 값 으로 잘 합쳐서 동작할 것이다. 값을 좀더 유연하게 합치려 한다면 merge메서드를 이용할 수 있다. + +merge는 중복된 키를 어떻게 합칠지 결정하는 BiFunction을 인수로 받는다. 같은 키에 다른 값이 존재한다면 forEach와 merge메서드를 이용해 해결할 수 있다. + +```java +Map everyone = new HashMap<>(family); +friends.forEach((k, v) -> + everyone.merge(k, v, (movie1, movie2) -> movie1 + " & " + movie2)); + // 중복된 키가 있으면 두 값을 연결한다 +System.out.println(everyone); +// Outputs {Raphael=Star Wars, Cristina=James Bond & Matrix, Teo=Star Wars} +``` + +merge 메서드는 null과 관련한 복잡한 상황도 처리할 수 있다 + +지정된 키와 연관된 값이 없거나 값이 null이면[merge]는 키를 null이 아닌 값과 연결한다. 아니면 [merge]는 연결된 값을 주어진 매핑 함수의 [결과] 값으로 대치하거나 결과가 널이면 [항목]을 제거한다 + +
+ + +```java +merge를 이용한 초기화 검사 구현 +영화 시청 횟수 기록맵, 영화에 대한 정보 존재하는지 확인 + +Map moviesToCount = new HashMap<>(); +String movieName = "JamesBond"; +long count = moviesToCount.get(movieName); +if(count == null) { + moviesToCount.put(movieName, 1); +} else { + moviesToCount.put(moviename, count + 1); +} + +merge 사용하기 + +moviesToCount.merge(movieName, 1L, (key, count) -> count + 1L); + +여기서 merge의 두번째 인수는 1L 이다. 이 인수는 “키와 연관된 기존 값에 합쳐질 null이 아닌, 값 또는 값이 없거나 키에 null 값이 연관되어 있다면 이 값을 키와 연결” 하는데 사용한다. +처음 코드가 실행될때 1이 사용되고 이후로 1로 초기화 되고 BiFunction을 적용해 값이 증가된다 +``` + +
+ +### 개선된 ConcurrentHashMap + +`ConcurrentHashMap` 클래스는 동시성 친화적이며 최신 기술 반영한 HashMap 버전이다 `ConcurrentHashMap` 은 내부 자료구조의 특정 부분만 잠궈서 동시 추가하며, 갱신 작업을 허용한다. 따라서 동기화된 Hashtable 버전에 비해 읽기 쓰기 연산 능력이 월등하다(표준. HashMap은 비동기로 동작한다) + +
+ +### 리듀스와 검색 + +`ConcurrentHashMap`은 스트림과 비슷한 종류의 세 가지 연산, 네가지 연산 형태를 지원한다 + +연산 + +- forEach : 각 (키, 값) 쌍에 주어진 액션을 실행 +- reduce : 모든 (키, 값) 쌍을 제공된 리듀스 함수를 이용해 결과로 합친다 +- search : 널이 아닌 값을 반환할 때까지 각 (키, 값) 쌍에 함수를 적용한다 + +연산형태 + +- 키, 값으로 연산(forEach, reduce, search) +- 키로 연산(forEachkey, reduceKeys, searchKeys) +- 값으로 연산(forEachValue, reduceValues, searchValues) +- Map.Entry 객체로 연산(forEachEntry, reduceEntries, searchEntries) + +이들의 연산은 ConcurrentHashMap의 상태를 잠그지 않고 연산을 수행한다. 그래서 연산에 제공한 함수는 계산이 진행되는 동안 바뀔 수 있는 객체, 값, 순서 등에 의존하지 않아야 한다. + +여기서 연산에 병렬성 기준값을 지정해야 한다. 맵의 크기가 주어진 기준값보다 작으면 순차적으로 연산을 실행한다. 기준값을 1로 지정할시 공통 스레드 풀을 이용해 병렬성을 극대화 한다. Long.MAX_VALUE를 기준값으로 설정할시 한 개의 스레드로 연산을 실행한다. + +
+ +```java +reduceValues 메서드 이용한 맵의 최댓값 찾기 + +ConcurrentHashMap map = new ConcurrentHashMap<>(); + // 여러 키와 값을 포함하도록 갱신될 ConcurrentHashMap +long parallelismThreshold = 1; +Optional maxValue = + Optional.ofNullable(map.reduceValues(parallelismThreshold, Long::max)); +``` + +int, long, double 등의 기본값에는 전용 each reduce연산이 제공되어서 reduceValuesToInt, reduceKeysToLong 등을 이용하면 박싱 작업을 피해서 효율적으로 작업할 수 있다 + +
+ +### 계수 + +ConcurrentHashMap클래스는 맵의 매핑 개수 반환하는 mappingCount 메서드를 제공한다. 기존의 size메서드 대신 새 코드에서는 int를 반환하는 mappingCount메서드를 사용하는게 좋다. 그래야만 매핑의 개수가 int의 범위를 넘어서는 상황을 대처 할 수 있다(long형 반환함) + +
+ +### 집합뷰 + +ConcurrentHashMap클래스는 ConcurrentHashMap을 집합 뷰로 반환하는 KeySet 메서드 제공. + +맵 바꾸면 집합도 바뀌고 집합을 바꾸면 맵도 영향을 받는다. newKeySet 의 새 메서드 이용해 ConcurrentHashMap으로 유지되는 집합 만들수 있다 + +--- + +
+ +### 정리 + +- 자바 9는 원소 포함해 바꿀 수 없는 리스트, 집합, 맵을 쉽게 만드는 List.of, Set.of, Map.ofEntries등의 컬렉션 팩토리를 지원한다 +- 컬렉션 팩토리가 반환한 객체는 만들고 나서 바꿀 수 없다 +- List 인터페이스는 removeIf, replaceAll, sort 세 가지 디폴트 메서드를 지원한다 +- Set 인터페이스는 removeIf 디폴트 메서드를 지원한다 +- Map 인터페이스는 자주 쓰는 패턴과 버그를 방지 하도록 다양한 디폴트 메서드를 지원한다 +- ConcurrentHashMap은 Map에서 상속받은 새 디폴트 메서드를 지원함과 동시에 스레드 안전성도 제공 \ No newline at end of file diff --git "a/dongwon/image/List.of \355\212\271\354\247\225.png" "b/dongwon/image/List.of \355\212\271\354\247\225.png" new file mode 100644 index 0000000..e636018 Binary files /dev/null and "b/dongwon/image/List.of \355\212\271\354\247\225.png" differ diff --git "a/dongwon/image/UnsupportedOperationException \354\230\210\354\231\270\353\260\234\354\203\235.png" "b/dongwon/image/UnsupportedOperationException \354\230\210\354\231\270\353\260\234\354\203\235.png" new file mode 100644 index 0000000..1628ce0 Binary files /dev/null and "b/dongwon/image/UnsupportedOperationException \354\230\210\354\231\270\353\260\234\354\203\235.png" differ diff --git "a/dongwon/image/\354\266\224\354\203\201\354\240\201 \354\241\260\352\261\264\354\234\274\353\241\234 \355\225\204\355\204\260\353\247\201.png" "b/dongwon/image/\354\266\224\354\203\201\354\240\201 \354\241\260\352\261\264\354\234\274\353\241\234 \355\225\204\355\204\260\353\247\201.png" new file mode 100644 index 0000000..f31104d Binary files /dev/null and "b/dongwon/image/\354\266\224\354\203\201\354\240\201 \354\241\260\352\261\264\354\234\274\353\241\234 \355\225\204\355\204\260\353\247\201.png" differ diff --git "a/dongwon/image/\355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\234\240\354\227\260\354\204\261.png" "b/dongwon/image/\355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\234\240\354\227\260\354\204\261.png" new file mode 100644 index 0000000..0e53e77 Binary files /dev/null and "b/dongwon/image/\355\214\214\353\235\274\353\257\270\355\204\260\355\231\224 \354\234\240\354\227\260\354\204\261.png" differ