람다
람다란?
메서드로 전달할 수 있는 익명 함수를 단순화한 것이라고 할 수 있다.
- 익명
- 보통 메서드와 달리 이름이 없어 익명
- 함수
- 람다는 메서드처럼 특정 '클래스'에 종속되지 않아 함수라고 칭한다. (but, 메서드처럼 파라미터 리스트, 바디, 반환 형식, 가능한 예외 리스트를 포함)
- 전달
- 람다 표현식을 메서드 인수로 전달하거나 변수로 저장할 수 있다.
- 간결성
- 익명 클래스처럼 자질구레한 코드를 구현할 필요가 없다.
람다를 사용하여 코드를 전달하는 과정에서 판에 박힌 코드를 구현할 필요가 없게 만들어준다. (동작 파라미터 형식의 코드를 더 쉽게 구현할 수 있다) 결과적으로 코드를 더 간결하고 유연하게 만들어준다.
ex)
//기존
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
}
//Comparetor 객체를 byWeight란 이름으로 만들고 Comparator가 필요한 위치에 삽입하는 방식
//람다
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeigth().compareTo(a2.getWeight());
기본적인 람다 표현식
(Apple a1 . Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
(람다 파라미터) -> 람다 바디;
- 람다 파라미터 리스트
- Comparator의 compare 메서드 파라미터 (사과 두 개)
- 화살표
- 화살표 (→) 는 람다 파라미터 리스트와 바디를 구분한다.
- 람다 바디그래서 정확히 람다가 어디서 사용되는가?함수형 인터페이스란 정확히 하나의 추상 메서드를 지정하는 인터페이스이다.
인터페이스는 dafault 메서드를 포함할 수 있다. default 메서드는 구현이 이미 되어있는 메서드이다. 이미 구현을 했으니 해당 인터페이스를 구현하는 클래스는 추가된 메서드의 구현을 할 필요가 없다.public interface Predicate<T>{ boolean test (T t); }
@FunctionalInterface public interface Predicate<T>{ boolean test (T t); default void sort(Comparator<? super E> c){ Collections.sort(this,c); } } 이런 식으로 굳이 단 하나의 추상 메서드만 존재하지만 이미 구현된 default 메서드는 인터페이스에 마음껏 존재하여도 된다.
- Quiz
//1 public interface Adder { int add(int a, int b); } public interface SmartAdder extends Adder { int add(double a, dobule b); } public interface Nothing { }
//1 public interface Adder { int add(int a, int b); } public interface SmartAdder extends Adder { int add(double a, dobule b); } public interface Nothing { }
- 다음 인터페이스중 함수형 인터페이스는?
- 정답SmartAdder는 두 추상 add 메서드(하나는 상속받음)를 포함하므로 아니다.함수형 인터페이스로 뭘 할 수 있을까?```java
- 람다 표현식으로 함수형 인터페이스의 추상메서드 구현을 직접 전달할 수 있으므로, 전체 표현식을 함수형 인터페이스의 인스턴스로 취급할 수 있다.
- Nothing은 없으므로 아니다.
- Adder만 함수형 인터페이스이다.
- 람다 사용
Runnable r1 = () -> System.out.println("Hello World 1"); - 익명 클래스 사용
Runnable r2 = new Runnable() {
public void run() {}
} System.out.println("Hello World 2");
- 람다 직접 전달process(r1);
process(r2); - public static void process(Runnable r){
r.run();
}
- Quiz
> 함수 디스크립터 함수형 인터페이스의 추상 메서드 시그니처는 람다 표현식의 시그니처를 가리킨다. 람다 표현식의 시그니처를 서술하는 메서드를 **함수 디스크립터**라고 부른다. 즉, 위의 Runnable 인터페이스의 추상메서드 run은 인자가 없고, return값도 없다는 것이 함수 디스크립터이다. **() → void**
- 자바 8에서는 대표적으로 List 인터페이스의 sort, Collection 메서드의 stream 메서드가 있다.
- 추상 메서드란
선언만 하고 내용은 선언하지 않은 메서드이다. - 함수형 인터페이스
- 두 사과의 무게를 비교한다. 람다의 반환값에 해당하는 표현식이다.
왜 함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있을까?
언어 설계자들이 자바에 함수형식을 추가하는 방법도 대안으로 고려해봤는데, 언어를 더 복잡하게 만들지 않는 지금 방법을 선택했다. 기존의 자바 프로그래머가 하나의 추상 메서드를 가지는 인터페이스에 이미 익숙하다는 점을 고려했다.
> 실용적 사용 정리
### 실행 어라운드 패턴
자원 처리(예를 들면 데이터베이스의 파일 처리) 에 사용하는 순환 패턴은 자원을 열고, 처리한 다음에, 자원을 닫는 순서로 이루어진다.
즉, 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태이다.
이를 동작 파라미터전달로(람다) 간소화할 수 있다.
```jsx
// 1. 동작 파라미터화를 기억하라.
//자바 7에 추가된 try-with-resources라 자원을 닫아줄 필요가 없다.
public static String processFile() throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
return br.readLine();
}
}
// 2. 함수형 인터페이스를 이용해서 동작 전달
public interface BufferedReaderProcessor {
String process(BufferedReader b) throws IOException;
}
public static String processFile(BufferedReaderProcessor p) throws IOException {
...
}
// 3. 동작 실행
public static String processFile(BufferedReaderProcessor p) throws IOException {
try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
return p.process(br);
}
```
- Quiz BufferedReaderProcessor 의 함수 디스크립션을 말해보시오
BufferedReaderProcessor의 추상메서드는 process이며
(BufferedReader) → String으로 표현할 수 있다.
- Quiz 라인을 출력하려면 어떻게 해야 할까?
```jsx
// 4. 람다 전달.
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
```
### 함수형 인터페이스 사용
함수형 인터페이스는 오직 하나의 **'추상 메서드'**를 가진다고 했다. 함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사한다. 함수형 인터페이스의 추상 메서드 시그니처를 **'함수 디스크립터'** 라고 한다. 자바 API는 이미 제작된 다양한 함수형 인터페이스를 포함하고 있다.
> 람다 동작 원리
### 형식 검사
```jsx
List<Apple> HeavierThan150g =
filter(inventory, (Apple apple) -> apple.getWeight() > 150);
```
- filter 메서드의 선언을 확인한다.
filter(List<Apple>inventory, Predicate<Apple> p)
- filter 메서드는 두 번째 파라미터로 Predicate을 기대한다. T → boolean
- Predicate인터페이스의 추상 메서드는
boolean test(Apple apple)
4.Apple을 인수로 받아 boolean을 반환하는 test 메서드임을 확인한다.
5.함수 디스크립터가 Apple → boolean이므로 람다의 시그니처와 일치한다. 람다도 Apple을 인수로 받아 boolean을 반환하므로 코드 형식 검사가 성공적으로 완료된다.
형식 추론
자바 컴파일러가 람다 표현식이 사용된 콘텍스트(대상 형식)을 이용하여 람다 표현식과 관련된 함수형 인터페이스를 추론한다. 즉, 대상형식을 이용하여 함수 디스크립터를 알 수 있으므로 람다 시그니처도 추론할 수 있다. 그래서 람다 문법에서 이를 생략할 수 있다.
Comparator<Apple> c =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
Comparator<Apple> c =
(a1, a2) -> a1.getWeight().compareTo(a2.getWeight());
메서드참조
메서드 정의를 재활용하여 람다처럼 전달할 수 있다. 특정 메서드만을 호출하는 람다의 축약형이다. (즉, 람다표현식에서 하는 일이라고는 이미 존재하는 메서드를 호출하는게 저눕인 경우, 사용할 수 있다.)
명시적으로 메서드명을 참조함으로써 가독성을 높힐 수 있다.
1.정적 메소드 참조 예
Integer :: parseInt
2.다양한 형식의 인스턴스 메서드 예
3.기존 객체의 인스턴스 메서드 참조 예
- 1 번과 2번의 차이는 이런 느낌이라고 생각한다.
4. 생성자 참조
List<Integer> weights = Arrays.asList(7, 3, 4, 10);
List<Apple> apples = map(weights, Apple::new);
List<Apple> apples = map(weights, ()->new Apple());
public List<Apple> map(List<Integer> list,
Function<Integer, Apple> f){
List<Apple> result = new ArrayList<>();
for(Integer i : list){
result.add(f.apply(i));
}
return result;
}
//<T,R>->R 디스크립션인 Function <Integer,Apple> -> Apple객체 생성
응용, 정리
람다를 1회성 행위를 위해서 사용한다면 훌륭하지만, 여러 곳에서 사용된다면 어떨까?
class Person {
public String firstName;
public String lastName;
public int age;
};
Person 객체를 SortedSet에 저정하거나 어떤 형태로든 리스트 내에서 정렬될 필요가 있을 때, Person 인스턴스가 정렬되기 위한 다른 여러가지 방식을 필요로 합니다. 예를 들어, 이름으로 정렬하고 싶을 때도 있고 성으로 정렬하고 싶을 때도 있습니다. 이 목적으로 Comparator를 사용하는데 Comparator 인스턴스를 전달함으로써 정렬 방식을 정의할 수 있게 됩니다.
public interface Comparator<T>{
int compare(T o1, T o2);
}//함수형 인터페이스 Comparator
람다로 정의하면 간단하게 코드를 작성할 수 있습니다.
public static void main(String... args) {
Person[] people = new Person[] {
new Person("Ted", "Neward", 41),
new Person("Charlotte", "Neward", 41),
new Person("Michael", "Neward", 19),
new Person("Matthew", "Neward", 13)
};
// Sort by first name
Arrays.sort(people, (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName));
for (Person p : people)
System.out.println(p);
}
그러나 만약 Person 인스턴스를 이름에 의해 정렬하는 것을 여러번 수행한다면 저 람다 코드가 반복이 될 것이다. 그래서 Comparator 객체를 생성해준다.
//Comparator를 Person 자체의 멤버변수로 선언해준다. 변하면 안되기에 final
class Person {
public String firstName;
public String lastName;
public int age;
public final static Comparator<Person> compareFirstName =
(lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
public final static Comparator<Person> compareLastName =
(lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
public Person(String f, String l, int a) {
firstName = f; lastName = l; age = a;
}
public String toString() {
return "[Person: firstName:" + firstName + " " +
"lastName:" + lastName + " " + "age:" + age + "]";
}
}
그 뒤에 다른 static 필드처럼 참조당할 수 있다.
public static void main(String... args) {
Person[] people = . . .;
// Sort by first name
Arrays.sort(people, Person.compareFirstName);
for (Person p : people)
System.out.println(p);
}
compareFirstName을 바로 메소드 참조를 이용하여 사용하는 방법이다.
class Person {
public String firstName;
public String lastName;
public int age;
public static int compareFirstNames(Person lhs, Person rhs) {
return lhs.firstName.compareTo(rhs.firstName);
}
// ...
}
public static void main(String... args) {
Person[] people = . . .;
// Sort by first name
Arrays.sort(people, **Person::compareFirstNames**);
for (Person p : people)
System.out.println(p);
}
혹은 Comparator 객체를 생성하여 넣어주기
public static void main(String... args) {
Person[] people = . . .;
Comparator cf = Person::compareFirstNames;
// Sort by first name
Arrays.sort(people, cf);
for (Person p : people)
System.out.println(p);
}
Collections의 sort 메서드의 모습.
가장 강력한 방법으로는 새로운 라이브러리를 사용하는 것이다.
Arrays.sort(people, comparing(Person::getFirstName));
comparing은 Function함수 인터페이스를 받아서 Comparator 함수 인터페이스를 return해주는 자바8의 추가된 java.util.Comparator 라이브러리이다.
'개발언어 > JAVA' 카테고리의 다른 글
[디자인 패턴] 프록시 패턴(Proxy Pattern), 데코레이터 패턴(Decorator Pattern) (0) | 2022.11.05 |
---|---|
[디자인 패턴] 템플릿 메서드 패턴, 전략 패턴 (0) | 2022.11.03 |
동시성 문제 해결(ThreadLocal) (0) | 2022.08.31 |
[모던자바인액션] 2. 스트림 정리 (0) | 2022.04.05 |
[모던자바인액션] 0. 자바 8 변경점, 개요 (0) | 2022.02.27 |