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

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

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

외부 변수에 대한 액세스

이 코드는 익명 클래스로 컴파일됩니까?

int counter = 0;
Runnable r = new Runnable() { 

    @Override 
    public void run() { 
        counter++;
    }
};
아니요. counter 변수는 이어야 합니다 final. 또는 그렇지 않은 final경우 최소한 값을 변경할 수 없습니다. 동일한 원칙이 람다 식에도 적용됩니다. 선언된 위치에서 "볼" 수 있는 모든 변수에 액세스할 수 있습니다. 그러나 람다는 이를 변경해서는 안 됩니다(새 값을 할당). 그러나 익명 클래스에서 이 제한을 우회하는 방법이 있습니다. 참조 변수를 만들고 개체의 내부 상태를 변경하기만 하면 됩니다. 이렇게 하면 변수 자체가 변경되지 않고(동일한 객체를 가리킴) 로 안전하게 표시될 수 있습니다 final.

final AtomicInteger counter = new AtomicInteger(0);
Runnable r = new Runnable() { 

    @Override
    public void run() {
        counter.incrementAndGet();
    }
};
여기서 counter변수는 개체에 대한 참조입니다 AtomicInteger. 그리고 이 incrementAndGet()메소드는 이 객체의 상태를 변경하는 데 사용됩니다. 프로그램이 실행되는 동안 변수 자체의 값은 변경되지 않습니다. 항상 동일한 개체를 가리키므로 final 키워드로 변수를 선언할 수 있습니다. 다음은 동일한 예제이지만 람다 식을 사용합니다.

int counter = 0;
Runnable r = () -> counter++;
이것은 익명 클래스가 있는 버전과 같은 이유로 컴파일되지 않습니다.  counter프로그램이 실행되는 동안 변경되지 않아야 합니다. 그러나 다음과 같이 하면 모든 것이 괜찮습니다.

final AtomicInteger counter = new AtomicInteger(0); 
Runnable r = () -> counter.incrementAndGet();
이는 메소드 호출에도 적용됩니다. 람다 식 내에서 모든 "보이는" 변수에 액세스할 수 있을 뿐만 아니라 액세스 가능한 메서드를 호출할 수도 있습니다.

public class Main { 

    public static void main(String[] args) {
        Runnable runnable = () -> staticMethod();
        new Thread(runnable).start();
    } 

    private static void staticMethod() { 

        System.out.println("I'm staticMethod(), and someone just called me!");
    }
}
비공개 이지만 메서드 staticMethod()내에서 액세스할 수 있으므로 메서드 main()에서 생성된 람다 내부에서도 호출할 수 있습니다 main.

람다 식은 언제 실행됩니까?

다음 질문이 너무 간단하다고 생각할 수도 있지만, 똑같이 물어봐야 합니다. 람다 식 내부의 코드는 언제 실행됩니까? 언제 생성됩니까? 아니면 호출될 때(아직 알려지지 않음)? 이것은 확인하기가 매우 쉽습니다.

System.out.println("Program start"); 

// All sorts of code here
// ...

System.out.println("Before lambda declaration");

Runnable runnable = () -> System.out.println("I'm a lambda!");

System.out.println("After lambda declaration"); 

// All sorts of other code here
// ...

System.out.println("Before passing the lambda to the thread");
new Thread(runnable).start(); 
화면 출력:

Program start
Before lambda declaration
After lambda declaration
Before passing the lambda to the thread
I'm a lambda!
스레드가 생성된 후 프로그램의 실행이 메서드에 도달했을 때만 람다 표현식이 맨 마지막에 실행되었음을 알 수 있습니다 run(). 확실히 그것이 선언되었을 때 아닙니다. 람다 식을 선언함으로써 Runnable객체를 생성하고 해당 메서드가 어떻게 run()작동하는지 설명했을 뿐입니다. 메서드 자체는 훨씬 나중에 실행됩니다.

방법 참조?

메서드 참조는 람다와 직접적인 관련이 없지만 이 기사에서 람다에 대해 몇 마디 말하는 것이 이치에 맞다고 생각합니다. 특별한 작업을 수행하지 않고 단순히 메서드를 호출하는 람다 식이 있다고 가정합니다.

x -> System.out.println(x)
그것 은 일부 를 수신 x하고 그냥 호출 하여 System.out.println()전달 합니다 x. 이 경우 원하는 메서드에 대한 참조로 바꿀 수 있습니다. 이와 같이:

System.out::println
맞습니다. 끝에 괄호가 없습니다! 다음은 더 완전한 예입니다.

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo"); 

strings.forEach(x -> System.out.println(x));
마지막 줄에서는 인터페이스 forEach()를 구현하는 객체를 취하는 메서드를 사용합니다 Consumer. 다시 말하지만 이것은 하나의 메서드만 있는 기능적 인터페이스입니다 void accept(T t). 따라서 우리는 매개변수가 하나인 람다 표현식을 작성합니다(인터페이스 자체에 유형이 지정되기 때문에 매개변수 유형을 지정하지 않고 호출할 것이라고만 나타냅니다 x). 람다 식의 본문에는 메서드가 호출될 때 실행될 코드를 작성합니다 accept(). 여기서 우리는 단순히 변수에서 끝난 것을 표시합니다 x. 이 동일한 forEach()메서드는 컬렉션의 모든 요소를 ​​반복하고 accept()구현에서 메서드를 호출합니다.Consumer인터페이스(람다), 컬렉션의 각 항목을 전달합니다. 내가 말했듯이, 원하는 메서드에 대한 참조로 이러한 람다 식(단순히 다른 메서드를 분류하는 것)을 바꿀 수 있습니다. 그러면 코드는 다음과 같습니다.

List<String> strings = new LinkedList<>(); 

strings.add("Dota"); 
strings.add("GTA5"); 
strings.add("Halo");

strings.forEach(System.out::println);
가장 중요한 것은 println()accept()메소드의 매개변수가 일치한다는 것입니다. 메서드는 무엇이든 허용할 수 있기 때문에 println()(모든 기본 유형과 모든 개체에 대해 오버로드됨) 람다 식 대신 메서드에 대한 참조를 에 간단히 전달할 수 println()있습니다 forEach(). 그런 다음 forEach()컬렉션의 각 요소를 가져와 메서드에 직접 전달합니다 println(). 이 문제를 처음 접하는 사람은 우리가 호출하지 않는다는 점에 유의하십시오 System.out.println()(단어 사이에 점을 사용하고 끝에 괄호를 사용). 대신 이 메서드에 대한 참조를 전달합니다. 우리가 이것을 쓰면

strings.forEach(System.out.println());
컴파일 오류가 발생합니다. 를 호출하기 전에 forEach()Java는 그것이 System.out.println()호출되고 있음을 확인하므로 반환 값이 임을 이해 하고 대신 개체를 기대하는 에 전달 void을 시도할 것입니다 . voidforEach()Consumer

메서드 참조 구문

매우 간단합니다.
  1. 다음과 같이 정적 메서드에 대한 참조를 전달합니다.ClassName::staticMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>(); 
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            strings.forEach(Main::staticMethod); 
        } 
    
        private static void staticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  2. 다음과 같이 기존 개체를 사용하여 비정적 메서드에 대한 참조를 전달합니다.objectName::instanceMethodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<String> strings = new LinkedList<>();
            strings.add("Dota"); 
            strings.add("GTA5"); 
            strings.add("Halo"); 
    
            Main instance = new Main(); 
            strings.forEach(instance::nonStaticMethod); 
        } 
    
        private void nonStaticMethod(String s) { 
    
            // Do something 
        } 
    }
    
  3. 다음과 같이 이를 구현하는 클래스를 사용하여 비정적 메소드에 대한 참조를 전달합니다.ClassName::methodName

    
    public class Main { 
    
        public static void main(String[] args) { 
    
            List<User> users = new LinkedList<>(); 
            users.add (new User("John")); 
            users.add(new User("Paul")); 
            users.add(new User("George")); 
    
            users.forEach(User::print); 
        } 
    
        private static class User { 
            private String name; 
    
            private User(String name) { 
                this.name = name; 
            } 
    
            private void print() { 
                System.out.println(name); 
            } 
        } 
    }
    
  4. 다음과 같이 생성자에 대한 참조를 전달합니다.ClassName::new

    콜백으로 완벽하게 작동하는 메서드가 이미 있는 경우 메서드 참조가 매우 편리합니다. 이 경우 메서드의 코드를 포함하는 람다 식을 작성하거나 단순히 메서드를 호출하는 람다 식을 작성하는 대신 단순히 참조를 전달합니다. 그리고 그게 다야.

익명 클래스와 람다 식의 흥미로운 차이점

익명 클래스에서 this키워드는 익명 클래스의 개체를 가리킵니다. 그러나 이것을 람다 내에서 사용하면 포함하는 클래스의 개체에 액세스할 수 있습니다. 우리가 실제로 람다 식을 작성한 곳입니다. 이는 람다 식이 작성된 클래스의 전용 메서드로 컴파일되기 때문에 발생합니다. 이 "기능"에는 부작용이 있고 함수형 프로그래밍의 원칙에 위배되기 때문에 이 "기능"을 사용하지 않는 것이 좋습니다. 즉, 이 접근 방식은 OOP와 완전히 일치합니다. ;)

내 정보는 어디서 얻었으며 그 밖에 무엇을 읽어야 합니까?

그리고 물론 Google에서 수많은 자료를 찾았습니다. :)
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION