JavaRush /Java Blog /Random-KO /Java의 람다 표현식에 대해 인기가 있습니다. 예제와 작업이 포함되어 있습니다. 2 부
Стас Пасинков
레벨 26
Киев

Java의 람다 표현식에 대해 인기가 있습니다. 예제와 작업이 포함되어 있습니다. 2 부

Random-KO 그룹에 게시되었습니다
이 글은 누구를 위한 글인가요?
  • 이 글의 첫 부분을 읽으시는 분들을 위해 ;

  • 이미 Java Core를 잘 알고 있다고 생각하지만 Java의 람다 표현식에 대해서는 전혀 모르는 사람들을 위한 것입니다. 아니면 이미 람다에 대해 들어봤지만 자세한 내용은 없을 수도 있습니다.

  • 람다 표현식을 어느 정도 이해하고 있지만 여전히 이를 사용하는 것이 두렵고 익숙하지 않은 사람들을 위한 것입니다.

외부 변수에 대한 액세스

이 코드는 익명 클래스로 컴파일됩니까?
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("Я - метод staticMethod(), и меня только-что кто-то вызвал!");
    }
}
메소드 staticMethod()가 비공개이더라도 메소드 내부에서 호출하는 것이 "접근 가능"하므로 main()메소드에서 생성된 람다 내부에서 호출하는 것도 가능합니다 main.

람다 표현식 코드가 실행되는 순간

이 질문이 너무 단순해 보일 수도 있지만 질문해 볼 가치가 있습니다. 람다 표현식 내부의 코드는 언제 실행됩니까? 생성되는 순간? 아니면 (아직 어디인지 알 수 없음) 호출되는 순간에? 확인하는 것은 매우 쉽습니다.
System.out.println("Запуск программы");

// много всякого разного codeа
// ...

System.out.println("Перед объявлением лямбды");

Runnable runnable = () -> System.out.println("Я - лямбда!");

System.out.println("После объявления лямбды");

// много всякого другого codeа
// ...

System.out.println("Перед передачей лямбды в тред");
new Thread(runnable).start();
디스플레이 출력:
Запуск программы
Перед объявлением лямбды
После объявления лямбды
Перед передачей лямбды в тред
Я - лямбда!
스레드가 생성된 후 프로그램 실행 프로세스가 실제 메서드 실행에 도달한 경우에만 람다 표현식 코드가 맨 마지막에 실행되었음을 알 수 있습니다 run(). 그리고 발표 당시에는 전혀 그렇지 않았습니다. 람다 표현식을 선언함으로써 우리는 해당 유형의 객체만 생성 Runnable하고 해당 메소드의 동작을 설명했습니다 run(). 방법 자체는 훨씬 나중에 시작되었습니다.

메소드 참조?

람다 자체와 직접적인 관련은 없지만 이 기사에서 이에 대해 몇 마디 언급하는 것이 논리적이라고 생각합니다. 특별한 작업을 수행하지 않고 단지 일부 메서드를 호출하는 람다 표현식이 있다고 가정해 보겠습니다.
x -> System.out.println(x)
그들은 그에게 뭔가를 건네주었고 х, 그것은 단순히 그에게 전화를 걸어 System.out.println()그를 그곳으로 지나갔습니다 х. 이 경우 필요한 메소드에 대한 링크로 대체할 수 있습니다. 이와 같이:
System.out::println
예, 끝에 괄호가 없습니다! 더 완전한 예:
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(x -> System.out.println(x));
마지막 줄에서는 forEach()인터페이스 객체를 받아들이는 메소드를 사용합니다 Consumer. 이것은 다시 한 가지 메소드만 있는 기능적 인터페이스입니다 void accept(T t). 따라서 하나의 매개변수를 취하는 람다 표현식을 작성합니다(인터페이스 자체에 입력되므로 매개변수의 유형을 표시하지 않고 호출될 것임을 나타냅니다 х). 람다 표현식의 본문에 코드를 작성합니다. 메소드가 호출될 때 실행됩니다 accept(). 여기서는 변수에 있는 내용을 화면에 표시합니다 х. 메소드 자체는 forEach()컬렉션의 모든 요소를 ​​거치며 Consumer전달된 인터페이스 개체(람다)의 메소드를 호출합니다 accept(). 컬렉션의 각 요소를 전달하는 위치입니다. 이미 말했듯이 이것은 필요한 메서드에 대한 참조로 대체할 수 있는 람다 표현식(단순히 다른 메서드 호출)입니다. 그러면 코드는 다음과 같습니다.
List<String> strings = new LinkedList<>();
strings.add("Mother");
strings.add("soap");
strings.add("frame");

strings.forEach(System.out::println);
가장 중요한 것은 메소드 (println()accept()). 메서드는 println()무엇이든 받아들일 수 있기 때문에(모든 기본 형식과 개체에 대해 오버로드됨) 람다 식 대신 forEach()메서드에 대한 참조만 전달할 수 있습니다 println(). 그런 다음 forEach()컬렉션의 각 요소를 가져와 직접 전달합니다. 이 메소드를 println()처음 접하는 분들을 위해 참고하세요. 메소드를 호출하지 않고 System.out.println()(단어 사이에 점을 사용하고 끝에 괄호를 사용하여) 이 메소드 자체에 대한 참조를 전달합니다.
strings.forEach(System.out.println());
컴파일 오류가 발생합니다. 호출하기 전에 forEach()Java는 자신이 호출되고 있음을 확인하고 System.out.println()반환되고 있음을 이해하며 이를 대기 중인 유형의 개체 에 전달하려고 void시도합니다 . voidforEach()Consumer

메소드 참조 사용을 위한 구문

매우 간단합니다.
  1. 정적 메서드에 대한 참조 전달NameКласса:: NameСтатическогоМетода?

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            strings.forEach(Main::staticMethod);
        }
    
        private static void staticMethod(String s) {
            // do something
        }
    }
  2. 기존 객체를 사용하여 비정적 메서드에 대한 참조 전달NameПеременнойСОбъектом:: method name

    public class Main {
        public static void main(String[] args) {
            List<String> strings = new LinkedList<>();
            strings.add("Mother");
            strings.add("soap");
            strings.add("frame");
    
            Main instance = new Main();
            strings.forEach(instance::nonStaticMethod);
        }
    
        private void nonStaticMethod(String s) {
            // do something
        }
    }
  3. 해당 메소드가 구현된 클래스를 사용하여 비정적 메소드에 대한 참조를 전달합니다.NameКласса:: method name

    public class Main {
        public static void main(String[] args) {
            List<User> users = new LinkedList<>();
            users.add(new User("Vasya"));
            users.add(new User("Коля"));
            users.add(new User("Петя"));
    
            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. 생성자에 링크 전달 NameКласса::new
    메소드 링크를 사용하는 것은 완전히 만족스러운 기성 메소드가 있고 이를 콜백으로 사용하려는 경우 매우 편리합니다. 이 경우 해당 메서드의 코드로 람다 식을 작성하거나 단순히 이 메서드를 호출하는 람다 식을 작성하는 대신 해당 메서드에 대한 참조를 전달하기만 하면 됩니다. 그게 다야.

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

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

정보를 어디서 얻었는지, 또 무엇을 읽어야 하는지

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION