#chapter 3. 람다 표현식
-. 람다 표현식 : 메서드로 전달할 수 있는 익명함수를 단순화한 것
[example]
Comparator<Apple> byWeight = new Comparator<Apple>() {
public int compare(Apple a1, Apple a2) {
return a1.getWeight().compareTo(a2.getWeight());
}
};
[람다 이용]
Comparator<Apple> byWeight =
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
람다 파라미터 화살표 람다 바디
[람다 포멧]
-. (parameters) -> expression
-. (parameters) -> {expression;}
-. 함수형 인터페이스 : 정확히 하나의 추상메서드를 지정하는 인터페이스
public interface Comparator<T> {
boolean test(T t);
}
?) 많은 디폴트 메서드가 있더라도 추상 메서드가 오직 하나면 함수형 인터페이스다.
디포트 메서드 : 인터페이스의 메서드를 구현하지 않은 클래스를
@FunctionalInterface
해당 어노테이션을 쓰면
???) 함수형 인터페이스로 뭘 할 수 있을까?
람다 표현식으로 함수형 인터페이스의 추상 메서드 구현을 직접 전달할 수 있으므로 전체 표현식을 함수형 인터 페이스의 인스턴스로 취급
자바8 설계자들은 java.util.function.* 패키지로 여러가지 새로운 함수형 인터페이스를 제공.
-. Predicate
test 라는 추상 메서드를 정의하며, test는 제네릭형식의 T의 객체를 인수로 받아 불리언을 반환한다.
[java.util.function.Predicate]
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
[Predicate 예제]
public <T> List<T> filter(List<T> list, Predicate<T> p) {
List<T> results = new ArrayList<>();
for (T t:list) {
if(p.test(t)) {
results.add(t);
}
}
return results;
}
Predicate<String> nonEmptyStringPredicate = (String s) -> !s.isEmpty();
List<String> nonEmpty = filter(listOfStrings, nonEmptyStringPredicate);
-. Comsumer
accept 라는 추상 메서드를 정의하며, accept는 제네릭형식의 T의 객체를 인수로 받아 void를 반환한다.
[java.util.function.Consumer]
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
[Comsumer 예제]
public <T> void foreach(List<T> list, Consumer<T> c) {
for (T t: list) {
c.accept(t);
}
}
private void forEach() {
Arrays.asList(1, 2, 3, 4, 5),
(Integer i) -> System.out.println(i)
}
-. Function
java.util.function.Function<T, R> 인터페이스는 제네릭 형식 T를 인수로 받아서 제네릭 형식 R객체를 반환하는 추상메서드 apply를 정의한다.
[java.util.function.Function]
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
[Function 예제]
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;
}
List<Integer> l = map(
Arrays.asList("lamdas", "in", "action"),
(String s) -> s.length()
);
cf> java의 형식
참조형 : Byte, Integer, Object, List
기본형 : int, double, byte, char
-. 박싱 : 기본형 -> 참조형 으로 형 변환
-. 언박싱 : 참조형 -> 기본형 으로 형 변환
-. 오토박싱 : 박싱과 언박싱이 자유롭게 이뤄짐
람다로 함수형 인터페이스의 인스턴스를 만들수 있다.
[형식검사 문제] 다음 코드를 컴파일할 수 없는 이유는?
Object o = () -> { System.out.println("Eun example"); };
(solution)
람다 표현식의 콘텍스트 = Object(대상형식) 이다.
하지만 Object는 함수형 인터페이스가 아니다.
따라서 ()-> void 형식의 함수 디스크립터를 갖는 Runnable로 대상 형식을 바꿔서 문제를 해결할 수 있다.
sol) Object o = (Runnable) () -> { System.out.println("Eun example"); };
(추가) 예를 들어 execute (() -> {})라는 람다 표현식이 있다면 Runnable과 Action의 함수 디스크립터가 같으므로 누구를 가리키는지가 명확하지 않다.
public void excute(Runnable runnable) {
runnable.run();
}
public void excute(Action<T> action) {
action.act();
}
@FunctionlInterface
interface Action {
void act();
}
sol) execute ( (Action) () -> {})
-. 형식추론
자바 컴파일러는 람다표현식이 사용된 콘텍스트(대상 형식)를 이용해서 람다 표현식과 관련된 함수형 인터페이스를 추론한다.
[Comparator 객체 예제]
Comparator<Apple> c = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()); // 형식을 추론하지 않음.
Comparator<Apple> c = (a1, a2) -> a1.getWeight().compareTo(a2.getWeight()); // 형식을 추론함
=> 개발자 스스로 어떤 코드가 가독성을 향상 시킬 수 있는지 결정해야 한다.
-. 지역변수 사용
람다 캡쳐링 : 람다 표현식에서는 익명 함수가 하는 것처럼 자유변수를 활용할 수 있다.
cf> 자유변수란) 파라미터로 넘겨진 변수가 아닌 외부에서 정의된 변수
[Question]
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber); // 컴파일 ERROR 발생
portNumber = 31337;
(solution)
2번째 줄에서 컴파일에러 발생,
why?) 자유변수의 사용에도 약간의 제약이 있다.
지역변수는 final로 선언되어 있어야 한다.
final int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337; // 컴파일 ERROR 발생(final 변수를 수정했으므로)
[자유변수 사용에 제약이 생긴 이유?]
(저장공간)
인스턴스변수 => 힙
지역 변수 => 스택
람다에서 지역변수에 바로 접근할 수 있다는 가정하에 람다가 스레드에서 실행된다면 변수를 할당한 스레드가 사라져서 변수 할당이 해제되었는데도 람다를 실행하는 스레드에서는
해당 변수에 접근하려 할 수 있다. 따라서 자바 구현에서는 원래 변수에 접근을 허용하는것이 아닌 자유 지역 변수의 복사본을 제공한다.
따라서 복사본의 값이 바뀌지 않아야 하므로 지역 변수에서는 한 번만 값을 할당해야 한다는 제약이 생긴것이다.
-. 메서드 참조
메서드 참조를 사용함으로써 가독성을 높일 수 있고, 간결하게 구현할 수 있다.
[예제]
inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));
inventory.sort(comparing(Apple::getWeight));
=> Apple클래스에 정의된 getWeight의 메서드 참조.
생성자, 배열 생성자, super 호출 등에 사용할 수 있는 특별한 형식의 메서드 참조도 있다.
1) 기존의 메서드 구현을 재활용해서 메서드 참조를 만드는 방법
[예제] List에 포함된 문자열을 대,소문자를 구분하지 않고 정렬하는 프로그램.
List<String> str = Arrays.asList("a", "b", "A", "B");
str.sort((s1, s2) -> s1.compareToIgnoreCase(s2));
str.sort(String::compareToIgnoreCase);
2) 생성자 참조 (ClassName::new)
Supplier<Apple> c1 = Apple::new;
Apple a1 = c1.get(); // Supplier의 get메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.
Function<Integer, Apple> c2 = Apple::new; // (weight) -> new Apple(weight)
Apple a2 = c2.apply(110); // Function의 get메서드를 호출해서 새로운 Apple 객체를 만들 수 있다.
119 ~ 131 페이지
생성자 참조~ 부터는 다음에 한번 더 보기! (이해가 잘 안감)