JavaRush /Java Blog /Random-KO /예제가 포함된 람다 표현식

예제가 포함된 람다 표현식

Random-KO 그룹에 게시되었습니다
Java는 원래 완전한 객체 지향 언어입니다. 기본 유형을 제외하고 Java의 모든 것은 객체입니다. 배열도 객체입니다. 각 클래스의 인스턴스는 객체입니다. 함수를 별도로 정의할 수 있는 가능성은 하나도 없습니다(클래스 외부 - 대략 transl. ). 그리고 메서드를 인수로 전달하거나 다른 메서드의 결과로 메서드 본문을 반환할 방법이 없습니다. 그렇죠. 하지만 이는 Java 8 이전이었습니다. 예제가 포함된 람다 표현식 - 1예전 Swing 시절부터 일부 기능을 일부 메소드에 전달해야 할 때 익명 클래스를 작성해야 했습니다. 예를 들어 이벤트 핸들러를 추가하면 다음과 같습니다.
someObject.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {

                //Event listener implementation goes here...

            }
        });
여기서는 마우스 이벤트 리스너에 일부 코드를 추가하려고 합니다. 우리는 익명 클래스를 정의 MouseAdapter하고 즉시 그로부터 객체를 생성했습니다. 이러한 방식으로 추가 기능을 addMouseListener. 즉, Java에서는 간단한 메소드(기능)를 인수를 통해 전달하는 것이 쉽지 않습니다. 이러한 제한으로 인해 Java 8 개발자는 Lambda 표현식과 같은 기능을 언어 사양에 추가해야 했습니다.

Java에 Lambda 표현식이 필요한 이유는 무엇입니까?

처음부터 Java 언어는 Annotation, Generics 등과 같은 것을 제외하고는 크게 발전하지 않았습니다. 우선 Java는 항상 객체 지향을 유지해 왔습니다. JavaScript와 같은 기능적 언어를 사용해 보면 Java가 어떻게 엄격하게 객체 지향적이고 강력한 형식인지 이해할 수 있습니다. Java에서는 함수가 필요하지 않습니다. 그 자체로는 Java 세계에서는 찾을 수 없습니다. 함수형 프로그래밍 언어에서는 함수가 가장 중요합니다. 그것들은 그 자체로 존재합니다. 이를 변수에 할당하고 인수를 통해 다른 함수에 전달할 수 있습니다. JavaScript는 함수형 프로그래밍 언어의 가장 좋은 예 중 하나입니다. 기능적 언어로서 JavaScript의 이점을 자세히 설명하는 좋은 기사를 인터넷에서 찾을 수 있습니다. 기능적 언어에는 Closure와 같은 강력한 도구가 있어 전통적인 애플리케이션 작성 방법에 비해 여러 가지 이점을 제공합니다. 클로저는 환경이 연결된 함수, 즉 함수의 모든 비지역 변수에 대한 참조를 저장하는 테이블입니다. Java에서는 Lambda 표현식을 통해 클로저를 시뮬레이션할 수 있습니다. 물론 클로저와 람다 표현식 사이에는 차이가 있고 작지는 않지만 람다 표현식은 클로저에 대한 좋은 대안입니다. 냉소적이고 재미있는 블로그에서 Steve Yegge는 Java의 세계가 명사(엔티티, 객체 - 대략 번역 )와 어떻게 엄격하게 연결되어 있는지 설명합니다. 그의 블로그를 아직 읽지 않았다면 추천합니다. 그는 Java에 Lambda 표현식이 추가된 정확한 이유를 재미있고 흥미로운 방식으로 설명합니다. 람다 표현식은 오랫동안 누락되었던 기능을 Java에 가져옵니다. 람다 표현식은 객체와 마찬가지로 언어에 기능을 제공합니다. 이것이 100% 사실은 아니지만 Lambda 표현식은 클로저가 아니지만 유사한 기능을 제공한다는 것을 알 수 있습니다. 함수형 언어에서 람다 표현식은 함수입니다. 그러나 Java에서 람다 표현식은 객체로 표현되며 기능적 인터페이스라는 특정 객체 유형과 연결되어야 합니다. 다음으로 그것이 무엇인지 살펴보겠습니다. Mario Fusco의 기사 "Java에서 Lambda 표현식이 필요한 이유"에서는 모든 현대 언어에 클로저 기능이 필요한 이유를 자세히 설명합니다.

람다 표현식 소개

람다 표현식은 익명 함수입니다(Java에 대한 정의가 100% 정확하지는 않지만 어느 정도 명확성을 제공합니다). 간단히 말해서 이는 선언이 없는 메서드입니다. 액세스 한정자 없이 값과 이름을 반환합니다. 즉, 메서드를 작성하고 즉시 사용할 수 있습니다. 일회성 메서드 호출의 경우 특히 유용합니다. 클래스를 만들지 않고도 메서드를 선언하고 작성하는 데 걸리는 시간이 줄어듭니다. Java의 람다 표현식은 일반적으로 다음과 같은 구문을 갖습니다 (аргументы) -> (тело). 예를 들어:
(арг1, арг2...) -> { тело }

(тип1 арг1, тип2 арг2...) -> { тело }
다음은 실제 람다 표현식의 몇 가지 예입니다.
(int a, int b) -> {  return a + b; }

() -> System.out.println("Hello World");

(String s) -> { System.out.println(s); }

() -> 42

() -> { return 3.1415 };

람다 표현식의 구조

람다 표현식의 구조를 살펴보겠습니다.
  • 람다 표현식에는 0개 이상의 입력 매개변수가 있을 수 있습니다.
  • 매개변수 유형은 명시적으로 지정하거나 컨텍스트에서 얻을 수 있습니다. 예를 들어 ( int a)는 다음과 같이 쓸 수 있습니다 ( a)
  • 매개변수는 괄호로 묶이고 쉼표로 구분됩니다. 예를 들어 ( a, b) 또는 ( int a, int b) 또는 ( String a, int b, float c)
  • 매개변수가 없으면 빈 괄호를 사용해야 합니다. 예를 들어() -> 42
  • 매개변수가 하나만 있는 경우, 유형을 명시적으로 지정하지 않으면 괄호를 생략할 수 있습니다. 예:a -> return a*a
  • Lambda 표현식의 본문에는 0개 이상의 표현식이 포함될 수 있습니다.
  • 본문이 단일 문으로 구성된 경우 중괄호로 묶을 수 없으며 키워드 없이 반환 값을 지정할 수 있습니다 return.
  • 그렇지 않으면 중괄호가 필요하며(코드 블록) 반환 값은 키워드를 사용하여 끝에 지정해야 합니다 return(그렇지 않으면 반환 유형은 void).

기능적 인터페이스란?

Java에서 Marker 인터페이스는 메소드나 필드를 선언하지 않은 인터페이스입니다. 즉, 토큰 인터페이스는 빈 인터페이스입니다. 마찬가지로, 기능적 인터페이스는 선언된 추상 메서드가 하나만 있는 인터페이스입니다. java.lang.Runnable기능적 인터페이스의 예입니다. 하나의 메소드만 선언합니다 void run(). 인터페이스도 있습니다 ActionListener. 또한 기능적입니다. 이전에는 기능적 인터페이스를 구현하는 객체를 생성하기 위해 익명 클래스를 사용해야 했습니다. 람다 표현식을 사용하면 모든 것이 더 단순해졌습니다. 각 람다 식은 일부 기능적 인터페이스에 암시적으로 바인딩될 수 있습니다. Runnable예를 들어, 다음 예와 같이 인터페이스 에 대한 참조를 생성할 수 있습니다 .
Runnable r = () -> System.out.println("hello world");
이러한 종류의 변환은 기능적 인터페이스를 지정하지 않을 때 항상 암시적으로 수행됩니다.
new Thread(
    () -> System.out.println("hello world")
).start();
위의 예에서 컴파일러는 Runnable클래스 생성자로부터 인터페이스 구현으로 람다 식을 자동 으로 생성합니다 Thread. public Thread(Runnable r) { }다음은 람다 식과 해당 기능 인터페이스의 몇 가지 예입니다.
Consumer<Integer> c = (int x) -> { System.out.println(x) };

BiConsumer<Integer, String> b = (Integer x, String y) -> System.out.println(x + " : " + y);

Predicate<String> p = (String s) -> { s == null };
@FunctionalInterfaceJava 언어 사양에 따라 Java 8에 추가된 주석은 선언된 인터페이스가 작동하는지 확인합니다. 또한 Java 8에는 Lambda 표현식과 함께 사용할 수 있도록 미리 만들어진 여러 기능 인터페이스가 포함되어 있습니다. @FunctionalInterface선언된 인터페이스가 작동하지 않으면 컴파일 오류가 발생합니다. 다음은 기능적 인터페이스를 정의하는 예입니다.
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
정의에서 알 수 있듯이 기능적 인터페이스에는 하나의 추상 메서드만 있을 수 있습니다. 다른 추상 메서드를 추가하려고 하면 컴파일 오류가 발생합니다. 예:
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

    public void doSomeMoreWork();

}
오류
Unexpected @FunctionalInterface annotation
    @FunctionalInterface ^ WorkerInterface is not a functional interface multiple
    non-overriding abstract methods found in interface WorkerInterface 1 error
После определения функционального интерфейса, мы можем его использовать и получать все преимущества Lambda-выражений. Пример:// defining a functional interface
@FunctionalInterface
public interface WorkerInterface {

    public void doSomeWork();

}
public class WorkerInterfaceTest {

    public static void execute(WorkerInterface worker) {
        worker.doSomeWork();
    }

    public static void main(String [] args) {

      // calling the doSomeWork method via an anonymous class
      // (classic)
      execute(new WorkerInterface() {
            @Override
            public void doSomeWork() {
               System.out.println("Worker called via an anonymous class");
            }
        });

      // calling the doSomeWork method via Lambda expressions
      // (Java 8 new)
      execute( () -> System.out.println("Worker called via Lambda") );
    }

}
결론:
Worker вызван через анонимный класс
Worker вызван через Lambda
여기에서는 자체 기능 인터페이스를 정의하고 람다 식을 사용했습니다. 이 메서드는 execute()람다 식을 인수로 받아들일 수 있습니다.

람다 표현식의 예

Lambda 표현식을 이해하는 가장 좋은 방법은 몇 가지 예를 살펴보는 것입니다. 스트림은 Thread두 가지 방법으로 초기화될 수 있습니다.
// Old way:
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello from thread");
    }
}).start();
// New way:
new Thread(
    () -> System.out.println("Hello from thread")
).start();
Java 8의 이벤트 관리는 Lambda 표현식을 통해서도 수행할 수 있습니다. 다음은 ActionListenerUI 구성요소에 이벤트 핸들러를 추가하는 두 가지 방법입니다.
// Old way:
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button pressed. Old way!");
    }
});
// New way:
button.addActionListener( (e) -> {
        System.out.println("Button pressed. Lambda!");
});
주어진 배열의 모든 요소를 ​​표시하는 간단한 예입니다. 람다 식을 사용하는 방법은 여러 가지가 있습니다. 아래에서는 화살표 구문을 사용하여 일반적인 방법으로 람다 식을 만들고 (::)Java 8에서 일반 메서드를 람다 식으로 변환하는 이중 콜론 연산자도 사용합니다.
// Old way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
for(Integer n: list) {
    System.out.println(n);
}
// New way:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
list.forEach(n -> System.out.println(n));
// New way using double colon operator ::
list.forEach(System.out::println);
다음 예에서는 기능적 인터페이스를 사용하여 Predicate테스트를 생성하고 해당 테스트를 통과하는 항목을 인쇄합니다. 이렇게 하면 람다 식에 논리를 추가하고 이를 기반으로 작업을 수행할 수 있습니다.
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Main {

    public static void main(String [] a)  {

        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.print("Outputs all numbers: ");
        evaluate(list, (n)->true);

        System.out.print("Does not output any number: ");
        evaluate(list, (n)->false);

        System.out.print("Output even numbers: ");
        evaluate(list, (n)-> n%2 == 0 );

        System.out.print("Output odd numbers: ");
        evaluate(list, (n)-> n%2 == 1 );

        System.out.print("Output numbers greater than 5: ");
        evaluate(list, (n)-> n > 5 );

    }

    public static void evaluate(List<Integer> list, Predicate<Integer> predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.print(n + " ");
            }
        }
        System.out.println();
    }

}
결론:
Выводит все числа: 1 2 3 4 5 6 7
Не выводит ни одного числа:
Вывод четных чисел: 2 4 6
Вывод нечетных чисел: 1 3 5 7
Вывод чисел больше 5: 6 7
람다 표현식을 조작하면 목록의 각 요소를 제곱으로 표시할 수 있습니다. stream()일반 목록을 스트림으로 변환하는 방법을 사용하고 있음에 유의하세요 . Java 8은 멋진 클래스 Stream( java.util.stream.Stream)를 제공합니다. 여기에는 람다 표현식을 사용할 수 있는 유용한 메서드가 많이 포함되어 있습니다. 람다 식을 x -> x*x메서드에 전달하여 map()스트림의 모든 요소에 적용합니다. 그런 다음 forEach목록의 모든 요소를 ​​인쇄하는 데 사용합니다.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
for(Integer n : list) {
    int x = n * n;
    System.out.println(x);
}
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
list.stream().map((x) -> x*x).forEach(System.out::println);
목록이 주어지면 목록의 모든 요소의 제곱의 합을 인쇄해야 합니다. 람다 표현식을 사용하면 코드 한 줄만 작성하여 이를 달성할 수 있습니다. 이 예에서는 컨볼루션(축소) 방법을 사용합니다 reduce(). 우리는 각 요소를 제곱하는 방법을 사용한 map()다음 reduce()모든 요소를 ​​단일 숫자로 축소하는 방법을 사용합니다.
// Old way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = 0;
for(Integer n : list) {
    int x = n * n;
    sum = sum + x;
}
System.out.println(sum);
// New way:
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7);
int sum = list.stream().map(x -> x*x).reduce((x,y) -> x + y).get();
System.out.println(sum);

람다 표현식과 익명 클래스의 차이점

주요 차이점은 키워드를 사용한다는 것입니다 this. 익명 클래스의 경우 ' ' 키워드는 this익명 클래스의 개체를 나타내고, 람다 식에서 ' this' 키워드는 람다 식이 사용되는 클래스의 개체를 나타냅니다. 또 다른 차이점은 컴파일 방식입니다. Java는 람다 표현식을 컴파일하고 이를 private클래스 메소드로 변환합니다. 이는 동적 메소드 바인딩을 위해 Java 7에 도입된 Invokedynamic 명령을 사용합니다 . Tal Weiss는 자신의 블로그에서 Java가 람다 표현식을 바이트코드로 컴파일하는 방법을 설명했습니다.

결론

Mark Reinhold(Oracle의 수석 설계자)는 Lambda 표현식을 지금까지 발생한 프로그래밍 모델에서 가장 중요한 변화, 즉 제네릭보다 훨씬 더 중요한 변화라고 말했습니다. 그 사람 말이 맞아야 하는데, 왜냐면... 이는 Java 프로그래머에게 모두가 기다려온 기능적 프로그래밍 언어 기능을 제공합니다. 가상 확장 방법과 같은 혁신과 함께 Lambda 표현식을 사용하면 매우 높은 품질의 코드를 작성할 수 있습니다. 이 기사가 여러분에게 Java 8의 내부를 살펴보는 데 도움이 되기를 바랍니다. 행운을 빕니다 :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION