CodeGym/Java Blog/무작위의/Java의 람다 표현식에 대한 설명입니다. 예제와 작업이 있습니다. 1 부
John Squirrels
레벨 41
San Francisco

Java의 람다 표현식에 대한 설명입니다. 예제와 작업이 있습니다. 1 부

무작위의 그룹에 게시되었습니다
회원
이 기사는 누구를 위한 것입니까?
  • Java Core에 대해 이미 잘 알고 있다고 생각하지만 Java의 람다 식에 대해 전혀 모르는 사람들을 위한 것입니다. 또는 람다 식에 대해 들어봤지만 세부 정보가 부족할 수 있습니다.
  • 람다 식에 대해 어느 정도 이해하고 있지만 여전히 람다 식에 겁을 먹고 사용에 익숙하지 않은 사람들을 위한 것입니다.
Java의 람다 표현식에 대한 설명입니다.  예제와 작업이 있습니다.  파트 1 - 1이러한 범주 중 하나에 맞지 않으면 이 기사가 지루하거나 결함이 있거나 일반적으로 차 한잔이 아니라고 생각할 수 있습니다. 이 경우에는 자유롭게 다른 것으로 넘어가거나 해당 주제에 정통한 경우 의견에서 기사를 개선하거나 보완할 수 있는 방법에 대한 제안을 하십시오. 자료는 참신함은 고사하고 학문적 가치가 있다고 주장하지 않습니다. 정반대입니다. (어떤 사람들에게는) 복잡한 것을 가능한 한 간단하게 설명하려고 노력할 것입니다. Stream API에 대한 설명 요청으로 이 글을 쓰게 되었습니다. 나는 그것에 대해 생각했고 내 스트림 예제 중 일부는 람다 ​​식에 대한 이해 없이는 이해할 수 없을 것이라고 결정했습니다. 그래서 우리는 람다 식으로 시작할 것입니다. 이 기사를 이해하기 위해 알아야 할 사항은 무엇입니까?
  1. 다음과 같은 객체 지향 프로그래밍(OOP)을 이해해야 합니다.

    • 클래스, 개체 및 이들 간의 차이점
    • 인터페이스, 클래스와의 차이점, 인터페이스와 클래스 간의 관계
    • 메소드, 메소드 호출 방법, 추상 메소드(예: 구현이 없는 메소드), 메소드 매개변수, 메소드 인수 및 이를 전달하는 방법
    • 액세스 수정자, 정적 메소드/변수, 최종 메소드/변수;
    • 클래스와 인터페이스의 상속, 인터페이스의 다중 상속.
  2. Java Core에 대한 지식: 제네릭 유형(제네릭), 컬렉션(목록), 스레드.
자, 시작하겠습니다.

약간의 역사

람다 식은 함수형 프로그래밍에서 Java로, 수학에서 왔습니다. 20세기 중반 미국에서는 수학과 온갖 추상화를 무척 좋아했던 알론조 처치가 프린스턴 대학에서 일했습니다. 람다 미적분학을 발명한 사람은 알론조 처치였습니다. 람다 미적분은 처음에는 프로그래밍과 전혀 관련이 없는 일련의 추상적인 아이디어였습니다. Alan Turing 및 John von Neumann과 같은 수학자들은 동시에 Princeton University에서 근무했습니다. 모든 것이 하나로 합쳐졌습니다. Church는 람다 미적분학을 고안했습니다. Turing은 현재 "Turing machine"으로 알려진 추상 컴퓨팅 기계를 개발했습니다. 그리고 폰 노이만은 현대 컴퓨터의 기초가 된 컴퓨터 아키텍처(지금은 "폰 노이만 아키텍처"라고 함)를 제안했습니다. 당시 알론조 처치' 그의 아이디어는 그의 동료들의 작품만큼 잘 알려지지 않았습니다(순수 수학 분야는 제외). 그러나 조금 후에 John McCarthy (역시 Princeton University 졸업생이자 우리 이야기 당시 Massachusetts Institute of Technology 직원)는 Church의 아이디어에 관심을 갖게되었습니다. 1958년에 그는 이러한 아이디어를 바탕으로 최초의 함수형 프로그래밍 언어인 LISP를 만들었습니다. 그리고 58년 후, 함수형 프로그래밍의 아이디어가 Java 8로 유출되었습니다. 70년도 지나지 않았습니다... 솔직히 수학적 아이디어가 실제로 적용되는 데 걸린 가장 긴 시간은 아닙니다. Massachusetts Institute of Technology의 직원)이 Church의 아이디어에 관심을 갖게 되었습니다. 1958년에 그는 이러한 아이디어를 바탕으로 최초의 함수형 프로그래밍 언어인 LISP를 만들었습니다. 그리고 58년 후, 함수형 프로그래밍의 아이디어가 Java 8로 유출되었습니다. 70년도 지나지 않았습니다... 솔직히 수학적 아이디어가 실제로 적용되는 데 걸린 가장 긴 시간은 아닙니다. Massachusetts Institute of Technology의 직원)이 Church의 아이디어에 관심을 갖게 되었습니다. 1958년에 그는 이러한 아이디어를 바탕으로 최초의 함수형 프로그래밍 언어인 LISP를 만들었습니다. 그리고 58년 후, 함수형 프로그래밍의 아이디어가 Java 8로 유출되었습니다. 70년도 지나지 않았습니다... 솔직히 수학적 아이디어가 실제로 적용되는 데 걸린 가장 긴 시간은 아닙니다.

