728x90
반응형
SMALL
참고자료
김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 강의 | 김영한 - 인프런
김영한 | , [사진]국내 개발 분야 누적 수강생 1위,제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바 문법을 안다? 이걸로는 안됩니다!전 우아한형제들 기술이사, 누적 수강생 40만
www.inflearn.com
람다 vs 익명 클래스 1
자바에서 익명 클래스와 람다 표현식은 모두 간단하게 기능을 구현하거나, 일회성으로 사용할 객체를 만들 때 유용하지만, 그 사용 방식과 의도에는 차이가 있다.
1. 문법 차이
익명 클래스
Runnable anonymous = new Runnable() {
private String message = "익명 클래스";
@Override
public void run() {
System.out.println("[익명 클래스] this: " + this);
System.out.println("[익명 클래스] this.class: " + this.getClass());
System.out.println("[익명 클래스] this.message: " + this.message);
}
};
- 익명 클래스는 클래스를 선언하고 즉시 인스턴스를 생성하는 방식이다.
- 반드시 new 인터페이스명() {...} 형태로 작성해야 하며, 메서드를 오버라이드해서 구현한다.
- 익명 클래스도 하나의 클래스이다.
람다 표현식
Runnable lambda = () -> {
System.out.println("[람다] this:" + this);
System.out.println("[람다] this.class:" + this.getClass());
System.out.println("[람다] this.message:" + this.message);
};
- 람다 표현식은 함수를 간결하게 표현할 수 있는 방식이다.
- 함수형 인터페이스(메서드가 하나인 인터페이스)를 간단히 구현할 때 주로 사용된다.
- 람다는 -> 연산자를 사용하여 표현하며, 매개변수와 실행할 내용을 간결하게 작성할 수 있다.
- 물론 람다도 인스턴스가 생성된다.
2. 코드의 간결함
- 익명 클래스: 문법적으로 더 복잡하고 장황하다. new 인터페이스명() 같은 형태와 함께 메서드를 오버라이드 해야하므로 코드의 양이 상대적으로 많다.
- 람다 표현식: 간결하며, 불필요한 코드를 최소화한다. 또한 많은 생략 기능을 지원해서 핵심 코드만 작성할 수 있다.
3. 상속 관계
- 익명 클래스: 일반적인 클래스처럼 다양한 인터페이스와 클래스를 구현하거나 상속할 수 있다. 즉, 여러 메서드를 가진 인터페이스를 구현할 때도 사용할 수 있다.
- 람다 표현식: 메서드를 딱 하나만 가지는 함수형 인터페이스만을 구현할 수 있다.
- 람다 표현식은 클래스를 상속할 수 없다. 오직 함수형 인터페이스만 구현할 수 있으며, 상태(필드, 멤버 변수)나 추가적인 메서드 오버라이딩은 불가능하다.
- 람다는 단순히 함수를 정의하는 것으로, 상태나 추가적인 상속 관계를 필요로 하지 않는 상황에서만 사용할 수 있다.
4. 호환성
- 익명 클래스: 자바의 오래된 버전에서도 사용할 수 있다.
- 람다 표현식: 자바 8부터 도입됐기 때문에 그 이전 버전에서는 사용할 수 없다.
5. this 키워드의 의미
- 익명 클래스: this는 익명 클래스 자기 자신을 가리킨다. 외부 클래스와 별도의 컨텍스트를 가진다.
- 람다 표현식: this는 람다를 선언한 클래스의 인스턴스를 가리킨다. 즉, 람다 표현식은 별도의 컨텍스트를 가지는 것이 아니라, 람다를 선언한 클래스의 컨텍스트를 유지한다.
- 쉽게 말해, 람다 내부의 this는 람다가 선언된 외부 클래스의 this와 동일하다.
예제 코드
package lambda.lambda6;
public class OuterMain {
private String message = "외부 클래스";
public void execute() {
Runnable anonymous = new Runnable() {
private String message = "익명 클래스";
@Override
public void run() {
System.out.println("[익명 클래스] this: " + this);
System.out.println("[익명 클래스] this.class: " + this.getClass());
System.out.println("[익명 클래스] this.message: " + this.message);
}
};
Runnable lambda = () -> {
System.out.println("[람다] this:" + this);
System.out.println("[람다] this.class:" + this.getClass());
System.out.println("[람다] this.message:" + this.message);
};
anonymous.run();
System.out.println("------------------------------");
lambda.run();
}
public static void main(String[] args) {
OuterMain outerMain = new OuterMain();
System.out.println("[외부 클래스]: " + outerMain);
System.out.println("------------------------------");
outerMain.execute();
}
}
[외부 클래스]: lambda.lambda6.OuterMain@647dd0b5
------------------------------
[익명 클래스] this: lambda.lambda6.OuterMain$1@e0caac5d
[익명 클래스] this.class: class lambda.lambda6.OuterMain$1
[익명 클래스] this.message: 익명 클래스
------------------------------
[람다] this:lambda.lambda6.OuterMain@647dd0b5
[람다] this.class:class lambda.lambda6.OuterMain
[람다] this.message:외부 클래스
- 외부 클래스는 OuterMain@647dd0b5를 가리키고 있다.
- 익명 클래스는 OuterMain$1@e0caac5d를 가리키고 있다. 또한, this.message는 익명 클래스 내부에 선언한 message를 출력한다.
- 람다는 외부 클래스와 동일한 OuterMain@647dd0b5를 가리키고 있다.
6. 캡처링(Capturing)
- 익명 클래스: 외부 변수에 접근할 수 있지만, 지역 변수는 반드시 final 혹은 사실상 final인 변수만 캡처할 수 있다.
- 람다 표현식: 람다도 익명 클래스와 같이 캡처링을 지원한다. 지역 변수는 반드시 final 혹은 사실상 final인 변수만 캡처할 수 있다.
package lambda.lambda6;
public class CaptureMain {
public static void main(String[] args) {
final int final1 = 10;
int final2 = 20;
int changedVar = 30;
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("익명 클래스 - final1 : " + final1);
System.out.println("익명 클래스 - final2 : " + final2);
// 컴파일 오류
// System.out.println("익명 클래스 - changedVar : " + changedVar);
}
};
Runnable lambda = () -> {
System.out.println("익명 클래스 - final1 : " + final1);
System.out.println("익명 클래스 - final2 : " + final2);
// 컴파일 오류
// System.out.println("익명 클래스 - changedVar : " + changedVar);
};
changedVar++;
runnable.run();
lambda.run();
}
}
람다 vs 익명 클래스 2
7. 생성 방식
생성 방식은 자바 내부 동작 방식으로 크게 중요하지는 않지만, 이런게 있구나 하고 참고해볼만 하다.
- 익명 클래스: 새로운 클래스를 정의하여 객체를 생성하는 방식이다.
- 즉, 컴파일 시 새로운 내부 클래스로 변환된다. 예를 들어, OuterClass$1.class와 같이 이름이 지정된 클래스 파일이 생성된다.
- 이 방식은 클래스가 메모리 상에서 별도로 관리되므로, 메모리 상에 약간의 추가 오버헤드가 발생한다 (매우 미미)
- 람다: 내부적으로 invokeDynamic 이라는 매커니즘을 사용하여 컴파일 타임에 실제 클래스 파일을 생성하지 않고 런타임 시점에 동적으로 필요한 코드를 처리한다.
- 따라서, 람다는 익명 클래스보다 메모리 관리가 더 효율적이며, 생성된 클래스 파일이 없으므로 클래스 파일 관리의 복잡성도 줄어든다.
쉽게 정리하면 다음과 같다.
익명 클래스
- 컴파일 시 실제로 OuterClass$1.class와 같은 클래스 파일이 생성된다.
- 일반적인 클래스와 같은 방식으로 작동한다.
- 해당 클래스 파일을 JVM에 불러서 사용하는 과정이 필요하다.
람다
- 컴파일 시점에 별도의 클래스 파일이 생성되지 않는다.
- 자바를 실행하는 실행 시점에 동적으로 필요한 코드를 처리한다.
참고로, 이 부분은 자바 스펙에 명시된 것이 아니기 때문에 자바 버전과 자바 구현 방식에 따라 내용이 달라질 수는 있다. 따라서 대략 이런 방식으로 작동하는구나라고 참고만 해두자.
원본 코드
public class FunctionMain {
public static void main(String[] args) {
Function<String, Integer> function = x -> x.length();
System.out.println("function1 = " + function.apply("hello"));
}
}
- 람다가 포함된 코드가 있다면 자바는 다음과 같이 컴파일 한다.
public class FunctionMain {
public static void main(String[] args) {
Function<String, Integer> function = 람다 인스턴스 생성(구현 코드는 lambda1() 연결)
System.out.println("function1 = " + function.apply("hello"));
}
// 람다를 private 메서드로 추가
private Integer lambda1(String x) {
return x.length();
}
}
- 컴파일 단계에서 람다를 별도의 클래스로 만드는 것이 아니라, private 메서드로 만들어 숨겨둔다.
- 참고로 자바 내부에서 일어나는 일이므로 개발자가 이렇게 만들어진 코드를 확인하기는 어렵다.
- 그리고 실행 시점에 동적으로 람다 인스턴스를 생성하고, 해당 인스턴스의 구현 코드로 앞서 만든 lambda1() 메서드가 호출되도록 연결한다.
이론적으로는 람다가 별도의 클래스 파일도 만들지 않고, 더 가볍기 때문에 약간의 메모리와 성능의 이점이 있지만, 이런 부분은 매우 미미하기 때문에 실무 관점에서 익명 클래스와 람다의 성능 차이는 거의 없다고 보면 된다.
8. 상태 관리
익명 클래스
- 익명 클래스는 인스턴스 내부에 상태(필드, 멤버 변수)를 가질 수 있다. 예를 들어, 익명 클래스 내부에 멤버 변수를 선언하고 해당 변수의 값을 변경하거나 상태를 관리할 수 있다.
- 이처럼 상태를 필요로 하는 경우 익명 클래스가 유리하다.
람다
- 클래스는 그 내부에 상태(필드, 멤버 변수)와 기능(메서드)을 가진다. 반면에 함수는 그 내부에 상태(필드)를 가지지 않고 기능만 제공한다.
- 함수인 람다는 기본적으로 필드(멤버 변수)가 없으므로 스스로 상태를 유지하지는 않는다.
9. 익명 클래스와 람다의 용도 구분
익명 클래스
- 상태를 유지하거나 다중 메서드를 구현할 필요가 있는 경우
- 기존 클래스 또는 인터페이스를 상속하거나 구현할 때
- 복잡한 인터페이스 구현이 필요할 때
람다
- 상태를 유지할 필요가 없고, 간결함이 중요한 경우
- 단일 메서드만 필요한 간단한 함수형 인터페이스 구현 시
- 더 나은 성능(매우 미미하지만)과 간결한 코드가 필요한 경우
정리
- 대부분의 경우 익명 클래스를 람다로 대체할 수 있다. 하지만, 여러 메서드를 가진 인터페이스나 클래스의 경우에는 여전히 익명 클래스가 필요할 수 있다.
- 자바 8 이후에는 익명 클래스, 람다 둘 다 선택할 수 있는 경우라면 익명 클래스 보다는 람다를 선택하는 것이 간결한 코드, 가독성 관점에서 대부분 더 나은 선택이다.
728x90
반응형
LIST
'JAVA의 가장 기본이 되는 내용' 카테고리의 다른 글
기본형 특화 스트림(IntStream 등) (0) | 2025.03.31 |
---|---|
takeWhile, dropWhile, flatMap - Stream API (0) | 2025.03.31 |
람다가 필요한 이유 (0) | 2025.03.28 |
Lombok은 어떻게 동작하는걸까? (0) | 2024.12.01 |
[Java 8] CompletableFuture (0) | 2024.11.30 |