Java8 Lambda와 익명 클래스


111

Java8이 최근에 출시되었고 새로운 람다식이 정말 멋져 보이기 때문에 이것이 우리가 익숙했던 익명 클래스의 종말을 의미하는지 궁금합니다.

나는 이것에 대해 조금 연구 해 왔고 Lambda 표현식이 어떻게 분류를 수행하기 위해 Comparator의 익명 인스턴스를 가져 오는 데 사용되는 Collection의 정렬 방법과 같은 이러한 클래스를 체계적으로 대체하는지에 대한 멋진 예제를 찾았습니다.

Collections.sort(personList, new Comparator<Person>(){
  public int compare(Person p1, Person p2){
    return p1.firstName.compareTo(p2.firstName);
  }
});

이제 Lambda를 사용하여 수행 할 수 있습니다.

Collections.sort(personList, (Person p1, Person p2) -> p1.firstName.compareTo(p2.firstName));

그리고 놀랍도록 간결하게 보입니다. 제 질문은 Lambda 대신 Java8에서 이러한 클래스를 계속 사용하는 이유가 있습니까?

편집하다

같은 질문이지만 반대 방향으로, Lambda는 단일 메서드 인터페이스에서만 사용할 수 있기 때문에 익명 클래스 대신 Lambda를 사용하면 어떤 이점이 있습니까?이 새로운 기능은 몇 가지 경우에만 사용되는 바로 가기입니까 아니면 정말 유용합니까?


5
물론, 부작용이있는 메서드를 제공하는 모든 익명 클래스에 대해.
tobias_k 2014 년

11
정보를 위해 비교기를 다음 Comparator.comparing(Person::getFirstName)과 같이 구성 할 수도 getFirstName()있습니다 firstName.
skiwi

1
여러 방법으로 또는 익명 클래스, 또는 ...
마크 Rotteveel

1
나는 특히 EDIT 이후의 추가 질문으로 인해 너무 광범위하게 투표하고 싶습니다 .
Mark Rotteveel 2014 년

1
이 주제에 대한 좋은 심층 기사 : infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood
램 파트라

답변:


108

AIC (익명 내부 클래스)를 사용하여 추상 클래스 또는 구체적인 클래스의 하위 클래스를 만들 수 있습니다. AIC는 상태 (필드) 추가를 포함하여 인터페이스의 구체적인 구현을 제공 할 수도 있습니다. AIC의 인스턴스는 this메서드 본문에서 사용하여 참조 될 수 있으므로 추가 메서드를 호출 할 수 있고 시간이 지남에 따라 상태가 변경 될 수 있습니다. 이들 중 어느 것도 람다에 적용되지 않습니다.

AIC의 대부분의 용도는 단일 함수의 상태 비 저장 구현을 제공하는 것이기 때문에 람다 식으로 대체 할 수 있지만 람다를 사용할 수없는 AIC의 다른 용도가 있습니다. AIC는 여기에 있습니다.

최신 정보

AIC와 람다 식의 또 다른 차이점은 AIC가 새로운 범위를 도입한다는 것입니다. 즉, 이름은 AIC의 수퍼 클래스 및 인터페이스에서 확인되며 어휘로 둘러싸는 환경에서 발생하는 이름을 섀도 잉 할 수 있습니다. 람다의 경우 모든 이름이 어휘로 확인됩니다.


1
Lambda는 상태를 가질 수 있습니다. 이와 관련하여 Lambda와 AIC의 차이는 없습니다.
nosid

1
모든 클래스의 인스턴스와 마찬가지로 @nosid AIC는 필드에 상태를 보유 할 수 있으며이 상태는 클래스의 모든 메서드에 액세스 할 수 있으며 잠재적으로 변경 가능합니다. 이 상태는 객체가 GC 될 때까지 존재합니다. 즉, 무한한 범위를 가지므로 메서드 호출간에 지속될 수 있습니다. 무한한 범위의 람다를 가진 유일한 상태는 람다를 만날 때 캡처됩니다. 이 상태는 불변입니다. 람다 내의 지역 변수는 변경 가능하지만 람다 호출이 진행되는 동안에 만 존재합니다.
Stuart Marks

1
@nosid 아, 단일 요소 배열 해킹. 여러 스레드에서 카운터를 사용하려고하지 마십시오. 힙에 무언가를 할당하고이를 람다로 캡처하려는 경우 AIC를 사용하고 직접 변경할 수있는 필드를 추가하는 것이 좋습니다. 이런 식으로 람다를 사용하면 효과가 있지만 실제 객체를 사용할 수 있는데 왜 귀찮게할까요?
Stuart Marks