문제의 핵심

람다 식은 일종의 함수입니다. 일반적인 Java 메서드로 간주할 수 있지만 다른 메서드에 인수로 전달되는 고유한 기능이 있습니다. 좋아요. 숫자, 문자열, 고양이 뿐만 아니라 다른 메소드에도 전달이 가능해졌습니다! 언제 이것이 필요할 수 있습니까? 예를 들어 콜백 메서드를 전달하려는 경우 유용합니다. 즉, 우리가 호출하는 메서드가 필요한 경우 전달하는 다른 메서드를 호출할 수 있는 기능을 갖습니다. 즉, 특정 상황에서는 하나의 콜백을 전달하고 다른 상황에서는 다른 콜백을 전달할 수 있습니다. 콜백을 수신하는 메소드가 콜백을 호출하도록 합니다. 정렬은 간단한 예입니다. 다음과 같은 영리한 정렬 알고리즘을 작성한다고 가정합니다.
public void mySuperSort() {
    // We do something here
    if(compare(obj1, obj2) > 0)
    // And then we do something here
}
명령문 에서 메서드 if를 호출하고 compare()비교할 두 개체를 전달하고 이러한 개체 중 어느 것이 "더 큰" 개체인지 알고 싶습니다. 우리는 "더 큰" 것이 "더 작은" 것보다 먼저 온다고 가정합니다. 오름차순뿐만 아니라 내림차순으로 정렬하는 방법을 알고 있는 보편적인 방법을 작성하고 있기 때문에 "큰" 개체를 따옴표로 묶었습니다(이 경우 "큰" 개체는 실제로 "작은" 개체가 됩니다). , 그 반대). 정렬에 대한 특정 알고리즘을 설정하려면 이를 메서드에 전달할 메커니즘이 필요합니다 mySuperSort(). 그렇게 하면 메서드가 호출될 때 메서드를 "제어"할 수 있습니다. 물론 두 가지 별도의 메서드를 작성할 수 있습니다 mySuperSortAscend().mySuperSortDescend()- 오름차순 및 내림차순으로 정렬합니다. 또는 메서드에 일부 인수를 전달할 수 있습니다(예: 부울 변수, true인 경우 오름차순으로 정렬하고 false인 경우 내림차순으로 정렬). 하지만 문자열 배열 목록과 같은 복잡한 것을 정렬하려면 어떻게 해야 할까요? mySuperSort()우리의 방법은 이러한 문자열 배열을 정렬하는 방법을 어떻게 알 수 있습니까 ? 크기별로? 모든 단어의 누적 길이로? 아마도 배열의 첫 번째 문자열을 기준으로 알파벳순으로? 그리고 경우에 따라 배열 크기별로 배열 목록을 정렬해야 하고 다른 경우에는 각 배열에 있는 모든 단어의 누적 길이별로 정렬해야 하는 경우에는 어떻게 해야 할까요? 나는 당신이 이미 비교기에 대해 들었을 것으로 예상하고 이 경우 원하는 정렬 알고리즘을 설명하는 비교기 개체를 정렬 메서드에 전달하기만 하면 됩니다. 때문에 표준sort()방법은 와 동일한 원칙에 따라 구현되며 예제에서 mySuperSort()사용할 것입니다 .sort()
String[] array1 = {"Dota", "GTA5", "Halo"};
String[] array2 = {"I", "really", "love", "Java"};
String[] array3 = {"if", "then", "else"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

Comparator<;String[]> sortByLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
};

Comparator<String[]> sortByCumulativeWordLength = new Comparator<String[]>() {

    @Override
    public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }

        for (String s : o2) {
            length2 += s.length();
        }

        return length1 - length2;
    }
};

