안녕하세요. 궁금증연구소입니다.
오늘 포스팅 주제는 "[Java] 람다와 익명내부클래스의 차이점은?"입니다.
1. 객체 생성
// Example using anonymous inner class
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
};
r.run(); // creates a new object every time
// Example using lambda expression
Runnable r = () -> System.out.println("Hello, world!");
r.run(); // reuses the same object every time
익명 내부 클래스가 인스턴스화되면 매번 새 개체가 생성됩니다. 이는 특히 개체가 자주 생성되는 경우 성능에 영향을 미칠 수 있습니다. 반면에 람다 식은 호출될 때마다 새 개체를 만들지 않습니다. 대신 람다의 단일 인스턴스가 생성되어 필요에 따라 재사용됩니다.
// Example using anonymous inner class
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
};
Runnable r2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
};
System.out.println(r1 == r2); // prints false, two different objects
// Example using lambda expression
Runnable r3 = () -> System.out.println("Hello, world!");
Runnable r4 = () -> System.out.println("Hello, world!");
System.out.println(r3 == r4); // prints false, two different objects
Runnable r5 = () -> System.out.println("Hello, world!");
Runnable r6 = r5;
System.out.println(r5 == r6); // prints true, same object
위의 예에서 r1과 r2는 두 개의 다른 익명 내부 클래스에 의해 생성된 두 개의 다른 개체이므로 r1 == r2는 false를 반환합니다. 마찬가지로 r3과 r4는 서로 다른 두 람다 식으로 생성된 서로 다른 두 개체이므로 r3 == r4는 false를 반환합니다. 그러나 r5와 r6은 동일한 람다 식으로 생성된 동일한 개체이므로 r5 == r6은 true를 반환합니다. == 연산자를 사용하여 개체를 비교하면 익명의 내부 클래스가 매번 새 개체를 생성하는 반면 람다 식은 동일한 개체를 지속적으로 재활용한다는 것을 알 수 있습니다.
<정리>
익명 내부 클래스 및 람다 식과 같이 새로 생성된 모든 식은 인스턴스화될 때 새 개체를 생성합니다. 주요 차이점은 이러한 개체를 재사용하는 방법입니다.
새 익명 내부 클래스를 만들 때마다 해당 클래스를 나타내는 새 개체가 만들어집니다. 즉, 동일한 익명 내부 클래스의 인스턴스를 여러 개 만들면 각 인스턴스에 고유한 개체가 있습니다.
반면에 람다 식을 만들면 해당 식을 나타내는 단일 개체가 만들어집니다. 이 개체는 람다 식을 사용할 때마다 재사용할 수 있습니다. 이는 람다 표현식이 상태 비저장이기 때문에 가능합니다. 즉, 변경 가능한 상태가 포함되어 있지 않으므로 코드 동작에 영향을 주지 않고 안전하게 재사용할 수 있습니다.
요약하면 익명 내부 클래스와 람다 식은 모두 인스턴스화될 때 새 개체를 생성하지만 람다 식은 사용될 때마다 동일한 개체를 재사용할 수 있는 반면 익명 내부 클래스는 인스턴스화될 때마다 새 개체를 만듭니다.
2. 범위(Scope)
int x = 1;
int y = 2;
// Example using anonymous inner class
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("x + y = " + (x + y)); // can access both x and y
}
};
r.run();
// Example using lambda expression
Runnable r = () -> System.out.println("x + y = " + (x + y)); // can only access final or effectively final variables
r.run();
람다 식은 둘러싸는 범위에서 최종 또는 사실상 최종 변수에만 액세스 할 수 있는 반면 익명 내부 클래스는 둘러싸는 범위에서 모든 변수에 액세스할 수 있습니다.
위의 예에서 익명의 내부 클래스는 x와 y 모두에 액세스할 수 있는 반면 람다 식은 최종이거나 사실상 최종인 경우에만 x와 y에 액세스할 수 있습니다.
3. 직렬화
// Example using anonymous inner class
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello, world!");
}
};
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("runnable.ser"));
out.writeObject(r); // throws NotSerializableException
// Example using lambda expression
Runnable r = () -> System.out.println("Hello, world!");
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("runnable.ser"));
out.writeObject(r); // serializes successfully
대상 인터페이스가 @FunctionalInterface 주석으로 표시된 기능 인터페이스인 경우 람다 식을 직렬화할 수 있지만 익명 내부 클래스는 직렬화할 수 없습니다.
내부 익명 클래스는 자신이 정의된 외부 클래스 인스턴스에 대한 암시적 참조를 포함하므로 직렬화할 수 없습니다. 즉, 내부 익명 클래스를 직렬화할 때 외부 클래스 인스턴스도 직렬화하려고 시도하므로 외부 클래스 자체가 직렬화할 수 없는 경우 문제가 발생할 수 있습니다. 또한 내부 익명 클래스에는 직렬화할 수 없는 상태가 있을 수 있으며 이로 인해 직렬화 문제가 발생할 수도 있습니다.
반면에 람다 식은 외부 클래스 인스턴스에 대한 암시적 참조를 포함하지 않기 때문에 직렬화 가능합니다. 즉, 람다 식을 직렬화할 때 추가 종속성 없이 람다 식 자체의 상태만 직렬화하는 것입니다. 람다 식은 기능적 인터페이스를 나타내는 데 자주 사용되므로 가볍고 상태 비저장으로 설계되어 쉽게 직렬화할 수 있습니다.
요약하면 내부 익명 클래스는 직렬화할 수 없는 개체에 대한 암시적 참조를 포함할 수 있기 때문에 직렬화할 수 없지만 람다 식은 가볍고 상태 비저장으로 설계되었기 때문에 직렬화할 수 있습니다.
[Java] IntStream의 map 메소드와 Stream<Integer>의 map 메소드의 차이 (1) | 2023.05.10 |
---|---|
[java] 와일드카드의 쓸모:: 읽을수 있으나, 쓸수 없다(vice-versa) (0) | 2023.04.10 |
[java] 람다식의 유효범위와 effective final (0) | 2023.04.10 |
[코드업 Java100제] 1023 : [기초-입출력] (0) | 2023.04.05 |
[Java] 삼항연산자로 조건 3개인 경우 처리하는 방법은? (0) | 2023.03.06 |