1. 인터페이스

람다 함수가 무엇인지 이해하려면 먼저 인터페이스가 무엇인지 이해해야 합니다. 따라서 요점을 기억합시다.

인터페이스 클래스 개념의 변형입니다. 심하게 잘린 클래스라고 가정해 보겠습니다. 클래스와 달리 인터페이스는 자체 변수를 가질 수 없습니다(정적 변수 제외). 유형이 인터페이스인 객체도 만들 수 없습니다.

  • 클래스의 변수를 선언할 수 없습니다.
  • 개체를 만들 수 없습니다.

예:

interface Runnable
{
   void run();
}
표준 인터페이스의 예

인터페이스 사용

그렇다면 왜 인터페이스가 필요할까요? 인터페이스는 상속과 함께만 사용됩니다. 동일한 인터페이스는 다른 클래스에 의해 상속될 수 있습니다. 또는 말했듯이 — 클래스는 인터페이스를 구현합니다 .

클래스가 인터페이스를 구현하는 경우 인터페이스에 의해 선언되었지만 구현되지 않은 메서드를 구현해야 합니다. 예:

interface Runnable
{
   void run();
}

class Timer implements Runnable
{
   void run()
   {
      System.out.println(LocalTime.now());
   }
}

class Calendar implements Runnable
{
   void run()
   {
      var date = LocalDate.now();
      System.out.println("Today: " + date.getDayOfWeek());
   }
}

클래스 는 인터페이스를 Timer구현하므로 인터페이스 Runnable에 있는 모든 메서드를 내부에서 선언 Runnable하고 구현해야 합니다. 즉, 메서드 본문에 코드를 작성해야 합니다. 수업도 마찬가지입니다 Calendar.

그러나 이제 Runnable변수는 인터페이스를 구현하는 개체에 대한 참조를 저장할 수 있습니다 Runnable.

예:

암호 메모
Timer timer = new Timer();
timer.run();

Runnable r1 = new Timer();
r1.run();

Runnable r2 = new Calendar();
r2.run();

run()클래스 의 메서드가 호출 Timer됩니다 클래스 의 메서드가


호출 됩니다 클래스 의 메서드가 호출 됩니다 run()Timer


run()Calendar

해당 유형이 개체의 조상 클래스 중 하나인 한 항상 모든 유형의 변수에 개체 참조를 할당할 수 있습니다. Timer및 클래스 의 경우 및 와 Calendar같은 두 가지 유형이 있습니다 .ObjectRunnable

개체 참조를 변수에 할당하면 클래스 Object에서 선언된 메서드만 호출할 수 있습니다 Object. 그리고 개체 참조를 변수에 할당하면 Runnable해당 유형의 메서드를 호출할 수 있습니다 Runnable.

예 2:

ArrayList<Runnable> list = new ArrayList<Runnable>();
list.add (new Timer());
list.add (new Calendar());

for (Runnable element: list)
    element.run();

Timer이 코드는 및 Calendar개체에 완벽하게 잘 작동하는 실행 메서드가 있기 때문에 작동합니다 . 따라서 그들을 부르는 것은 문제가 되지 않습니다. 방금 두 클래스에 run() 메서드를 추가했다면 그렇게 간단한 방법으로 두 클래스를 호출할 수 없었을 것입니다.

기본적으로 Runnable인터페이스는 run 메서드를 넣는 곳으로만 ​​사용됩니다.



2. 분류

좀 더 실용적인 것으로 넘어 갑시다. 예를 들어 문자열 정렬을 살펴보겠습니다.

문자열 모음을 사전순으로 정렬하기 위해 Java에는 다음과 같은 훌륭한 방법이 있습니다.Collections.sort(collection);

이 정적 메서드는 전달된 컬렉션을 정렬합니다. 그리고 정렬 과정에서 요소를 교체해야 하는지 여부를 이해하기 위해 요소의 쌍별 비교를 수행합니다.

compareTo정렬하는 동안 이러한 비교는 모든 표준 클래스에 있는 ( Integer, String, ... ) 메서드를 사용하여 수행됩니다.

Integer 클래스의 compareTo() 메서드는 두 숫자의 값을 비교하는 반면 String 클래스의 compareTo() 메서드는 문자열의 알파벳 순서를 확인합니다.

따라서 숫자 모음은 오름차순으로 정렬되고 문자열 모음은 알파벳순으로 정렬됩니다.

대체 정렬 알고리즘

