일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 우선순위 큐
- Sort
- 간단한 완전탐색
- 기본자료구조
- Simple Brute-Force Algorithm
- 깊이우선탐색
- 동적계획법
- parametric search
- Adv. recursive function
- hint
- Stack
- heap
- Divide and Conquer
- 알고리즘잡스
- 선형자료구조
- 매개 변수 탐색
- 개념
- Queue
- 큐
- Advanced Sort
- 스택
- dfs
- 내돈후기
- 고급정렬
- basic data-structure
- 이진탐색
- 힙
- 정렬
- 완전탐색
- binary search
- Today
- Total
루시와 프로그래밍 이야기
ITEM42 익명 클래스보다는 람다를 사용하라 본문
3판에서 볼 수 있는 새로운 내용
Java7 이후의 새로운 내용을 다루는 것뿐 아니라 함수형 프로그래밍 요소도 자세히 알아봅니다. 람다와 스트림에 집중한 7장을 포함하여 새로운 아이템도 많이 추가되었습니다.
람다에도 맵핵 시전!!
자바 7, 8, 9 신기능 반영
– 함수형 인터페이스, 람다식, 메서드 참조, 스트림
– 인터페이스의 디폴트 메서드와 정적 메서드
– 제네릭 타입에서의 다이아몬드 연산자를 포함한 타입 추론
– @SafeVarargs 애너테이션
– try-with-resources 문
– Optional〈T〉 인터페이스, java.time, 컬렉션의 편의 팩터리 메서드 등의 새로운 라이브러리 기능
예전에는 자바에서 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스(드물게는 추상 클래스)를 사용했다.
이런 인터페이스의 인스턴스를 함수객체라고 하여, 특정 함수나 동작을 나타내는 데 썼다.
1997 JDK 1.1의 등장으로 인해, 함수 객체를 만드는 주요 수단은 익명 클래스가 되었다.
다음 코드를 예로 살펴보자. 문자열을 길이순으로 정렬하는데, 정렬을 위한 비교 함수로 익명 클래스를 사용한다.
Collections.sort(words,new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
쪼개서 보아야 쉽게 보인다!
new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
}
);
익명 클래스 방식은 코드가 너무 길기 때문에 자바는 함수형 프로그래밍에 적합하지 않았다.
익명클래스란? "이름이 없는" 클래스
JAVA - 익명클래스(Anonymous class)란?
오늘은 자바의 익명클래스(Anonymous Class) 혹 무명클래스 에 관해 포스팅을 할 것이다. 익명클래스는 말 그대로 익명의 성질을 가진 클래스라는 뜻이다. 즉 이름이 없는 클래스라고 불리우는데, '
mommoo.tistory.com
public class Test{
private int num = 1;
public int getNum(){
return this.num;
}
public void setNum(int num){
this.num = num;
}
}
//Test 라는 클래스
Test t1 = new Test();
//익명클래스 @Override Test를 이름없는 클래스안에서 재정의함
Test t1 = new Test(){
public int num = 10;
@Override
public int getNum(){
return this.num;
}
};
자바 8에 와서 추상 메서드 하나짜리 인터페이스는 특별한 의미를 인정받아 특별한 대우를 받게 된다. 지금은 함수형 인터페이스라 부르는 이 인터페이스들의 인스턴스를 람다식을 사용해 만들 수 있게 된 것이다. 람다는 함수나 익명 클래스와 개념은 비슷하지만 코드는 훨씬 간결하다.
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
여기서 람다, 매개변수(s1, s2), 반환값의 타입은 각각 (Comparator<String), String, int지만 코드에서는 언급이 없다. 우리 대신 컴파일러가 문맥을 살펴 타입을 추론해준 것이다.
상황에 따라 컴파일러가 타입을 결정하지 못할 수도 있는데, 그럴 때는 프로그래머가 직접 명시해야 한다. 타입을 명시해야 코드가 더 명확할 때만 제외하고는, 람다의 모든 매개변수 타입은 생략하자. 그런 다음 컴파일러가 '타입을 알 수 없다'는 오류를 낼 때만 해당 타입을 명시하면 된다. 반환값이나 람다식 전체를 형변환해야 할 때도 있겠지만, 아주 드물 것이다.
https://developer-cheol.tistory.com/61
Effective Java ( 이펙티브 자바 ) - 아이템 42 - 7장 - 람다와 스트림
익명 클래스보다는 람다를 사용하라 예전에는 자바에서 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스(드물게는 추상 클래스)를 사용했다. 이런 인터페이스의 인스턴스를 함수
developer-cheol.tistory.com
https://jjingho.tistory.com/91
예전에는 함수 객체를 만드는 주요 수단으로 익명 클래스를 많이 사용했다.
함수 객체
추상 메서드를 하나만 담은 인터페이스의 인스턴스
// 익명 클래스를 함수 객체로 사용 - 낡은 기법이다.
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2){
return Integer.compare(s1.length(), s2.length());
}
});
하지만 이 방식은 낡은 기법이고 코드가 너무 길어서 함수형 프로그래밍에 적합하지 않다.
Java8부터는 추상 메서드가 하나인 인터페이스는 특별한 대우를 받게 되었다. 지금은 함수형 인터페이스로 부르는 이 인터페이스를 람다 표현식으로 만들 수 있게 된 것이다.
// 람다식을 함수 객체로 사용 - 익명 클래스를 대체했다.
Collections.sort(words,
(s1,s2) -> Integer.compare(s1.length(), s2.length()));
람다는 함수나 익명 클래스와 개념은 비슷하지만 코드는 훨씬 간결하고 의도를 명확히 드러낸다는 장점이 있다. 또한, 컴파일러가 함수형 인터페이스의 타입을 추론해주기 때문에 타입을 생략할 수 있다. 따라서 타입을 명시해야 코드가 더 명확할 때를 제외하고 타입을 생략하는 것이 좋다.
다만 주의해야 할 점은 컴파일러가 제네릭을 통해 타입 정보를 얻는다는 것이다. 즉, 제네릭을 사용하지 않으면 컴파일러는 타입 정보를 추론할 수 없게 된다.
열거 타입과 람다 표현식
// 추상 메서드 구현
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 symbol) {
this.symbol = symbol;
}
public abstract double apply(double x, double y);
}
위의 예제에서는 추상 메서드를 선언하고 상수 별로 다르게 동작하는 연산 코드를 구현했다. 이를 람다로 표현하면 훨씬 간결하고 깔끔하게 개선할 수 있다.
// 람다 구현
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;
OperationLambda(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
public double apply(double x, double y){
return op.applyAsDouble(x,y);
}
}
DoubleBinaryOperator ?
Java에서 제공하는 함수형 인터페이스이다. double 타입 인수 2개를 받아 double을 리턴한다.
열거 타입에서 람다 사용 시 주의사항
1. 람다는 이름이 없기 때문에 문서화를 할 수 없다.
람다는 간결한 코드와 의도를 명확히 드러낼 때 강점을 가진다. 따라서 코드 자체로 동작이 명확히 설명되지 않거나 람다로 표현한 코드가 세 줄 이상이라면 람다를 사용하지 말자.
2. 열거 타입 생성자 안의 람다는 열거 타입의 인스턴스 멤버에 접근할 수 없다.
열거 타입 생성자에 넘겨지는 인수들의 타입도 컴파일 타임에 추론된다. 하지만 인스턴스는 런타임에 만들어지기 때문에 인스턴스 멤버에 접근할 수 없게 된다. 이 경우에는 상수별 클래스 몸체를 사용해야 한다.
2번 추가 설명.
public enum Operation {
PLUS("+", (x, y) -> {
System.out.println(symbol);
return x + y;
}),
MINUS("-", (x, y) -> x - y),
TIMES("*", (x, y) -> x * y),
DIVIDE("/", (x, y) -> x / y);
Operation(String symbol, DoubleBinaryOperator op) {
this.symbol = symbol;
this.op = op;
}
public String getSymbol() {
return symbol;
}
protected final String symbol;
private final DoubleBinaryOperator op;
public double apply(double x, double y) {
return op.applyAsDouble(x, y);
}
}
람다를 생성자의 인수로 넘길 때 인스턴스 멤버인 symbol을 참조하면 컴파일 오류가 발생한다.
이는 람다가 열거 타입의 멤버가 아니기 때문에 발생한다. 즉, 문맥(context)이 다르기 때문에 열거 타입의 인스턴스 멤버에 접근할 수 없다. 따라서 람다가 생성자의 인자로 전달되는 시점에 인스턴스 멤버인 symbol을 참조할 수 없어 컴파일 오류가 발생한다.
람다로 대체 불가능한 부분
람다의 시대가 열리면서 익명 클래스는 설 자리가 크게 좁아진 게 사실이다. 하지만 람다로 대체할 수 없는 부분이 존재한다.
1. 람다는 함수형 인터페이스에서만 쓰인다.
함수형 인터페이스란 추상 메서드가 한 개인 인터페이스를 말한다. 즉, 추상 메서드가 하나 이상이라면 람다를 사용할 수 없다.
2. 추상 클래스의 인스턴스를 만들 때는 익명 클래스를 사용해야 한다.
추상 클래스에서는 람다를 사용할 수 없기 때문에 익명 클래스를 사용해야 한다.(추상 메서드가 여러 개인 인터페이스도 마찬가지)
3. 람다의 this는 바깥 인스턴스를 가리킨다.
람다의 스코프(scope)는 익명 클래스와 다르다. 람다는 바깥 인스턴스와 스코프가 같다. 하지만 익명 클래스는 더 좁은 범위의 스코프를 가진다. 따라서 익명 클래스의 this는 익명 클래스의 인스턴스 자신을 가리키고, 람다의 this는 바깥 인스턴스를 가리킨다. 만약 함수 객체가 자신을 참조해야 한다면 반드시 익명 클래스를 사용해야 한다.
'스터디 > 이펙티브 자바' 카테고리의 다른 글
ITEM44 표준 함수형 인터페이스를 사용하라 (0) | 2022.11.18 |
---|---|
ITEM43 람다보다는 메서드 참조를 사용하라 (0) | 2022.11.18 |
ITEM33. 타입 안전 이종 컨테이너를 고려하라 (0) | 2022.10.13 |
ITEM32. 제네릭과 가변인수를 함께 쓸 때는 신중하라 (0) | 2022.10.13 |
ITEM31. 한정적 와일드카드를 사용해 API 유연성을 높이라 (0) | 2022.10.13 |