diff --git a/mission/chapter04/mission.md b/mission/chapter04/mission.md new file mode 100644 index 0000000..70cd622 --- /dev/null +++ b/mission/chapter04/mission.md @@ -0,0 +1,199 @@ +## 1. 함수형 인터페이스 + +**함수를 위한 인터페이스**로, 이 인터페이스를 구현한 클래스는 **하나의 함수처럼 동작**하게 된다.
+이는 static 메소드와 default 메소드가 아닌 추상 메소드를 딱 하나만 가져야 한다.
+함수형 인터페이스는 아래와 같이 사용할 수 있다. + +```java +@FunctionalInterface +public interface Function{ + +} +``` + +이 경우는 T라는 타입을 입력 받아 R이라는 타입을 출력한다.
+즉, `Function`의 경우에는 String을 입력 받아 Integer를 출력한다고 볼 수 있다. + +### 함수형 인터페이스 종류 + +| 함수형 인터페이스 이름 | 설명 | 추상 메소드 | +|:---------------------:|:------------------------------:|:-------------------:| +| Function | 타입 T를 받아 타입 R을 반환 | R apply(T t) | +| Consumer | 타입 T를 받아 소비 | void accept(T t) | +| Supplier | 타입 T를 반환 | T get() | +| Predicate | 타입 T를 받아 boolean을 반환 | boolean test(T t) | +| BiFunction | 타입 T, U를 받아 타입 R을 반환 | R apply(T t, U u) | +| UnaryOperator | 타입 T를 받아 타입 T를 반환 | - | +| BinaryOperator | 동일한 타입의 입력값 두 개를 받아 동일 타입 반환 | - | + +크게 4개만 살펴보자. + +#### Function + +주로 T 타입의 객체를 받아 다른 형태 R로 변환하는데 사용된다.
+이 메소드를 호출할 때 생성자 참조 방식을 이용해서 정수 값을 문자열로 변환하고 변환된 문자열을 출력한다. +```java +public static void print(Function function, int value) { + System.out.println(function.apply(value)); +} + +// 메소드 호출 +print(Object::toString, 5); +// 출력 : 5 +``` + +#### Consumer + +한 개의 입력을 받아서 결과를 반환하지 않는 함수를 정의한다.
+주로 입력값을 이용한 연산이나 출력 등의 동작에 사용된다. +```java +public static void printList(Consumer consumer, List list) { + for (String item : list) { + consumer.accept(item); + } +} + +// 메소드 호출 +printList(System.out::println, Arrays.asList("Apple", "Banana", "Cherry")); +// 출력: +// Apple +// Banana +// Cherry +``` + +#### Supplier + +입력 없이 결과를 반환하는 함수를 정의한다.
+주로 파라미터 없이 특정 결과를 생성하는데 사용된다. +```java +public static void print(Supplier supplier) { + System.out.println(supplier.get()); +} + +// 메소드 호출 +print(() -> "안녕하세요!"); +// 출력: 안녕하세요! +``` + +위의 경우, 람다식을 이용해 문자열을 출력한다. + +#### Predicate + +한 개의 입력을 받아서 boolean 결과를 반환하는 함수를 정의한다.
+주로 객체를 조건에 따른 필터링이 필요할 때 사용된다. +```java +public static void print(Predicate predicate, int value) { + if (predicate.test(value)) { + System.out.println(value); + } +} + +// 메서드 호출 +print(x -> x > 5, 10); +// 출력: 10 +``` +위의 경우 역시, 람다식을 이용해 조건을 만족하면 value 값이 출력된다. + +> AOP처럼 공통적인 기능을 한 곳에서 관리하고, 핵심 기능만을 분리할 수는 없는지 생각하다가,
**함수형 인터페이스를 사용해 함수로 추상화하여 재사용가능한 코드를 만들 수 있다!** + +### 문제풀이 + +```java +import java.util.function.Function; + +// Function 인터페이스를 상속받은 클래스 +class ExFunction implements Function { + @Override + public String apply(Integer integer) { + return String.valueOf(integer); + } +} + +public class Main { + public static void main(String[] args) { + // 1. 익명클래스 정의 + Function function1 = new Function() { + @Override + public String apply(Integer integer) { + return String.valueOf(integer); + } + }; + System.out.println(function1.apply(111)); // 출력: "111" + + // 2. 클래스 파일을 만들어 상속받아서 정의 + ExFunction function2 = new ExFunction(); + System.out.println(function2.apply(222)); // 출력: "222" + + // 3. 람다식으로 정의 + Function function3 = integer -> String.valueOf(integer); + System.out.println(function3.apply(333)); // 출력: "333" + + // 4. 메서드 참조로 정의 + Function function4 = String::valueOf; + System.out.println(function4.apply(444)); // 출력: "444" + } +} +``` + +### 질문 + +Q. 람다식을 사용해서 함수형 인터페이스를 어떻게 간결하게 표현하고 람다식을 사용했을 때의 장점은 무엇이 있나요? +
+A. 익명 클래스를 사용한 경우를 예로 들면, 전통적인 방법인 오버라이드를 할 필요 없이 `integer -> String.valueOf(integer)`를 통해 간결하게 나타낼 수 있습니다. +
또한, 불필요한 클래스 정의와 메소드 오버라이드를 생략할 수 있어서 코드가 간결해지고 가독성이 올라갑니다. + +
+
+ +## 2. stream api + +데이터를 추상화하고, 처리하는데 자주 사용되는 함수들을 정의한다.
+여기서 **데이터를 추상화**한다는 것은? **데이터의 종류에 상관없이 같은 방식으로 데이터를 처리**할 수 있음을 의미한다. + +#### 특징 + +1. 원본의 데이터를 변경하지 않는다. +2. Stream은 일회용이기 때문에 한 번 사용이 끝나면 재사용이 불가능하다. +3. 내부 반복으로 작업을 처리한다. 따라서 코드가 간결해진다. + +#### 과정 +![img.png](img.png) +1. Stream 생성 +2. 중간 연산 +3. 최종 연산 + +#### 장점 + +- 훨씬 간결해지고 명료해져서 소스코드의 가독성이 좋아진다. +- 코드를 직접 개발한 개발자가 아니어도 코드의 구조를 한눈에 알아보기 쉬워 유지보수 및 인수인계 시에도 어려움없이 작업이 가능하다. +- 병렬처리를 지원하기에 대량의 데이터를 빠르고 쉽게 처리할 수 있다. + +### 문제풀이 + +```java +import java.util.Arrays; + +public class ExStream { + public static void main(String[] args) { + int[] arr = {1,2,3,4,5,6,7,8,9,10}; + int[] doubledArr = Arrays.stream(arr) + .map(x->x*2) + .toArray(); + + System.out.println("원본 배열 : " + Arrays.toString(arr)); + System.out.println("2배 배열 : " + Arrays.toString(doubledArr)); + + String[] evenArr = Arrays.stream(arr) + .filter(x->x%2==0) + .map(x->x + "is even number") + .toArray(String[]::new); + + System.out.println("짝수 배열 : " + Arrays.toString(evenArr)); + } +} +``` + +### 질문 + +Q. 스트림의 요소들을 변환하거나 필터링하는 등의 중간 연산은 그때 그떄 실행되는가?
+A. 아니다. 중간 연산은 실행되고 있지 않다가 최종 연산을 만나게 되면, 그때 중간 연산이 실제로 수행된다. \ No newline at end of file