[java] 람다식이 코드 줄을 저장하는 것 외에 다른 용도로 사용됩니까?

람다식이 코드 줄을 저장하는 것 외에 다른 용도로 사용됩니까?

해결하기 쉽지 않은 문제를 해결 한 람다가 제공하는 특별한 기능이 있습니까? 내가 본 일반적인 사용법은 다음과 같이 작성하는 것입니다.

Comparator<Developer> byName = new Comparator<Developer>() {
  @Override
  public int compare(Developer o1, Developer o2) {
    return o1.getName().compareTo(o2.getName());
  }
};

람다 식을 사용하여 코드를 줄일 수 있습니다.

Comparator<Developer> byName =
(Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName());



답변

Lambda 표현식은 일반적으로 Java로 해결할 수있는 문제 집합을 변경하지 않지만, 더 이상 어셈블리 언어로 프로그래밍하지 않는 것과 같은 이유로 특정 문제를 쉽게 해결할 수 있도록합니다. 프로그래머의 작업에서 중복 된 작업을 제거하면 삶이 더 쉬워지고, (수동으로) 생성해야하는 코드의 양만큼만 손대지 않았던 작업을 수행 할 수 있습니다.

그러나 람다 식은 단순히 코드 줄을 절약하는 것이 아닙니다. Lambda 표현식을 사용하면 이전에 익명 내부 클래스를 해결 방법으로 사용할 수 있었던 함수 를 정의 할 수 있습니다. 그렇기 때문에 이러한 경우 익명 내부 클래스를 대체 할 수 있지만 일반적으로는 그렇지 않습니다.

특히, 람다 식은 변환 될 기능 인터페이스에 독립적으로 정의되므로 액세스 할 수있는 상속 된 멤버가 없으며 기능 인터페이스를 구현하는 유형의 인스턴스에 액세스 할 수 없습니다. 람다 식 내 thissuper주변의 상황에서와 같은 의미를 가지고, 또한 볼 이 대답 . 또한 주변 컨텍스트의 지역 변수를 숨기는 새 지역 변수를 만들 수 없습니다. 함수를 정의하는 의도 된 작업의 경우 많은 오류 소스를 제거하지만 다른 사용 사례의 경우 기능 인터페이스를 구현하더라도 람다 식으로 변환 할 수없는 익명 내부 클래스가있을 수 있음을 의미합니다.

또한,이 구조 new Type() { … }new항상 그렇듯이 새로운 고유 한 인스턴스를 생성하도록 보장합니다 . 익명의 내부 클래스 인스턴스는 비 static컨텍스트 에서 생성 된 경우 항상 외부 인스턴스에 대한 참조를 유지합니다 . 대조적으로, 람다 식은 this필요할 때 (예 : 액세스 this또는 비 static멤버)에 대한 참조 만 캡처합니다 . 또한 의도적으로 지정되지 않은 ID의 인스턴스를 생성하여 구현시 기존 인스턴스를 재사용할지 여부를 런타임에 결정할 수 있습니다 (“ 람다식이 실행될 때마다 힙에 개체를 생성합니까? ”참조).

이러한 차이점은 귀하의 예에 적용됩니다. 익명의 내부 클래스 구성은 항상 새 인스턴스를 생성하고 외부 인스턴스에 대한 참조를 캡처 할 수있는 반면, (Developer o1, Developer o2) -> o1.getName().compareTo(o2.getName())일반적인 구현에서 싱글 톤으로 평가되는 비 캡처 람다 표현식입니다. 또한 .class하드 드라이브에 파일을 생성하지 않습니다 .

의미론과 성능의 차이를 감안할 때, 람다 식은 프로그래머가 미래에 특정 문제를 해결하는 방식을 바꿀 수 있습니다. 물론 새로운 언어 기능을 활용하는 함수형 프로그래밍의 아이디어를 수용하는 새로운 API 덕분입니다. Java 8 람다 표현식 및 일류 값을 참조하십시오 .


답변

프로그래밍 언어는 기계가 실행할 수 없습니다.

프로그래머가 생각 하기위한 입니다.

언어는 우리의 생각을 기계가 실행할 수있는 것으로 바꾸는 컴파일러와의 대화입니다. 다른 언어에서 온 (또는 사람에서 자바에 대한 주요 불만 중 하나 남겨위해 이 있음으로 사용되는 다른 언어) 강제 프로그래머의 특정 정신적 모델을 (즉, 모든 클래스)입니다.

나는 그것이 좋은지 나쁜지에 대해서는 무게를 두지 않을 것입니다. 모든 것이 절충안입니다. 그러나 Java 8 람다는 프로그래머가 이전에는 Java에서 할 수 없었던 기능 측면 에서 생각할 수 있도록합니다 .

Java에 올 때 클래스 측면 에서 생각 하는 법을 배우는 절차 적 프로그래머와 똑같습니다 . 합리적인 OO 디자인 (mea culpa)과 더 유사합니다.

익명의 내부 클래스를 표현하는 더 짧은 방법으로 생각한다면 아마도 위의 절차 적 프로그래머가 클래스가 크게 향상되었다고 생각하지 않았던 것과 같은 방식으로 매우 인상적이지 않을 것입니다.


답변

코드 줄을 저장하는 것은 다른 사람이 읽고 이해하는 데 더 적은 시간이 걸리는 더 짧고 명확한 방식으로 상당한 논리 청크를 작성할 수있는 경우 새로운 기능으로 볼 수 있습니다.

