
람다 표현식
특징
람다 표현식
(Apple a1, Apple a2) → a.getWeight().compareTo(a2.getWeight());
-람다 파라미터 - - 화살표- -람다 바디-
//String 형식의 파라미터를 가지며 int를 반환 람다 표현식은 return이 함축 되어있어 안써도됨
(String s) -> s.length()
//Apple 형식의 파라미너 하나를 가지며 boolean을 반환
(Apple a) -> a.getWeight() > 150
//int 형식의 파리미터 두개를 가지며 리턴값은 없다 , 여러행을 포함해 쓸 수 있다.
(int x, int y) -> { System.out.println("Result:" ); System.out.println(x+y);}
//파라미터가 없으며 int 42 반환
() -> 42
//Apple 형식의 파라미터 두개를 가지며 int를 반환
(Apple a1, Apple a2) → a.getWeight().compareTo(a2.getWeight());
람다 문법
예제
Predicate 가 함수형 인터페이스 , 오직 하나의 추상 메서드만 지정하기 때문
public interface Predicate<T> {
boolean test(T t)
}
함수형 인터페이스는 정확히 하나의 추상 메서드를 지정하는 인터페이스
추가로 Comparator, Runnable 등이 있음
전체 표현식을 함수형 인터페이스의 인스턴스로 취급 가능
Runnable r1 = () -> System.out.println("Hello Wolrd 1"); // ㅏㄹㅁ다 사용
Runnable 41 = new Runnable(){ // 익명 클래스 사용
public void run(){
System,out.println("Hello Wolrd 2");
}
};
public static void process(Runnable r){
r.run();
}
process(r1); //Hello Wolrd 1 출력
process(r2); //Hello Wolrd 2 출력
process(() -> System.out.println("Hello world 3")); // 직접 전달된 람다 표현식
함수형 인터페이스의 추상 메서드 시그니처 는 람다 표현식의 시그니처를 가르킴
람다 표현식의 시그니처를 서술하는 메서드를 함수 디스크립터 라고 부름
ex) Runnable 인터페이스의 유일한추상 메서드 run은 인수와 반환값이 없으므로 인수와 반환값이 없는 시그니처로 생각
() → void // 파라미터 리스트가 없고 void를 반환
(Apple, Apple) → int 는 두개의 인수를 받아 int를 반환
함수형 인터페이스를 인수로 받는 메서드에만 람다 표현식을 사용할 수 있을까 → 언어를 더 복잡하게 만들지 않기 위해
자원 처리에 사용하는 순환패턴: 자원 열기 → 처리 → 자원 닫기
설정과 정리 과정은 대부분 비슷
→ 실제 자원을 처리하는 코드를 설정과 정리 두 과정이 둘러싸는 형태를 가짐
초기화/준비코드→ 작업 → 정리/ 마무리 코드
→ 이걸 실행 어라운드 패턴 이라고 부름
예제, 자바 8에서 새로 추가된 trh-with-resources 구문, 자원을 명시적으로 닫을 필요가 없어 간결
public String porcessFile() throws IOException {
try( BufferedReader br = new BufferedReader(new FileReader("data.txt")))}
return br.readLing();
}
}
혅내 코드는 파일에서 한번에 한줄말 읽을 수 있음
한번데 두줄을 읽거나 가장 자주 사용되는 단어를 반환하려면?
→ 기존의 설정, 정리과정은 재사용하고 processFile 메서드만 다른 동작을 수행하도록 명령할 수 있으면 좋음
→ 동작을 파라미터화화 하는 것
processFile 메서드가BufferedReader를 이용해 다른 동작을 수해할 수 있도록 processFile 메서드로 동작을 전달
람다를 이용해 동작 전달
한번에 두행을 읽게 하려면?
String result = processFile((BufferedReader br) -> br.readLine() + br.readLine());
함수형 인터페이스 자리에 람다를 사용할 수 있음
BufferedReader → String과 IOException을 던질 수 있는 시그니처와 일치하는 함수형 인터페이스를 만들어함
@FunctinalInterface
public interface BufferedReaderProcessor{
String process(BufferedReader b) throws IOException;
}
public String processFile(BufferedReaderProcessor p) thorws IOException{
...
}
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으면 전달된 코드는 함수형 인터페이스의 인스턴스로 전달된 코드와 같은 방식으로 처리
public String porcessFile(BufferedReaderProcessor p ) throws IOException {
try( BufferedReader br = new BufferedReader(new FileReader("data.txt")))}
return p.process(br);
}
}
이제 람다를 이용해 다양한 동작을 메서드로 전달 가능
String oneLine = processFile((BufferedReader br) -> br.readLine());
String twoLines = processFile((BufferedReader br) -> br.readLine() + br.readLine());
함수형 인터페이스는 오직 하나의 추상 메서드를 지정
함수형 인터페이스의 추상 메서드는 람다 표현식의 시그니처를 묘사함
다양한 람다 표현식을 사용하려면 공통의 함수 디스크립터를 기술하는 함수형 인터페이스 집합이 필요
java.util.function.Predicate 인터페이스
T 형식의 객체를 사용하는 불리언 표현식이 필요한 상황에서 Predicate 인터페이스를 사용할 수 있음
ex) String 객체를 인수로 받는 람다를 정의
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
public <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> results = new ArrayList<>();
for(T t: list){
if(p.test(t)){
result.add(t);
}
}
return results;
}
Predicate<String nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
Predicate 인터페이스의 자바독 명세를 보면 and나 or 같은 메서드도 있음을 알 수 있음
java.util.function.Consumer 인터페이스는 제너릭 형식 T 객체를 받아서 void를 반환하는 accpet라는 추상 메서드를 정의
T 형식의 객체를 인수로 받아서 어떤 동작을 수행하고 싶을 때 Consumer 인터페이스를 사용할 수 있음
ex) Integer 리스트를 인수로 받아서 각 항목에 어떤 동작을 수행하는 forEach메서드를 정의할 때 Consumer를 활용할 수 있음
@FunctinalInterface
public interface Consumer<T>{
void accept(T t);
}
public <T> void forEach(List<T> list, Consumer<T> c){
for(T t:list){
c.accpet(t);
}
}
forEach( Arrays.asList(1,2,3,4,5),(Integer i ) -> System.out.println(i)
);
java.util.function.Function<T,R> 인터페이스는 제네릭 형식 T를 인수로받아서 제네릭 형식 R 객체를 반환하는 추상 메서드 apply를 정의
입력을 출력으로 매핑하는 랃마를 정의할 때 Function인터페이스를 활용
ex) String 리스트를 인수로 받아 각 String의 길이를 포함하는 Integer리스트로 변환하는 map메서드를 정의하는 예제
@FunctionalInterface
public interface Function<T,R>{
R apply(T t);
}
public <T,R> List<R> map(List<T> list,Function<T,R> f){
List<R> result = new ArrayList<>();
for(T t:list){
result.add(f.apply(t));
}
return result;
}
//[7,2,6]
List<Integer> l = map(Arrays.asList("lambds","in","action"),(String s) -> s.length()
);
기본형 특화
Predicate, Consumer,Function<T,R>
자바의 모든 형식은 참조형 아니면 기본형에 해당
제네릭 파라미터에는 참조형만 사용할 수 있음 제네릭의 내부 구현 때문에 어쩔 수 없는 일
자바에서는 기본형으로 참조형으로 변환하는 기능 제공
→ 이 기능을 박싱, 반대 동작을 언박싱
박싱, 언박싱 자동으로 이루어지는 오토박싱
이런 변환 과정을 비용이 소모됨
박싱한 값은 기본형을 감싸는 래퍼며 힙에 저장됨
→ 박싱한 값은 메모리를 더 소미하며 기본형을 가져올 때도 메모리를 탐색하는 과정이 필요
자바 8에서는 기본형 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 특별한 버전의 함수형 인터페이스를 제공
하지만 이런 변환 과정은 비용이 소모
자바 8에서는 기본형을 입출력으로 사용하는 상황에서 오토박싱 동작을 피할 수 있도록 함수형 인터페이스 제공
public interface IntPredicate{
boolean test(int t);
}
IntPredicate evenNumbers = (int i) -> i%2 ==0;
evenNumbers.test(1000); //참(박싱없음)
Predicate<Integer> oddNumbers = (Integer i) -> i%2 !=0;
oddNumberse.test(1000); //거짓(박싱)
람다가 어떤 함수형 인터페이스를 구현하는지의 정보가 포함되어 있진 않음
람다가 사용되는 콘텍스트를 이용해 람다의 형식을 추론할 수 있음
어떤 콘텍스트에서 기대되는 람다 표현식의 형식을 대상 형식이라고 부름
List<Apple> heavierThan150g =
filter(inventory, (Apple apple) -> apple.getWeight() > 150);
다음 순서로 형식 확인 과정 진행
람다 표현식이 예외를 던질 수 있다면 추상 메서드도 같이 예외를 던질 수 있도록 throws로 선언
대상 형식 이라는 특징 때문에 같은 람다 표현식이더라도 호환되는 추상 메서드를 가진 다른 함수형 인터페이스로 사용 될 수 있음
ex) Callable 과 PrivilegedAction 인터페이스는 인수를 받지 않고 제네릭 형식 T를 반환하는 함수를 정의
Callable<Integer> c = () -> 42;
PrivilegedAction<Integer> p = () -> 42;
둘다 유효
자바 컴파일러는 대상 형식을 이용해서 함수 디스크립터를 알수 있으므로 컴파일러는 람다 시그니처도 추론 가능
→ 컴파일러는 람다 표현식의 파라미터 형식에 접근할 수 있으므로 람다 문법에서 이를 생략 가능
아래처럼 람다 파라미터 형식을 추론할 수 있음
List<Apple> greenApples = filter(inventory, apple -> GREEN.equals(apple.getColor());
//파라미터 apple에 형식을 명시적으로 지정하지 않음
상황에 따라 명시적으로 형식을 포함하는게 좋을 수도 있고 배제하는 것이 가독성을 향상시킬때도 음
정해진 규칙은 없기 때문에 스스로 향상 시킬 수 있는 코드를 결정
람다 표현식에서 익명 함수가 하는 것 처럼 자유 변수를 활용할 수 있음
→ 람다 캡처링
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
자유 변수에도 약간의 제약이 있음
람다는 인스턴스 변수와 정적 변수를 자유롭게 캡쳐할 수 있음
→ 지역변수는 명시적으로 final로 선언 되어있어야하거나 실질적으로 final로 선언된 변수와 똑같이 사용되어야함
즉 람다 표현식은 한 번만 할당할 수 있는 지역 변수를 캡처 가능
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337; //에러 람다에서 참고하는 지역 변수는 final로 선언되건 final 취급
지역 변수 제약
제약이 필요한 이유
내부적으로 인스턴스 변수와 지역 변수는 태생부터 다름
인스턴스 변수는 힙에 저장되는 반면 지역 변수는 스택에 위치
람다에서 지역 변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행되면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는 해당 변수에 접글하려할 수 있음
자바 구현에서는 원래 변수에 접근을 허용하는 것이 아니라 자유 지역 변수의 복사본을 제공
→ 복사본의 값이 바뀌지 않아야하므로 지역 변수에는 한번만 값을 할당해야한다는 제약이 생김
이 제약 때문에 외부 변수를 변화시키는 일반적인 명령형 프로그램 패턴에 제동 걸 수 있음
메서드 참조를 이용하면 기존의 메서드 정의를 재활용해서 람다처럼 전달 할 수 있음
→ 람다 표현식보다 메서드 ㅊ암조를 사용하는 것이 더 가독성 좋을 때가 있음
inventory.sort((Apple a1,Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(comparing(Apple::getWeight)//메서드 참조와 java.util.Comparator.comparing 활용한 코드
//(Apple a) → a.getWeight() 축약
메서드 참조
ex)
(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) => System.out::println
(String s) -> this.isValidName(s) => this::isValidName
//뭐야 책을 굉장히 보기 어렵게 해놨다;;;;
메서드 참조 만드는 법
컴파일러는 람다 표현식의 형식을 검사하던 방식과 비슷한 과정으로 메서드 참조가 주어진 함수형 인터페이스와 호환하는지 확인
→ 메서드 참조는 콘텍스트의 형식과 일치해야함
ClassName::new 처럼 클래스명과 new 키워드를 이용해 기존 생성자의 참조를 만들 수 있음
ex) 인수가 없는 생성자 즉 Supplierd의 () → Apple 과 같은 시그니처를 갖는 생성자가 있다고 가정
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get(); //Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있음
//아래와 같은 코드
Supplier<Apple> c1 = () -> new Apple(); //람다 표현식은 디폴트 생성자를 가진 Apple을 만듦
Apple a1 = c1.get(); //Supplier의 get 메서드를 호출해서 새로운 Apple 객체를 만들 수 있음
Apple(Integer weight)라는 시그니처를 갖는 생성자는 Function 인터페이스와 같음
Function<Integer,Apple> c2= Apple::new;
Apple a2 = c2.apply(110);
Function<Integer,Apple> c2= (weight) -> new Apple(weight);
Apple a2 = c2.apply(110);
인스턴스화 하지 않고도 생성자에 접근할 수 있는 기능을 다양한 상황에 응용 가능
sort 메서드에 정렬 전략을 전달 할 수 있나?
void sort(Comparator<? super E> c)
Comparator 객체를 인수로 받아 두 사과를 비교
객체 안에 동작을 포함시키는 방식으로 다양한 전략을 전달
sort의 동작은 파라미터화 됨
→ sort에 전달된 정렬 전략 에 따라 sort의 동작 달라짐
public class AppleComparator implements Comparator<Apple>{
public int compare(Apple a1, Apple a2)
{ return a1.getWeight().compareTo(a2.getWeight());
}
inventory sort(new AppleComparator());
inventory.sort(newComparator<Apple>(){
public int compare(Apple a1, Apple a2)
{ return a1.getWeight().compareTo(a2.getWeight());
});
inventory.sort((Apple a1, Apple a2) -> a1.getWeigth().compareTo(a2.getWeight()));
//형식 추론을 통해 아래와 같이 더 줄일 수 있음
inventory.sort((a1,a2) -> a1.getWeight().compareTo(a2.getWeight()));
가독성 더 향상
Comparator 는 comparable 키를 추출해서 Comparator 객체로 만드는 Function 함수를 인수로 받는 정적 메서드 comparing 포함
Comparator<Apple> c = Comparator.comparing((Apple a) -> a.getWeight());
import static java.util.Comparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight())));
메서드 참조 이용하면 더 깔끔하고 간소하게 전달가능
inventory.srot(comparing(Apple::getWeight));
Comparator c = Comparator.comparing(Apple::getWeight);
역정렬
inventory.srot(comparing(Apple::getWeight).reversed());
Comperator 연결
무게가 같은 두 사과가 존재할때 어떤 사과부터 나열"?
→ 비교 결과를 더 다듬을 수 있는 두번째 Comparator를 만들 수 있음
inventory.sort(comparing(Apple::getWeight).reversed().thenComparing(Apple::getCountry));
Predicate 인퍼테이스는 복잡한 프레디케이트를 만들 수 있도록 netgate, and, or 세가지 메서드 제공
Predicate<Apple> notRedApple = redApple.netgate();
Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);
Function 인터페이스는 Function 인스턴스를 반환하는 andThen,compose 두가지 디폴트 메서드를 제공
andThen 메서드는 주어진 함수를 먼저 적용한 결과를 다른 함수의 입력으로 전달하는 함수를 반환
compose 메서드는 인수로 주어진 함수를 먼저 실행한 다음에 그 결과를 외부 함수의 인수로 제공