함수 객체
예전 자바에서는 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스를 사용
이런 인터페이스의 인스턴스를 함수 객체라고 하여, 특정 함수나 동작을 나타내는데 사용
- 함수 객체를 만드는 주요 수단은 익명 클래스
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
익명 클래스의 한계
위 코드에서 Comparator 인터페이스가 정렬을 담당하는 추상 전략을 뜻하며문자열을 정렬하는 구체적인 전략을 익명 클래스로 구현
- 익명 클래스 방식은 코드가 너무 길다
- 따라서 자바는 함수형 프로그래밍에 적합하지 않았다.
람다의 등장
자바8에 와서 추상 메서드 하나짜리 인터페이스는 람다식을 사용해 함수형 인터페이스를 만들 수 있게 됨!
- 지금은 함수형 인터페이스라고 부름
Collections.sort(words,
(s1, s2) -> return Integer.compare(s1.length(), s2.length()));
눈여겨볼 점
- 람다, 매개변수, 반환값의 타입은 각각 (Comparator<String>), String, int지만 코드에서 언급X
- 컴파일러가 타입을 추론
하지만, 상황에 따라 직접 개발자가 명시해줘야할 때가 있다. → 너무 복잡..
타입을 명시해야 코드가 명확할 때만 제외하고는, 람다의 모든 매개변수 타입은 생략하자
더 나아가기
람다 자리에 비교자 생성 메서드를 사용 시 위의 코드를 더 간결하게 만들 수 있다.
Collections.sort(words, comparingInt(String::length));
자바 8의 List 인터페이스에 추가된 sort 메서드를 이용해 더더 간결하게 만들 수 있다.
words.sort(comparingInt(String::length));
람다를 언어 차원에!
람다를 언어 차원에 지원하면서 기존에는 적합하지 않았던 곳에서도 함수 객체를 실용적으로 사용할 수 있게 되었음
- apply 메서드의 동작이 상수마다 달라야 해서 상수별 클래스 몸체를 사용해 각 상수에서 apply 메소드를 재정의 한 예제
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
}
private final String symbol;
Operation(String symobl) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
- 각 열거 타입 상수의 동작을 람다로 구현해 생성자에 넘기고
- 생성자는 이 람다를 인스턴스 필드로 저장
- apply 메서드에서 필드에 저장된 람다를 호출
public enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y)
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symobl, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override public String toString() { return symbol; }
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
그렇다면,
람다 기반 Operation 열거 타입을 보면 상수별 클래스 몸체는 더 이상 사용할 이유가 없을까?
- 람다는 이름이 없고 문서화를 하지 못함
- 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 쓰지 말아야 함
- 람다는 한 줄일 때가 best, 최대 3줄 안에 끝내야 함
- 열거 타입 생성자에 넘겨지는 인수들의 타입도 컴파일 타임에 추론
- 따라서, 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없음(인스턴스는 런타임에 만들어지기 때문)
즉,
상수별 동작을 단 몇 줄로 구현하기 어렵거나
인스턴스 필드나 메서드를 사용해야만 하는 상황이라면 상수별 클래스 몸체를 사용해야함
또,
- 람다는 함수형 인터페이스에서만 쓰임
- 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없기 때문에 익명 클래스를 써야함
- 람다는 자신을 참조할 수 없음
- 람다에서 this는 바깥 인스턴스를 가리킴
- 반면 익명 클래스에서 this는 자기 자신을 가리킴
람다도 익명 클래스처럼 직렬화 형태가 구현별로 다를 수 있음
직렬화해야만 하는 함수 객체가 있다면 private 정적 중첩 클래스의 인스턴스를 사용하자
'독서📚' 카테고리의 다른 글
자바 모듈 시스템(+ module-info.java & setting.gradle) (1) | 2024.02.07 |
---|---|
[아이템 44] 표준 함수형 인터페이스를 사용하라 (0) | 2023.09.15 |
[아이템 43] 람다보다는 메서드 참조를 사용하라 (0) | 2023.09.15 |
새로운 인프라 환경이 온다 (0) | 2023.09.12 |
[자바의 정석] 1. 자바를 시작하기 전에 (0) | 2023.06.27 |
함수 객체
예전 자바에서는 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스를 사용
이런 인터페이스의 인스턴스를 함수 객체라고 하여, 특정 함수나 동작을 나타내는데 사용
- 함수 객체를 만드는 주요 수단은 익명 클래스
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
익명 클래스의 한계
위 코드에서 Comparator 인터페이스가 정렬을 담당하는 추상 전략을 뜻하며문자열을 정렬하는 구체적인 전략을 익명 클래스로 구현
- 익명 클래스 방식은 코드가 너무 길다
- 따라서 자바는 함수형 프로그래밍에 적합하지 않았다.
람다의 등장
자바8에 와서 추상 메서드 하나짜리 인터페이스는 람다식을 사용해 함수형 인터페이스를 만들 수 있게 됨!
- 지금은 함수형 인터페이스라고 부름
Collections.sort(words,
(s1, s2) -> return Integer.compare(s1.length(), s2.length()));
눈여겨볼 점
- 람다, 매개변수, 반환값의 타입은 각각 (Comparator<String>), String, int지만 코드에서 언급X
- 컴파일러가 타입을 추론
하지만, 상황에 따라 직접 개발자가 명시해줘야할 때가 있다. → 너무 복잡..
타입을 명시해야 코드가 명확할 때만 제외하고는, 람다의 모든 매개변수 타입은 생략하자
더 나아가기
람다 자리에 비교자 생성 메서드를 사용 시 위의 코드를 더 간결하게 만들 수 있다.
Collections.sort(words, comparingInt(String::length));
자바 8의 List 인터페이스에 추가된 sort 메서드를 이용해 더더 간결하게 만들 수 있다.
words.sort(comparingInt(String::length));
람다를 언어 차원에!
람다를 언어 차원에 지원하면서 기존에는 적합하지 않았던 곳에서도 함수 객체를 실용적으로 사용할 수 있게 되었음
- apply 메서드의 동작이 상수마다 달라야 해서 상수별 클래스 몸체를 사용해 각 상수에서 apply 메소드를 재정의 한 예제
public enum Operation {
PLUS("+") {
public double apply(double x, double y) { return x + y; }
},
MINUS("-") {
public double apply(double x, double y) { return x - y; }
},
TIMES("*") {
public double apply(double x, double y) { return x * y; }
},
DIVIDE("/") {
public double apply(double x, double y) { return x / y; }
}
private final String symbol;
Operation(String symobl) { this.symbol = symbol; }
@Override public String toString() { return symbol; }
public abstract double apply(double x, double y);
}
- 각 열거 타입 상수의 동작을 람다로 구현해 생성자에 넘기고
- 생성자는 이 람다를 인스턴스 필드로 저장
- apply 메서드에서 필드에 저장된 람다를 호출
public enum Operation {
PLUS("+", (x, y) -> x + y),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y)
private final String symbol;
private final DoubleBinaryOperator op;
Operation(String symobl, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
@Override public String toString() { return symbol; }
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
그렇다면,
람다 기반 Operation 열거 타입을 보면 상수별 클래스 몸체는 더 이상 사용할 이유가 없을까?
- 람다는 이름이 없고 문서화를 하지 못함
- 따라서 코드 자체로 동작이 명확히 설명되지 않거나 코드 줄 수가 많아지면 쓰지 말아야 함
- 람다는 한 줄일 때가 best, 최대 3줄 안에 끝내야 함
- 열거 타입 생성자에 넘겨지는 인수들의 타입도 컴파일 타임에 추론
- 따라서, 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없음(인스턴스는 런타임에 만들어지기 때문)
즉,
상수별 동작을 단 몇 줄로 구현하기 어렵거나
인스턴스 필드나 메서드를 사용해야만 하는 상황이라면 상수별 클래스 몸체를 사용해야함
또,
- 람다는 함수형 인터페이스에서만 쓰임
- 추상 클래스의 인스턴스를 만들 때 람다를 쓸 수 없기 때문에 익명 클래스를 써야함
- 람다는 자신을 참조할 수 없음
- 람다에서 this는 바깥 인스턴스를 가리킴
- 반면 익명 클래스에서 this는 자기 자신을 가리킴
람다도 익명 클래스처럼 직렬화 형태가 구현별로 다를 수 있음
직렬화해야만 하는 함수 객체가 있다면 private 정적 중첩 클래스의 인스턴스를 사용하자
'독서📚' 카테고리의 다른 글
자바 모듈 시스템(+ module-info.java & setting.gradle) (1) | 2024.02.07 |
---|---|
[아이템 44] 표준 함수형 인터페이스를 사용하라 (0) | 2023.09.15 |
[아이템 43] 람다보다는 메서드 참조를 사용하라 (0) | 2023.09.15 |
새로운 인프라 환경이 온다 (0) | 2023.09.12 |
[자바의 정석] 1. 자바를 시작하기 전에 (0) | 2023.06.27 |