람다 식 (및 / 또는 메서드 참조)이 없으면 Stream파이프 라인의 가독성이 훨씬 떨어졌습니다.

예를 들어 Stream각 람다 식을 익명의 클래스 인스턴스로 바꾼 경우 다음 파이프 라인이 어떻게 생겼을 지 생각해보십시오 .

List<String> names =
    people.stream()
          .filter(p -> p.getAge() > 21)
          .map(p -> p.getName())
          .sorted((n1,n2) -> n1.compareToIgnoreCase(n2))
          .collect(Collectors.toList());

다음과 같습니다.

List<String> names =
    people.stream()
          .filter(new Predicate<Person>() {
              @Override
              public boolean test(Person p) {
                  return p.getAge() > 21;
              }
          })
          .map(new Function<Person,String>() {
              @Override
              public String apply(Person p) {
                  return p.getName();
              }
          })
          .sorted(new Comparator<String>() {
              @Override
              public int compare(String n1, String n2) {
                  return n1.compareToIgnoreCase(n2);
              }
          })
          .collect(Collectors.toList());

이것은 람다 식을 사용하는 버전보다 작성하기가 훨씬 더 어렵고 오류가 발생하기 쉽습니다. 이해하기도 더 어렵습니다.

그리고 이것은 비교적 짧은 파이프 라인입니다.

람다 식 및 메서드 참조없이이를 읽을 수 있도록하려면 여기에서 사용되는 다양한 기능 인터페이스 인스턴스를 보유하는 변수를 정의해야했으며, 이는 파이프 라인의 논리를 분할하여 이해하기 어렵게 만들었습니다.


답변

내부 반복

자바 컬렉션을 반복 할 때, 대부분의 개발자는 경향이 얻을 요소를 다음 처리 를. 즉, 해당 항목을 꺼내서 사용하거나 다시 삽입하는 등의 작업입니다. Java 8 이전 버전에서는 내부 클래스를 구현하고 다음과 같은 작업을 수행 할 수 있습니다.

numbers.forEach(new Consumer<Integer>() {
    public void accept(Integer value) {
        System.out.println(value);
    }
});

이제 Java 8을 사용하면 다음을 통해 더 나은 작업을 수행 할 수 있습니다.

numbers.forEach((Integer value) -> System.out.println(value));

이상

numbers.forEach(System.out::println);

인수로서의 행동

다음 경우를 추측하십시오.

public int sumAllEven(List<Integer> numbers) {
    int total = 0;

    for (int number : numbers) {
        if (number % 2 == 0) {
            total += number;
        }
    }
    return total;
}

Java 8 Predicate 인터페이스를 사용 하면 다음과 같이 더 잘할 수 있습니다.

public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
    int total = 0;

    for (int number : numbers) {
        if (p.test(number)) {
            total += number;
        }
    }
    return total;
}

그것을 다음과 같이 부르십시오.

sumAll(numbers, n -> n % 2 == 0);

출처 : DZone-Java에서 Lambda 표현식이 필요한 이유


답변

다음과 같이 내부 클래스 대신 람다를 사용하면 많은 이점이 있습니다.

  • 더 많은 언어 구문 의미를 도입하지 않고도 코드를 더 간결하고 표현력있게 만듭니다. 당신은 이미 질문에 예를 들었습니다.

  • 람다를 사용하면 컬렉션에 대한 맵 축소 변환과 같은 요소 스트림에 대한 함수 스타일 작업으로 프로그래밍 할 수 있습니다. java.util.functionjava.util.stream 패키지 문서를 참조하십시오 .

  • 컴파일러가 람다에 대해 생성 한 물리적 클래스 파일이 없습니다. 따라서 제공되는 애플리케이션을 더 작게 만듭니다. 메모리가 람다에 어떻게 할당됩니까?

  • 컴파일러는 람다가 범위 밖의 변수에 액세스하지 않는 경우 람다 생성을 최적화합니다. 즉, 람다 인스턴스는 JVM에 의해 한 번만 생성됩니다. 자세한 내용 은 Java 8에서 메서드 참조 캐싱이 좋은 아이디어입니까? 질문에 대한 @Holger의 답변을 참조하십시오 .
    .

  • Lambda는 기능 인터페이스 외에 다중 마커 인터페이스를 구현할 수 있지만 익명 내부 클래스는 더 많은 인터페이스를 구현할 수 없습니다. 예를 들면 다음과 같습니다.

    //                 v--- create the lambda locally.
    Consumer<Integer> action = (Consumer<Integer> & Serializable) it -> {/*TODO*/};

답변

Lambda는 익명 클래스의 구문 설탕 일뿐입니다.

람다 이전에는 익명 클래스를 사용하여 동일한 작업을 수행 할 수 있습니다. 모든 람다 식은 익명 클래스로 변환 할 수 있습니다.

IntelliJ IDEA를 사용하는 경우 변환을 수행 할 수 있습니다.

  • 람다에 커서를 놓습니다.
  • Alt / 옵션 + Enter 누르기

여기에 이미지 설명 입력


답변

귀하의 질문에 답하기 위해 사실 람다 Java-8 이전에는 할 수 없었던 작업을 허용하지 않고보다 간결한 코드 를 작성할 수 있습니다 . 이것의 장점은 코드가 더 명확하고 유연하다는 것입니다.