arrays.sort(sortByLength);
결과:
Dota GTA5 Halo
if then else
I really love Java
여기서 배열은 각 배열의 단어 수에 따라 정렬됩니다. 단어 수가 적은 배열은 "더 적은" 것으로 간주됩니다. 그래서 그것이 먼저 오는 이유입니다. 더 많은 단어가 있는 배열은 "더 큰" 것으로 간주되어 끝에 배치됩니다. sort()와 같은 다른 비교자를 메서드에 전달하면 sortByCumulativeWordLength다른 결과를 얻게 됩니다.
if then else
Dota GTA5 Halo
I really love Java
이제 are 배열은 배열 단어의 총 문자 수로 정렬됩니다. 첫 번째 배열에는 10개의 문자가 있고 두 번째 배열에는 12개, 세 번째 배열에는 15개가 있습니다. 비교기가 하나뿐인 경우 별도의 변수를 선언할 필요가 없습니다. 대신 메서드를 호출할 때 바로 익명 클래스를 만들 수 있습니다 sort(). 이 같은:
String[] array1 = {"Dota", "GTA5", "Halo"};
String[] array2 = {"I", "really", "love", "Java"};
String[] array3 = {"if", "then", "else"};

List<String[]> arrays = new ArrayList<>();

arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
첫 번째 경우와 같은 결과를 얻게 됩니다. 작업 1. 각 배열에 있는 단어 수의 오름차순이 아니라 내림차순으로 배열을 정렬하도록 이 예제를 다시 작성합니다. 우리는 이미 이 모든 것을 알고 있습니다. 객체를 메서드에 전달하는 방법을 알고 있습니다. 현재 필요한 것에 따라 다른 개체를 메서드에 전달할 수 있습니다. 그런 다음 구현한 메서드를 호출합니다. 이것은 다음과 같은 질문을 던집니다. 세상에 왜 여기에 람다 표현식이 필요한가요?  람다 식은 정확히 하나의 메서드를 가진 객체이기 때문입니다. "메서드 개체"처럼. 개체에 패키지된 메서드입니다. 약간 생소한 구문이 있습니다(나중에 자세히 설명). 이 코드를 다시 살펴보겠습니다.
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
여기서 우리는 배열 목록을 가져오고 그 sort()메서드를 호출합니다. 여기에 단일 compare()메서드가 있는 비교 객체를 전달합니다(이름은 중요하지 않습니다. 결국 이 객체의 유일한 메서드이므로 잘못될 수 없습니다). 이 메서드에는 작업할 두 개의 매개 변수가 있습니다. IntelliJ IDEA에서 작업하는 경우 다음과 같이 코드를 상당히 압축하는 제안을 보았을 것입니다.
arrays.sort((o1, o2) -> o1.length - o2.length);
이렇게 하면 6개의 라인이 하나의 짧은 라인으로 줄어듭니다. 6줄이 하나의 짧은 줄로 재작성됩니다. 뭔가 사라졌지만 중요한 건 아니었음을 장담합니다. 이 코드는 익명 클래스와 동일한 방식으로 작동합니다. 작업 2. 람다 식을 사용하여 작업 1의 솔루션을 다시 작성하는 방법을 추측해 보세요(최소한 IntelliJ IDEA에 익명 클래스를 람다 식으로 변환하도록 요청).

인터페이스에 대해 알아보자

원칙적으로 인터페이스는 단순히 추상 메서드 목록입니다. 인터페이스를 구현하는 클래스를 만들 때 클래스는 인터페이스에 포함된 메서드를 구현해야 합니다(또는 클래스를 추상화해야 함). 다양한 메서드가 있는 인터페이스(예:  List)가 있고 메서드가 하나만 있는 인터페이스(예: Comparatoror Runnable)가 있습니다. 단일 메서드가 없는 인터페이스(소위 와 같은 마커 인터페이스 Serializable)가 있습니다. 메서드가 하나만 있는 인터페이스를 기능적 인터페이스 라고도 합니다 . Java 8에서는 특별한 주석으로 표시됩니다.@FunctionalInterface. 람다 식의 대상 유형으로 적합한 것은 이러한 단일 메서드 인터페이스입니다. 위에서 말했듯이 람다 식은 개체에 래핑된 메서드입니다. 그리고 이러한 개체를 전달할 때 본질적으로 이 단일 메서드를 전달하는 것입니다. 메소드가 무엇인지는 신경 쓰지 않는다는 것이 밝혀졌습니다. 우리에게 중요한 유일한 것은 메소드 매개변수와 물론 메소드 본문입니다. 본질적으로 람다 식은 기능적 인터페이스의 구현입니다. 단일 메서드가 있는 인터페이스를 볼 때마다 익명 클래스를 람다로 다시 작성할 수 있습니다. 인터페이스에 하나 이상의 메서드가 있는 경우 람다 식은 작동하지 않으며 대신 익명 클래스 또는 일반 클래스의 인스턴스를 사용합니다. 이제 람다를 조금 파헤칠 시간입니다. :)

통사론

일반적인 구문은 다음과 같습니다.
(parameters) -> {method body}
즉, 메서드 매개 변수를 둘러싼 괄호, "화살표"(하이픈과 보다 큼 기호로 구성됨), 메서드 본문은 항상 그렇듯이 중괄호로 묶여 있습니다. 매개변수는 인터페이스 메소드에 지정된 매개변수에 해당합니다. List변수 유형이 컴파일러에 의해 명확하게 결정될 수 있는 경우(이 경우 개체가 ]를 사용하여 유형이 지정되기 때문에 문자열 배열로 작업하고 있음을 알고 있음) 해당 String[유형을 표시할 필요가 없습니다.
모호한 경우 유형을 표시하십시오. IDEA는 필요하지 않은 경우 회색으로 표시합니다.
이 Oracle 자습서 및 다른 곳 에서 자세한 내용을 읽을 수 있습니다 . 이것을 " 타겟 타이핑 "이라고 합니다. 원하는 대로 변수 이름을 지정할 수 있습니다. 인터페이스에 지정된 것과 동일한 이름을 사용할 필요가 없습니다. 매개변수가 없으면 빈 괄호만 표시합니다. 파라미터가 1개인 경우에는 괄호 없이 변수명만 기재하면 됩니다. 이제 매개변수를 이해했으므로 람다 표현식의 본문에 대해 논의할 시간입니다. 중괄호 안에는 일반 메서드와 마찬가지로 코드를 작성합니다. 코드가 한 줄로 구성된 경우 중괄호를 완전히 생략할 수 있습니다(if 문 및 for 루프와 유사). 한 줄 람다가 무언가를 반환하는 경우 다음을 포함할 필요가 없습니다.return성명. 그러나 중괄호를 사용하는 경우 return일반 메서드에서와 마찬가지로 문을 명시적으로 포함해야 합니다.

예 1.
() -> {}
가장 간단한 예입니다. 그리고 가장 무의미한 :), 아무 것도 하지 않기 때문입니다. 예 2.
() -> ""
또 다른 흥미로운 예입니다. 아무 것도 받지 않고 빈 문자열을 반환합니다( return불필요하므로 생략됨). 다음은 동일하지만 다음과 같습니다 return.
() -> {
    return "";
}
예 3. "Hello, World!" 람다 사용
() -> System.out.println("Hello, World!")
아무 것도 받지 않고 아무 것도 반환하지 않습니다( 메서드의 반환 유형이 이므로 return에 대한 호출 앞에 놓을 수 없습니다 ). 단순히 인사말을 표시합니다. 이는 인터페이스 구현에 이상적입니다 . 다음 예제가 더 완벽합니다. System.out.println()println()voidRunnable
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello, World!")).start();
    }
}
또는 다음과 같이:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello, World!"));
        t.start();
    }
}
Runnable또는 람다 식을 객체로 저장한 다음 생성자에게 전달할 수도 있습니다 Thread.
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello, World!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
람다식이 변수에 저장되는 순간을 자세히 살펴보자. 인터페이스 Runnable는 객체에 public void run()메서드가 있어야 한다고 알려줍니다. 인터페이스에 따르면 run메서드는 매개 변수를 사용하지 않습니다. 그리고 아무것도 반환하지 않습니다. 즉, 반환 유형은 입니다 void. 따라서 이 코드는 아무것도 가져오거나 반환하지 않는 메서드로 객체를 생성합니다. 이것은 Runnable인터페이스의 run()방법과 완벽하게 일치합니다. 이것이 우리가 이 람다 식을 변수에 넣을 수 있었던 이유입니다 Runnable.  예 4.
() -> 42
다시 말하지만, 아무것도 받지 않고 숫자 42를 반환합니다. 이러한 람다 식은 변수에 넣을 수 있습니다 Callable. 이 인터페이스에는 다음과 같은 메서드가 하나만 있기 때문입니다.
V call(),
여기서  V 는 반환 유형입니다(이 경우  int). 따라서 다음과 같이 람다 식을 저장할 수 있습니다.
Callable<Integer> c = () -> 42;
예 5. 여러 줄을 포함하는 람다 식
() -> {
    String[] helloWorld = {"Hello", "World!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
다시 말하지만 이것은 매개 변수가 없고 void반환 유형이 있는 람다 식입니다(문이 없기 때문 return).  실시예 6
x -> x
여기서 우리는 x변수를 가져와 반환합니다. 매개변수가 하나만 있는 경우 괄호를 생략할 수 있습니다. 다음은 동일하지만 괄호가 있습니다.
(x) -> x
다음은 명시적인 return 문이 있는 예입니다.
x -> {
    return x;
}
또는 다음과 같이 괄호와 return 문을 사용합니다.
(x) -> {
    return x;
}
또는 유형의 명시적 표시(따라서 괄호 사용):
(int x) -> x
실시예 7
x -> ++x
x1을 더한 후에야 반환합니다. 다음과 같이 람다를 다시 작성할 수 있습니다 .
x -> x + 1
두 경우 모두 return선택 사항이므로 문과 함께 매개 변수 및 메서드 본문 주변의 괄호를 생략합니다. 괄호와 return 문이 있는 버전은 예 6에 나와 있습니다. 예 8
(x, y) -> x % y
by 의 나눗셈의 나머지를 x취하고 반환 합니다 . 여기서는 매개변수 주변의 괄호가 필요합니다. 매개변수가 하나만 있는 경우에만 선택적입니다. 다음은 유형을 명시적으로 표시한 것입니다. yxy
(double x, int y) -> x % y
실시예 9
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
우리는 Cat객체, String이름 및 int 나이를 취합니다. 메서드 자체에서 전달된 이름과 나이를 사용하여 고양이에 변수를 설정합니다. 객체 cat는 참조 유형이므로 람다 식 외부에서 변경됩니다(전달된 이름과 나이를 얻음). 다음은 유사한 람다를 사용하는 약간 더 복잡한 버전입니다.
public class Main {

    public static void main(String[] args) {
        // Create a cat and display it to confirm that it is "empty"
        Cat myCat = new Cat();
        System.out.println(myCat);

        // Create a lambda
        Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);

        };

        // Call a method to which we pass the cat and lambda
        changeEntity(myCat, s);

        // Display the cat on the screen and see that its state has changed (it has a name and age)
        System.out.println(myCat);

    }

    private static <T extends HasNameAndAge>  void changeEntity(T entity, Settable<T> s) {
        s.set(entity, "Smokey", 3);
    }
}