2
AIC는 이와 같은 파일을 생성 A$1.class하지만 Lambda는 생성하지 않습니다. 차이에 이것을 추가 할 수 있습니까?
아시프 Mushtaq

2
@UnKnown 그것은 주로 구현 문제입니다. AIC와 람다를 사용하는 하나의 프로그램에 영향을 미치지 않습니다.이 질문이 주로 다루는 내용입니다. 람다 식은 같은 이름의 클래스를 생성합니다 LambdaClass$$Lambda$1/1078694789. 그러나이 클래스는에서가 아니라 람다 메타 팩토리에 의해 즉석에서 생성 javac되므로 해당 .class파일 이 없습니다 . 그러나 이것은 구현 문제입니다.
스튜어트 마크

60

Lambda는 훌륭한 기능이지만 SAM 유형에서만 작동합니다. 즉, 단일 추상 메서드와 만 인터페이스합니다. 인터페이스에 둘 이상의 추상 메서드가 포함되면 즉시 실패합니다. 익명의 클래스가 유용 할 것입니다.

따라서 우리는 익명 클래스를 무시할 수 없습니다. 참고로, and에 sort()대한 유형 선언을 건너 뛰어 메서드를 더 단순화 할 수 있습니다 .p1p2

Collections.sort(personList, (p1, p2) -> p1.firstName.compareTo(p2.firstName));

여기에서 메서드 참조를 사용할 수도 있습니다. 클래스에 compareByFirstName()메소드 를 추가 Person하고 다음을 사용하십시오.

Collections.sort(personList, Person::compareByFirstName);

또는에 대한 getter를 추가 firstName하고 Comparatorfrom Comparator.comparing()메서드를 직접 가져옵니다 .

Collections.sort(personList, Comparator.comparing(Person::getFirstName));

4
알고 있었지만 가독성 측면에서 긴 변수를 선호합니다. 그렇지 않으면 이러한 변수의 출처를 찾는 것이 혼란 스러울 수 있기 때문입니다.
Amin Abu-Taleb 2014 년

@ AminAbu-Taleb 왜 혼란 스러울까요? 이는 유효한 Lambda 구문입니다. 유형은 어쨌든 유추됩니다. 어쨌든 개인적인 선택입니다. 유형을 명시 적으로 지정할 수 있습니다. 문제 없습니다.
Rohit Jain

1
익명 클래스가 직접 새 주석 할 수 있습니다 람다와 익명 클래스 간의 또 다른 미묘한 차이가 자바 8 유형 주석 예로는, new @MyTypeAnnotation SomeInterface(){};. 람다 식에는 불가능합니다. 자세한 내용은 여기에서 내 질문 : Lambda 표현식의 기능적 인터페이스 주석 달기를 참조하십시오 .
Balder 2014 년

36

익명 클래스를 사용한 Lambda 성능

응용 프로그램이 시작되면 각 클래스 파일을로드하고 확인해야합니다.

익명 클래스는 주어진 클래스 또는 인터페이스에 대한 새 하위 유형으로 컴파일러에 의해 처리되므로 각각에 대해 새 클래스 파일이 생성됩니다.

Lambda는 바이트 코드 생성이 다르며 JDK7과 함께 제공되는 invokedynamic 명령어를 사용하여 더 효율적입니다.

Lambda의 경우이 명령어는 런타임까지 바이트 코드의 람다 표현식 번역을 지연하는 데 사용됩니다. (처음으로 지침이 호출 됨)

결과적으로 Lambda 표현식은 정적 메서드가됩니다 (런타임에 생성됨). (stateles 및 statefull 케이스에는 작은 차이가 있으며 생성 된 메소드 인수를 통해 해결됩니다.)


각 람다는 또한 새 클래스가 필요하지만 런타임에 생성되므로 이러한 의미에서 람다는 익명 클래스보다 효율적이지 않습니다. Lambda는 다음을 통해 생성됩니다.invokedynamic 는 일반적으로 invokespecial익명 클래스의 새 인스턴스를 생성하는 데 사용되는 것보다 느린 속도 . 따라서 이러한 의미에서 람다도 느립니다 (하지만 JVM은 invokedynamic대부분의 경우 호출 을 최적화 할 수 있습니다 ).
ZhekaKozlov

3
@AndreiTomashpolskiy 1. 예의를 갖추십시오. 2. 컴파일러 엔지니어가이 주석을 읽으십시오. habrahabr.ru/post/313350/comments/#comment_9885460
ZhekaKozlov

@ZhekaKozlov, JRE 소스 코드를 읽고 javap / debugger를 사용하기 위해 컴파일러 엔지니어가 될 필요는 없습니다. 누락 된 것은 람다 메서드에 대한 래퍼 클래스 생성이 완전히 메모리 내에서 수행되고 비용이 전혀 들지 않는 반면 AIC 인스턴스화에는 해당 클래스 리소스 (I / O 시스템 호출을 의미 함)를 확인하고로드하는 것이 포함된다는 것입니다. 따라서 invokedynamic임시 클래스 생성은 컴파일 된 익명 클래스에 비해 매우 빠릅니다.
Andrei Tomashpolskiy

@AndreiTomashpolskiy I / O는 반드시 천천히 아니다
ZhekaKozlov

14

다음과 같은 차이점이 있습니다.

1) 구문

Lambda 표현식은 AIC (Anonymous Inner Class)에 비해 깔끔하게 보입니다.

public static void main(String[] args) {
    Runnable r = new Runnable() {
        @Override
        public void run() {
            System.out.println("in run");
        }
    };

    Thread t = new Thread(r);
    t.start(); 
}

//syntax of lambda expression 
public static void main(String[] args) {
    Runnable r = ()->{System.out.println("in run");};
    Thread t = new Thread(r);
    t.start();
}

2) 범위

익명의 내부 클래스는 클래스이며, 이는 내부 클래스 내부에 정의 된 변수의 범위가 있음을 의미합니다.

반면에 람다 식은 자체 범위가 아니라 둘러싸는 범위의 일부입니다.

유사한 규칙이 적용됩니다. 익명의 내부 클래스와 람다 식을 사용할 때 superthis 키워드에 . 익명 내부 클래스의 경우이 키워드는 로컬 범위를 참조하고 super 키워드는 익명 클래스의 슈퍼 클래스를 참조합니다. 람다 표현식의 경우이 키워드는 둘러싸는 유형의 객체를 참조하고 super는 둘러싸는 클래스의 수퍼 클래스를 참조합니다.

//AIC
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = new Runnable() {
            @Override
            public void run() {
                int cnt = 5;    
                System.out.println("in run" + cnt);
            }
        };

        Thread t = new Thread(r);
        t.start();
    }

//Lambda
    public static void main(String[] args) {
        final int cnt = 0; 
        Runnable r = ()->{
            int cnt = 5; //compilation error
            System.out.println("in run"+cnt);};
        Thread t = new Thread(r);
        t.start();
    }

3) 성능

런타임시 익명 내부 클래스는 클래스로드, 메모리 할당 및 개체 초기화 및 비 정적 메서드 호출이 필요하지만 람다 식은 순수 컴파일 시간 활동이며 런타임 동안 추가 비용이 발생하지 않습니다. 따라서 람다 식의 성능은 익명의 내부 클래스에 비해 더 좋습니다. **

** 나는이 점이 전적으로 사실이 아니라는 것을 알고 있습니다. 자세한 내용은 다음 질문을 참조하십시오. Lambda 대 익명 내부 클래스 성능 : ClassLoader에 대한 부하 감소?


3

Java 8의 Lambda는 함수형 프로그래밍을 위해 도입되었습니다. 상용구 코드를 피할 수있는 곳. 나는 람다에 관한이 흥미로운 기사를 보았습니다.

http://radar.oreilly.com/2014/04/whats-new-in-java-8-lambdas.html

간단한 논리에는 람다 함수를 사용하는 것이 좋습니다. 람다를 사용하여 복잡한 논리를 구현하는 경우 문제 발생시 코드를 디버깅 할 때 오버 헤드가 발생합니다.


0

람다는 단일 추상 메서드를 사용하는 함수에는 좋지만 다른 모든 경우에는 익명 내부 클래스가 구세주이기 때문에 익명 클래스가 남아 있습니다.


-1
  • 람다 구문은 자바가 추론 할 수있는 명백한 코드를 작성할 필요가 없습니다.
  • 를 사용 invoke dynamic하면 람다는 컴파일 시간 동안 익명 클래스로 다시 변환되지 않습니다 (Java는 객체 생성을 거치지 않고 메서드의 서명 만 신경 쓰고 객체를 생성하지 않고 메서드에 바인딩 할 수 있음).
  • 람다는 우리가하고 싶은 일 대신에 우리가 할 수 있기 전에해야 할 일을 더 강조했습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.