diff --git a/keyword/chapter04/img.png b/keyword/chapter04/img.png new file mode 100644 index 0000000..ab4b9f7 Binary files /dev/null and b/keyword/chapter04/img.png differ diff --git a/keyword/chapter04/img_1.png b/keyword/chapter04/img_1.png new file mode 100644 index 0000000..8847a6e Binary files /dev/null and b/keyword/chapter04/img_1.png differ diff --git a/keyword/chapter04/img_2.png b/keyword/chapter04/img_2.png new file mode 100644 index 0000000..d2337e6 Binary files /dev/null and b/keyword/chapter04/img_2.png differ diff --git a/keyword/chapter04/img_3.png b/keyword/chapter04/img_3.png new file mode 100644 index 0000000..75342f0 Binary files /dev/null and b/keyword/chapter04/img_3.png differ diff --git a/keyword/chapter04/img_4.png b/keyword/chapter04/img_4.png new file mode 100644 index 0000000..75342f0 Binary files /dev/null and b/keyword/chapter04/img_4.png differ diff --git a/keyword/chapter04/keyword.md b/keyword/chapter04/keyword.md new file mode 100644 index 0000000..21e42c3 --- /dev/null +++ b/keyword/chapter04/keyword.md @@ -0,0 +1,322 @@ +## 1. DI + +개발 상황에서 객체들은 서로 **의존**되어있을 수밖에 없다.
+ +### 그럼 DI란 무엇인가? + +### DI : Depedency Injection. (의존관계 주입) + +외부로부터 **의존성을 주입**하는 것을 말한다.
+여기서 **의존성**이란? `의존대상 B가 변하면, 그것이 A에 영향을 미친다.` + +우리가 의존 관계를 공부하다 보면, **강한 결합**과 **약한 결합**에 대해 접하게 된다.
+이것들은 **유지보수성**에 직결되는데 어떻게 직결되는 것일까? + +### 강한 결합이란? + +어떠한 객체가 다른 객체에 강한 의존성을 가지고 있음을 의미한다. +아래 예시로 이해해보자. + +```java +public class Person { + private Chicken chicken; + + public Person(){ + this.chicken = new Chicken(); + } + + public void startEat() { + chicken.eat(); + } +} + +public class Chicken { + public void eat() { + System.out.println("치킨을 먹는다."); + } +} +``` + +Person 클래스의 멤버 변수 타입으로 Chicken 클래스가 존재한다.
+이때, +1. Chicken 클래스가 없으면 Person 클래스를 정의할 수 없으며
+2. Chicken 클래스를 Pizza 클래스로 바꾸게 되면 Person 클래스의 코드 대부분을 수정해야 한다. + +`즉, Person 클래스가 Chicken 클래스에 의존한다!` + +이는 유지보수 면에서 좋지 않다. + +### 그럼 약한 결합이란? + +**인터페이스**를 사용하는 것이다. + +```java +public interface Food { + void eat(); +} + +public class Chicken implements Food { + @java.lang.Override + public void eat() { + System.out.println("치킨을 먹는다."); + } +} + +public class Pizza implements Food { + @java.lang.Override + public void eat() { + System.out.println("피자를 먹는다."); + } +} + +public class Person { + private Food food; + + public Person(Food food){ + this.food = food; + } + + public void startEat(){ + food.eat(); + } +} +``` + +이처럼 Food 인터페이스를 사용하면, Chicken과 Pizza object를 모두 Food 타입에 대입할 수 있다.
+즉, Person 클래스 내부적으로 코드의 변경이 일어날 필요가 없다.
+이는 유지보수 면에서 매우 좋다. + +### 그럼 도대체 DI는 어떻게 구현되나? + +1. 생성자 주입 ******권장 +```java +@Service +public class UMC7thMember { + private final UMCRepository umcRepository; + + @Autowired + public UMC7thMember(final UMCRepository umcRepository){ + this.umcRepository = umcRepository; + } +} +``` + +**객체가 생성될 때 필요한 의존성을 설정**하는 방식이다.
+이는 **객체의 불변성을 보장**해주기에 생성자 주입이 권장되는 이유이다. + + +2. setter 주입 + +```java +@Service +public class UMC7thMember { + private final UMCRepository umcRepository; + + @Autowired + public setUMC7thMember(final UMCRepository umcRepository){ + this.umcRepository = umcRepository; + } +} +``` + +**생성자를 통해 의존성을 설정**하는 방식이다.
+이는 런타임에 의존성을 주입하기 때문에, 의존성이 없더라도 객체 생성이 가능하다.
+그러나 의존성을 주입받지 않은 상태에서 작동할 수 있으므로, **NullPointException 에러**가 발생할 수 있다. + + +3. 필드 주입 + +```java +@Service +public class UMC7thMember { + @Autowired + private final UMCRepository umcRepository; +} +``` + +**필드에 직접 의존성을 설정**하는 방식이다.
+이것 역시 런타임에 의존성을 주입하기 때문에, 의존성이 없더라도 객체 생성이 가능하다.
+코드는 깔끔하지만, **명시적으로 드러나는 의존성이 없기에 의존성 구조를 이해하기 어렵다.** + +
+
+ +## 2. loC + +아래 예시는 일반적으로 자바에서 객체를 생성하는 방식이다. +```java +UMC7thMember umc7thMember = new UMC7thMember(); +``` +즉, 객체를 생성하고 사용하는 작업을 개발자가 직접 제어하는 것이다. + +### 개발자가 직접 객체를 생성하지 않고, 객체의 생성 및 관리를 외부에 위임한다면? + +이것이 바로 IoC, 제어의 역전이다. + +### IoC : Inversion of Control. (제어의 역전) + +사용할 객체를 직접 생성하지 않고, 객체의 생성과 관리를 **외부에 위임**하는 것이다.
+여기서 **외부**란? **"스프링 컨테이너"** 를 말한다.
+이렇게 객체의 관리를 컨테이너에 맡겨 제어권이 넘어간 것을 **제어의 역전**이라고 부른다. + +### 그렇다면 왜 IoC를 사용할까? + +1. 객체 간 결합도를 낮춘다. +2. 유연한 코드 작성이 가능하다. +3. 가독성이 좋다. +4. 코드의 중복을 방지한다. +5. 유지보수가 용이하다. + + +#### IoC를 통해 DI와 AOP가 가능해지고, 우리는 비즈니스 로직에만 집중할 수 있다. +#### 이러한 IoC를 통해서 달성할 수 있는 것이 바로 POJO이다. + +### POJO란 무엇일까? + +### POJO : Plain Old Java Object. (순수한 오래된 자바 객체) + +**객체 지향적인 원리에 충실**하면서 **환경과 기술에 종속되지 않고**, **필요에 따라 재활용**될 수 있도록 설계된 객체이다.
+이를 이용하여 프로그래밍 코드를 작성하는 것이 **POJO 프로그래밍**이다. + +### POJO 프로그래밍이라고 하기 위해 지켜야하는 규칙 + +1. Java나 Java의 스펙에 정의된 것 이외에는 다른 기술이나 규약에 얽매이지 않아야 한다. +2. 특정 환경에 종속적이지 않아야 한다. 즉, 독립적이어야 한다는 소리이다. + +### POJO 프로그래밍을 해야하는 이유가 있을까? + +1. 특정 환경이나 기술에 종속적이지 않으면 재사용이 가능하고 확장 가능한 유연한 코드 작성이 가능하다. +2. 코드가 간결해지며 디버깅에도 유리하다. +3. 객체지향적인 설계를 제한없이 적용할 수 있다. + + +
+
+ + +## 3. 프레임워크와 API의 차이 + +### 프레임워크란 무엇일까? + +**어떠한 목적을 쉽게 달성할 수 있도록 해당 목적과 관련된 코드의 뼈대를 미리 만들어둔 것**과 같다.
+우리는 프레임워크에 의존하여 개발하고, 프레임워크가 정의한 규칙을 준수해야 한다.
+즉, **프레임이라는 뼈대 위에서 프로그램을 개발**하는 것이다. + +### API란 무엇일까? + +### API : Application Programming Interface + +**2개 이상의 소프트웨어 컴포넌트 사이에서 상호작용할 수 있도록 정의된 인터페이스**를 말한다.
+즉, 응용 프로그램을 만드는데 필요한 **연결 장치 또는 매개체**라고 생각하면 된다.
+API가 필요한 이유는? 개발을 할 때 모든 것을 혼자 개발할 수는 없기 때문에. + +### 프레임워크 vs API + +한마디로 정의해보자면, + +### 프레임워크는 프레임워크가 나를 호출하는 방식이고, +### API는 내가 API를 호출하는 방식이다! + +
+
+ +## 4. AOP + +### AOP : Aspect Oriented Programming (관점 지향 프로그래밍) + +부가 기능을 핵심 기능에서 분리해 한 곳으로 관리하도록 하고, 이 부가 기능을 어디에 적용할지 선택하는 기능을 합한 하나의 모듈이다.
+즉, 흩어진 관심사를 Aspect로 모듈화하고 핵심적인 비즈니스 로직에서 분리하여 재사용하겠다는 것이 AOP의 취지이다. + +### AOP 용어 정리 + +![img_2.png](img_2.png) +![img_4.png](img_4.png) + +### AOP의 적용 방식 + +- 컴파일 시점 +- 클래스 로딩 시점 +- 런타임 시점 (프록시 사용) + +컴파일 시점과 클래스 로딩 시점에는 AspectJ 프레임워크를 직접 사용해야 해서 번거롭다.
+따라서, 주로 런타임 시점 적용 방식을 사용하는 스프링 AOP를 사용한다. + +스프링 AOP를 적용하기 위해서는 @Aspect 애노테이션을 작성해야 한다. +아래 예시를 참고하자. +```java +@Aspect +public class LogTraceAspect { + @Around("execution(* hello.proxy.app..*(..))") + public Object execute(ProceedingJoinPoint joinPoint) { + ... + } +} +``` + +** 스프링 AOP 적용 시에는 private, final 메소드는 AOP 적용이 불가하다. ** + +
+
+ +## 5. 서블릿 + +### Servlet + +웹 애플리케이션에서 **클라이언트의 요청을 처리하고, 그에 대한 응답을 생성**하는 중요한 구성 요소이다.
+즉, **HTTP 요청을 처리**하는 역할을 한다. + +아래 사진을 통해 서블릿에 대해 자세히 알아보자. +![img_1.png](img_1.png) + +1. 클라이언트가 URL을 입력하면 HTTP Request가 Servlet Container로 전송한다. +2. 전송받은 Servlet Container는 HttpServletRequest, HttpServletResponse 객체를 생성한다. +3. web.xml을 기반으로 사용자가 요청한 URL이 어느 Servlet에 대한 요청인지 찾는다. +4. 해당 Servlet에서 service() 메소드를 호출하고, 클라이언트의 GET, POST 여부에 따라 doGet() 또는 doPost()를 호출한다. +5. 이 메소드들은 동적 페이지를 생성한 후, HttpServletResponse 객체에 응답을 보낸다. +6. 응답이 끝나면 HttpServletRequest, HttpServletResponse는 소멸시킨다. + +### 그럼 여기서 Servlet Container는 무엇인가? + +Servlet을 관리해주는 컨테이너이다.
+이는 클라이언트의 요청을 받아주고 응답할 수 있도록 웹 서버와 소켓으로 통신한다. + +#### 추가적인 역할 + +1. Servlet 생명주기 관리 +2. 멀티스레드 지원 및 관리 +3. 선언적인 보안 관리 + +### Servlet 중 가장 중요한 것은? + +### DispatcherServlet. + +모든 HTTP protocol로 들어오는 요청을 가장 먼저 받아 적합한 컨트롤러에 위임해주는 front controller이다. + +#### 장점 + +과거에는 모든 Servlet을 URL 매핑을 위해 web.xml에 모두 등록해주어야 했지만, DispatcherServlet이 해당 애플리케이션에 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리하면서 상당히 편리하게 이용 가능해졌다.
+즉, 우리는 컨트롤러를 구현해두기만 하면 DispatcherServlet이 알아서 적합한 컨트롤러로 위임을 해주는 구조이다. + +#### 동작 과정 + +아래 코드를 참고하며 살펴보자. +```java +@Controller +public class MyController { + @GetMapping("/umc") + public String hello(Model model){ + model.addAttribute("message", "UMC Spring Fighting!"); + return "greeting"; + } +} +``` + +1. 클라이언트가 '/umc' URL로 Http Request가 들어오면, 이는 DispatcherServlet으로 전달된다. +2. DispatcherServlet은 해당 요청을 처리할 수 있는 적합한 controller를 찾는다. >> **Handler Mapping** +3. controller는 요청 처리를 위해 hello() 메소드를 호출하고, 맞는 비즈니스 로직을 실행한 후 결과를 반환한다. +4. 이 결과는 **ViewResolver**를 통해 적절한 View로 렌더링하고, Http Response로 HTML이나 JSON의 형태로 전달된다. + + + + + diff --git a/keyword/chapter05/img.png b/keyword/chapter05/img.png new file mode 100644 index 0000000..bb71d7f Binary files /dev/null and b/keyword/chapter05/img.png differ diff --git a/keyword/chapter05/keyword.md b/keyword/chapter05/keyword.md new file mode 100644 index 0000000..d79104d --- /dev/null +++ b/keyword/chapter05/keyword.md @@ -0,0 +1,211 @@ +## 1. Domain + +**해결하고자 하는 문제의 영역**을 도메인이라고 한다.
+쉽게, **요구사항이나 문제 영역** 등을 예시로 들 수 있다.
+도메인은 주로 애플리케이션의 핵심 비즈니스 로직이나 데이터를 표현하는 객체들을 의미하며, '도메인 객체' 또는 '도메인 모델'이라고도 불린다. + +### 도메인 객체의 특징 + +1. 비즈니스 로직을 캡슐화한다. +2. 상태(변수)와 동작(메서드)을 함께 가진다. +3. 데이터베이스의 테이블과 매핑되어 ORM 프레임워크를 통해 데이터를 저장하거나 불러올 수 있다.
+이는 엔티티로 정의되어 데이터베이스와 직접 연동이 가능하다. + +### 도메인 객체와 관련된 개념 + +1. **entity** : 고유한 식별자를 갖는 도메인 객체이다. 보통 데이터베이스의 테이블에 매핑된다. +2. **value object** : 고유 식별자가 필요하지 않고, 다른 객체에 포함되는 데이터 객체이다. +3. **aggregate** : 비즈니스적으로 밀접하게 관련된 entity와 value의 모음이다. + +그렇다면, **웹 애플리케이션 계층 구조에서의 domain**은 무엇일까? + +![img.png](img.png) + +**Domain** : 엔티티 선언을 통해 DB에 저장되는 객체들을 구현한다.
+즉, 테이블의 각 column들이 하나의 도메인이라고 볼 수 있다. + +예시 코드를 봐보자. + +```java +public class Member { + private Long id; + private String name; + + public Long getId() { + return id; + } + + pbulic void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} +``` + +
+
+ +## 2. 양방향 매핑 + +테이블 간의 연관 관계 매핑에는 단방향 매핑과 양방향 매핑이 있다.
+양방향 매핑을 공부하기 전에 단방향 매핑에 대해 간단히 짚고 넘어가자. + +### 단방향 매핑이란? + +**연관 관계 주인에게만 연관 관계를 주입**하는 것이다.
+그렇다면 여기서 연관 관계 주인이란 무엇일까? + +### 연관 관계 주인 + +실제 데이터베이스에서 외래키를 가지는 엔티티, 즉 테이블을 의미한다. + +### 연관 관계 주인 설정은 어떻게? + +<단방향 매핑의 경우> +- **1:N**의 경우 : N에 해당하는 테이블이 외래키를 가지며 N에 해당하는 엔티티를 주인으로 설정한다. +- **1:1**의 경우 : 둘 중 하나만 외래키를 가지면 되기에 원하는 엔티티를 주인으로 설정한다. + +이때, N:1에서 N에 해당하는 엔티티가 1에 해당하는 엔티티와 연관 관계를 매핑할 때 사용하는 어노테이션은 무엇일까? + +### @ManyToOne과 실제 데이터베이스에서 해당 외래키의 이름을 설정하는 @JoinColumn. + +
+ +그렇다면 이제 양방향 매핑에 대해 알아보자. + +### 양방향 매핑이란? + +**연관 관계 주인이 아닌 엔티티에게도 연관 관계를 주입**하는 것이다. + +<양방향 매핑의 경우> +- **1:N**의 경우 : 1에 해당하는 엔티티에게 설정한다. + +이때 사용하는 어노테이션은 + +### @OneToMany(mappedBy=" ", -) 이다. + +이는 1에 해당하는 엔티티가 N에 해당하는 엔티티와 관계가 있음을 명시한다.
+N에 해당하는 엔티티에서 ManyToOne이 설정된 멤버변수를 mappedBy한다. + +### 양방향 매핑의 장점은 뭘까? + +- 양방향 매핑으로 인한 객체 그래프 탐색이 프로그래밍을 굉장히 편리하게 해준다. +- cascade의 설정이 가능하다. + +### cascade란? + +**참조 대상인 테이블의 칼럼이 삭제될 때 같이 삭제되거나 변경이 될 때 같이 변경이 되는 기능**이다.
+이는 JPA에서 연관관계의 주인이 아닌, 참조의 대상이 되는 엔티티에 설정하여 사용 가능하다.
+> 즉, 단방향 매핑 만으로는 cascade 설정을 하는 것에 문제가 있다는 것이다. + +단, 양방향 매핑을 할 경우 연관 관계 편의 메서드가 필요하다. + +### 연관 관계 편의 메서드란? + +연관 관계를 설정하는 메서드이다.
+예시로 이해해보자. + +```java +// 일반적인 코드 +member.setTeam(team); +team.getMembers().add(member); + +// 연관 관계 편의 메서드를 사용한 코드 +public void setTeam(Team team){ + this.team = team; + team.getMembers().add(this); +} +``` + +양방향 매핑에서는 따로따로 사용하다가 한 줄을 넣지 않아 둘 중 하나만 호출이 되어 양방향이 깨지는 상황을 대비하여 두 코드를 하나의 것처럼 사용하는 것이 안전하다. + +### 양방향 매핑의 문제점 + +1. **순환 참조 문제** +- 양방향 매핑은 순환 구조를 만들어, 객체가 서로를 참조하는 무한 루프에 빠질 수 있다. +- 이로 인해 예외가 발생할 수 있으며, 이 문제를 방지하려면 별도의 로직이나 설정이 필요하다. +2. **데이터 일관성 문제** +- 양방향 매핑은 양쪽의 데이터가 항상 일치해야 한다. +- 한 쪽의 엔티티에서 수정하면 다른 쪽에서도 동일한 변경이 이루어져야 하므로, 이를 위한 추가적인 코드가 필요하다. +- 이 과정을 처리하지 않는다면 데이터 불일치가 발생할 수 있다. + +
+
+ +## 3. N + 1 문제 + +ORM 기술에서 특정 객체를 대상으로 수행한 쿼리가 해당 객체가 가지고 있는 **연관관계 또한 조회하게 되면서 N번의 추가적인 쿼리가 발생**하는 문제를 말한다. + +### 왜 발생할까? + +**관계형 데이터베이스와 객체지향 언어간의 패러다임 차이**로 인해 발생한다.
+객체는 연관관계를 통해 레퍼런스를 가지고 있으면 언제든지 메모리 내에서 Random Access를 통해 연관 객체에 접근할 수 있지만, RDB의 경우 Select 쿼리를 통해서만 조회할 수 있기 때문. + +### N+1 문제를 Eager Loading으로 해결할 수 있을까? + +부분적으로 해결해줄 수는 있겠지만, **권장하지 않는다.**
+그럼 여기서 말하는 Eager Loading은 뭘까? + +### Eager Loading + +**연관된 entity 객체를 한 번에 조회하도록 하는 기능**이다.
+이를 사용한다면, 어떤 entity 연관 관계 범위까지 join 쿼리로 조회해올지 예상하기 어려워 필요없는 데이터까지 로딩하여 비효율적일 수 있다.
+ +그럼 N+1 문제를 해결하기 위한 방법에는 뭐가 있을까? + +### Fetch Join + Lazy Loading + +Root entity에 대해서 조회할 때 Lazy Loading으로 설정되어 있는 연관 관계를 join 쿼리를 발생시켜 한 번에 조회할 수 있는 기능이다.
+ +#### Fetch Join vs 일반 Join + +Fetch Join은 ORM에서의 사용을 전제로 DB Schema를 entity로 자동 변환 해주고 영속성 컨텍스트에 영속화해준다.
+ +- **Fetch Join으로 조회** : 연관 관계는 영속성 컨텍스트 1차캐시에 저장되어 다시 entity 그래프를 탐색하더라도 조회 쿼리가 수행되지 않는다. +- **일반 Join으로 조회** : 단순히 데이터를 조회하는 개념으로 영속성 컨텍스트나 entity와는 무관하다. + +따라서, Fetch Join을 활용하면 ORM 활용이 가능하여 RDB와의 패러다임 차이를 줄일 수 있다. + +#### Collection 연관 관계 Fetch Join 시 주의사항 + +Collection에 대해서 Fetch Join을 할 경우, 1:N 관계이기에 1쪽의 데이터는 중복된 상태로 조회된다.
+이를 방지하려면? distinct 절을 활용해야한다. >> 중복되는 entity 제거 가능. + +### Lazy Loading + +**데이터가 실제로 필요할 때까지 로드하지 않고 대기하는 기법**이다.
+즉, 객체나 리소스를 미리 로드하지 않고, 사용자가 요청하거나 액세스할 때 로드하는 방식이다. +> 데이터베이스에서 관련 entity를 가져올 때, 연관된 모든 데이터를 처음부터 다 가져오는 대신 실제로 접근할 때까지 대기. + +#### 장점 + +1. 사용하지 않는 데이터를 메모리에 로드하지 않으므로, 메모리를 절약할 수 있다. +2. 필요한 데이터만 가져오므로, 초기 응답 시간이 줄어들 수 있다. +3. 트랜잭션 처리 시간을 최적화할 수 있다. + +#### 단점 + +1. Lazy Loading을 부적절하게 사용하면 N+1 문제가 발생할 수 있다. +2. 코드가 복잡해질 수 있으며, 어느 시점에 데이터가 로드되는지 예측하기 어렵다. +3. 트랜잭션 경계를 벗어나 데이터를 로드하려고 하면 오류가 발생할 수 있다. + +### default_batch_fetch_size, @BatchSize + +이러한 Lazy Loading 시 프록시 객체를 조회할 때 where in 절로 묶어서 한 번에 조회할 수 있게 해주는 옵션이다.
+yml에 전역 옵션으로 적용할 수도 있으며, @BatchSize를 통해 연관 관계 BatchSize를 다르게 적용할 수도 있다. + +### Fetch Join vs BatchSize + +Collection Fetch Join 시 paging 문제나 1개까지만 Fetch Join을 할 수 있다는 한계들을 BatchSize는 해결할 수 있다. + +### 만약, 많은 컬럼 중 특정 컬럼만 조회해야 하는 경우나 커버링 인덱스를 활용하고 싶은 경우 데이터 전송량을 줄이고 싶다면? + +일반 Join을 하고 Projection하여 Dto로 변환하여 조회하는 방법이 있다.
+이 방식을 사용하는 쿼리는 DAO를 분리하는 것이 좋다. \ No newline at end of file diff --git a/keyword/chapter06/keyword.md b/keyword/chapter06/keyword.md new file mode 100644 index 0000000..e69de29 diff --git a/mission/chapter04/img.png b/mission/chapter04/img.png new file mode 100644 index 0000000..a320a6b Binary files /dev/null and b/mission/chapter04/img.png differ 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