interface HasNameAndAge {
    void setName(String name);
    void setAge(int age);
}

interface Settable<C extends HasNameAndAge> {
    void set(C entity, String name, int age);
}

class Cat implements HasNameAndAge {
    private String name;
    private int age;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
결과:
Cat{name='null', age=0}
Cat{name='Smokey', age=3}
보시 Cat다시피 개체에는 하나의 상태가 있었고 람다 식을 사용한 후 상태가 변경되었습니다. 람다 식은 제네릭과 완벽하게 결합됩니다. Dog또한 구현하는 클래스를 만들어야 하는 경우  람다 식을 변경하지 않고 메서드 에서 HasNameAndAge동일한 작업을 수행할 수 있습니다 . 작업 3. 숫자를 받아 부울 값을 반환하는 메서드로 기능 인터페이스를 작성합니다. 전달된 숫자가 13으로 나눌 수 있는 경우 true를 반환하는 람다 식과 같은 인터페이스 구현을 작성합니다 . 작업 4.Dogmain()두 개의 문자열을 사용하고 문자열도 반환하는 메서드를 사용하여 기능적 인터페이스를 작성합니다. 더 긴 문자열을 반환하는 람다 식과 같은 인터페이스 구현을 작성합니다. 작업 5. a, b 및 c의 세 가지 부동 소수점 숫자를 사용하고 부동 소수점 숫자도 반환하는 메서드를 사용하여 기능적 인터페이스를 작성합니다. 판별식을 반환하는 람다 식과 같은 인터페이스 구현을 작성합니다. 당신이 잊은 경우에, 그것은 D = b^2 — 4ac. 작업 6. 작업 5의 기능 인터페이스를 사용하여 의 결과를 반환하는 람다 식을 작성합니다 a * b^c. Java의 람다 표현식에 대한 설명입니다. 예제와 작업이 있습니다. 2 부
코멘트
  • 인기
  • 신규
  • 이전
코멘트를 남기려면 로그인 해야 합니다
이 페이지에는 아직 코멘트가 없습니다