하지만 문자열을 알파벳순이 아니라 길이순으로 정렬하려면 어떻게 해야 할까요? 숫자를 내림차순으로 정렬하려면 어떻게 해야 할까요? 이 경우 어떻게 합니까?

이러한 상황을 처리하기 위해 클래스에는 두 개의 매개 변수가 있는 Collections다른 메서드가 있습니다 .sort()

Collections.sort(collection, comparator);

여기서 comparator는 정렬 작업 중에 컬렉션개체를 비교하는 방법을 알고 있는 특수 개체입니다 . 비교기라는 용어는 "비교하다"를 의미하는 compare 에서 파생된 영어 단어 comparator 에서 유래했습니다.

그렇다면 이 특별한 물건은 무엇일까요?

Comparator상호 작용

음, 모두 매우 간단합니다. sort()메소드의 두 번째 매개변수 유형은 다음과 같습니다.Comparator<T>

여기서 T는 컬렉션 에 있는 요소의 유형을 나타내는 유형 매개변수 이며 단일 메소드를 Comparator갖는 인터페이스입니다.int compare(T obj1, T obj2);

즉, 비교기 개체는 Comparator 인터페이스를 구현하는 모든 클래스의 개체입니다. Comparator 인터페이스는 매우 간단해 보입니다.

public interface Comparator<Type>
{
   public int compare(Type obj1, Type obj2);
}
Comparator 인터페이스 코드

메서드 compare()는 전달된 두 인수를 비교합니다.

메서드가 음수를 반환하면 obj1 < obj2. 메서드가 양수를 반환하면 obj1 > obj2. 메서드가 0을 반환하면 obj1 == obj2.

다음은 문자열을 길이로 비교하는 비교기 개체의 예입니다.

public class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length()obj2.length();
   }
}
StringLengthComparator수업 코드

문자열 길이를 비교하려면 한 길이에서 다른 길이를 뺍니다.

문자열을 길이별로 정렬하는 프로그램의 전체 코드는 다음과 같습니다.

public class Solution
{
   public static void main(String[] args)
   {
      ArrayList<String> list = new ArrayList<String>();
      Collections.addAll(list, "Hello", "how's", "life?");
      Collections.sort(list, new StringLengthComparator());
   }
}

class StringLengthComparator implements Comparator<String>
{
   public int compare (String obj1, String obj2)
   {
      return obj1.length()obj2.length();
   }
}
문자열을 길이별로 정렬


3. 구문 설탕

이 코드를 더 간결하게 작성할 수 있다고 생각하십니까? 기본적으로 유용한 정보를 포함하는 라인은 단 하나뿐입니다 — obj1.length() - obj2.length();.

그러나 코드는 메서드 외부에 존재할 수 없으므로 메서드를 추가해야 했고 compare()메서드를 저장하기 위해 새 클래스를 추가해야 했습니다 — StringLengthComparator. 그리고 변수의 유형도 지정해야 합니다... 모든 것이 올바른 것 같습니다.

하지만 이 코드를 더 짧게 만드는 방법이 있습니다. 우리는 당신을 위한 구문 설탕을 가지고 있습니다. 두 국자!

익명 내부 클래스

메서드 내에서 바로 비교기 코드를 작성할 수 main()있으며 컴파일러가 나머지 작업을 수행합니다. 예:

public class Solution
{
    public static void main(String[] args)
    {
        ArrayList<String> list = new ArrayList<String>();
        Collections.addAll(list, "Hello", "how's", "life?");

        Comparator<String> comparator = new Comparator<String>()
        {
            public int compare (String obj1, String obj2)
            {
                return obj1.length()obj2.length();
            }
        };

        Collections.sort(list, comparator);
    }
}
문자열을 길이순으로 정렬

Comparator명시적으로 클래스를 생성하지 않고도 인터페이스를 구현하는 객체를 생성할 수 있습니다 ! 컴파일러는 자동으로 생성하고 임시 이름을 지정합니다. 비교해보자:

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
};
익명 내부 클래스
Comparator<String> comparator = new StringLengthComparator();

class StringLengthComparator implements Comparator<String>
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() – obj2.length();
    }
}
StringLengthComparator수업

두 가지 다른 경우에 동일한 코드 블록을 나타내기 위해 동일한 색상이 사용됩니다. 그 차이는 실제로 아주 작습니다.

컴파일러가 첫 번째 코드 블록을 만나면 해당하는 두 번째 코드 블록을 생성하고 클래스에 임의의 이름을 지정합니다.


4. Java의 람다 식

코드에서 익명의 내부 클래스를 사용하기로 결정했다고 가정해 보겠습니다. 이 경우 다음과 같은 코드 블록이 있습니다.

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
    {
        return obj1.length() obj2.length();
    }
};
익명 내부 클래스

여기서는 변수 선언과 익명 클래스 생성을 결합합니다. 그러나이 코드를 더 짧게 만드는 방법이 있습니다. 예를 들면 다음과 같습니다.

Comparator<String> comparator = (String obj1, String obj2) ->
{
    return obj1.length()obj2.length();
};

여기에는 암시적 클래스 선언뿐 아니라 변수 생성도 있기 때문에 세미콜론이 필요합니다.

이와 같은 표기법을 람다 식이라고 합니다 .

컴파일러가 코드에서 이와 같은 표기법을 발견하면 익명의 내부 클래스를 사용하여 자세한 버전의 코드를 생성합니다.

람다 식을 작성할 때 클래스 이름뿐만 아니라 메서드 이름도 생략했습니다 .Comparator<String>int compare()

컴파일은 메서드를 결정하는 데 아무런 문제가 없습니다 . 람다 식은 단일 메서드가 있는 인터페이스에 대해서만 작성할 수 있기 때문입니다 . 그건 그렇고, 이 규칙을 우회하는 방법이 있지만 OOP를 더 깊이 연구하기 시작하면 그것에 대해 배우게 될 것입니다(우리는 기본 방법에 대해 이야기하고 있습니다).

자세한 버전의 코드를 다시 살펴보겠습니다. 하지만 람다 식을 작성할 때 생략할 수 있는 부분은 회색으로 표시하겠습니다.

Comparator<String> comparator = new Comparator<String>()
{
    public int compare (String obj1, String obj2)
   {
      return obj1.length()obj2.length();
   }
};
익명 내부 클래스

중요한 건 하나도 빠뜨리지 않은 것 같다. 실제로 Comparator인터페이스에 메서드가 하나만 있는 경우 compare()컴파일러는 나머지 코드에서 회색으로 표시된 코드를 완전히 복구할 수 있습니다.

정렬

그건 그렇고, 이제 다음과 같은 정렬 코드를 작성할 수 있습니다.

Comparator<String> comparator = (String obj1, String obj2) ->
{
   return obj1.length()obj2.length();
};
Collections.sort(list, comparator);

또는 다음과 같이 할 수도 있습니다.

Collections.sort(list, (String obj1, String obj2) ->
   {
      return obj1.length()obj2.length();
   }
);

comparator변수 에 할당된 값으로 변수를 즉시 대체했습니다 comparator.

유형 추론

하지만 그게 다가 아닙니다. 이 예제의 코드는 훨씬 더 간단하게 작성할 수 있습니다. 첫째, 컴파일러는 obj1obj2변수가 Strings. 둘째, 메서드 코드에 명령이 하나만 있는 경우 중괄호와 return 문도 생략할 수 있습니다.

단축 버전은 다음과 같습니다.

Comparator<String> comparator = (obj1, obj2) ->
   obj1.length()obj2.length();

Collections.sort(list, comparator);

변수를 사용하는 대신 comparator해당 값을 즉시 사용하면 다음 버전을 얻습니다.

Collections.sort(list, (obj1, obj2) ->  obj1.length()obj2.length() );

글쎄, 당신은 그것에 대해 어떻게 생각하세요? 불필요한 정보가 없는 단 한 줄의 코드 — 변수와 코드만 있습니다. 짧게 할 수 있는 방법은 없습니다! 아니면 있습니까?



5. 작동 방식

실제로 코드를 훨씬 더 간결하게 작성할 수 있습니다. 그러나 나중에 더 자세히 설명합니다.

단일 메서드와 함께 인터페이스 유형을 사용하는 람다 식을 작성할 수 있습니다 .

예를 들어 코드에서 메소드의 서명이 다음과 같기 때문에 람다 표현식을 작성할 수 있습니다 .Collections.sort(list, (obj1, obj2) -> obj1.length() - obj2.length());sort()

sort(Collection<T> colls, Comparator<T> comp)

콜렉션을 정렬 메소드의 첫 번째 인수로 전달했을 때 ArrayList<String>컴파일러는 두 번째 인수의 유형이 임을 확인할 수 있었습니다 . 그리고 이것으로부터 이 인터페이스는 단일 메소드를 갖는다는 결론을 내렸습니다. 다른 모든 것은 기술입니다.Comparator<String>int compare(String obj1, String obj2)