수색…


소개

람다 식은 표현식을 사용하여 단일 메서드 인터페이스를 구현하는 명확하고 간결한 방법을 제공합니다. 이것들을 사용하면 생성하고 유지 보수해야하는 코드의 양을 줄일 수 있습니다. 익명 클래스와 비슷하지만, 타입 정보는 가지고 있지 않습니다. 유형 유추가 필요합니다.

메소드 참조는 표현식이 아닌 기존 메소드를 사용하여 기능적 인터페이스를 구현합니다. 그들은 또한 람다 가족에 속합니다.

통사론

  • () -> {return expression; } // 값을 반환하는 함수 본문으로 0을 반환합니다.
  • () -> expression // 위의 선언에 대한 단축 표현; 표현식에는 세미콜론이 없습니다.
  • () -> {function-body} // 람다 식에서 작업을 수행하기위한 부작용.
  • parameterName -> expression // 단일 - 성 람다 식. 인수가 하나 뿐인 람다 식에서는 괄호를 제거 할 수 있습니다.
  • (type parameterName, Type secondParameterName, ...) -> expression // 매개 변수가 왼쪽에 나열된 표현식을 평가하는 lambda
  • (parameterName, secondParameterName, ...) -> expression // 매개 변수 이름에 대한 매개 변수 유형을 제거하는 약식. 지정된 매개 변수 목록 크기가 예상되는 기능적 인터페이스의 크기 중 하나와 일치하는 컴파일러에서 추론 할 수있는 컨텍스트에서만 사용할 수 있습니다.

람다 식을 사용하여 컬렉션 정렬

목록 정렬

자바 (8) 이전에, 그것을 구현하기 위해 필요했다 java.util.Comparator 목록 (1)를 정렬 할 때 익명 (또는 이름) 클래스와 인터페이스를 :

Java SE 1.2
List<Person> people = ...
Collections.sort(
    people,
    new Comparator<Person>() {
        public int compare(Person p1, Person p2){
            return p1.getFirstName().compareTo(p2.getFirstName());
        }
    }
);

Java 8부터는 익명 클래스를 람다 식으로 대체 할 수 있습니다. 컴파일러가 자동으로 유추하기 때문에 매개 변수 p1p2 의 유형을 생략 할 수 있습니다.

Collections.sort(
    people, 
    (p1, p2) -> p1.getFirstName().compareTo(p2.getFirstName())
);

이 예제는 Comparator.comparing:: (이중 콜론) 기호를 사용하여 표현 된 메서드 참조 를 사용하여 단순화 할 수 있습니다.

Collections.sort(
    people,
    Comparator.comparing(Person::getFirstName)
);

정적 가져 오기를 사용하면 더 간결하게 표현할 수 있지만 전반적인 가독성이 향상되는지 여부는 논쟁의 여지가 있습니다.

import static java.util.Collections.sort;
import static java.util.Comparator.comparing;
//...
sort(people, comparing(Person::getFirstName));

이 방법으로 만들어진 비교기도 함께 연결할 수 있습니다. 예를 들어 이름을 다른 사람과 비교 한 후 이름이 같은 사람이 있으면 thenComparing 메서드도 성으로 비교합니다.

sort(people, comparing(Person::getFirstName).thenComparing(Person::getLastName));

1 - Collections.sort (...)는 List 부속 유형 인 콜렉션에서만 작동합니다. SetCollection API는 요소의 순서를 의미하지 않습니다.

지도 정렬

유사한 방식으로 HashMap 의 항목을 HashMap 으로 정렬 할 수 있습니다. LinkedHashMap 가 타겟으로서 사용되지 않으면 안됩니다. 통상의 HashMap 의 키는 순서 붙일 수 없습니다.

Map<String, Integer> map = new HashMap();  // ... or any other Map class
// populate the map
map = map.entrySet()
    .stream()
    .sorted(Map.Entry.<String, Integer>comparingByValue())
    .collect(Collectors.toMap(k -> k.getKey(), v -> v.getValue(),
                              (k, v) -> k, LinkedHashMap::new));

Java lambdas 소개

기능 인터페이스

람다 (Lambdas)는 단 하나의 추상 메소드를 가진 인터페이스 인 기능적 인터페이스에서만 작동 할 수 있습니다. 기능 인터페이스에는 default 또는 static 메소드가 여러 개있을 수 있습니다. (이러한 이유 때문에 때때로 Single Abstract Method Interfaces 또는 SAM Interfaces라고도 함).

interface Foo1 {
    void bar();
}

interface Foo2 {
    int bar(boolean baz);
}

interface Foo3 {
    String bar(Object baz, int mink);
}

interface Foo4 {
    default String bar() { // default so not counted
        return "baz";
    }
    void quux();
}

기능 인터페이스를 선언 할 때 @FunctionalInterface 주석을 추가 할 수 있습니다. 이것은 특별한 영향을 미치지 않지만,이 주석이 기능적이지 않은 인터페이스에 적용되면 인터페이스가 변경되지 않아야한다는 것을 상기시키는 역할을하는 컴파일러 오류가 생성됩니다.

@FunctionalInterface
interface Foo5 {
    void bar();
}

@FunctionalInterface
interface BlankFoo1 extends Foo3 { // inherits abstract method from Foo3
}

@FunctionalInterface
interface Foo6 {
    void bar();
    boolean equals(Object obj); // overrides one of Object's method so not counted
}

반대로, 이것은 하나 이상의 추상적 인 메소드를 가지고 있기 때문에 기능적인 인터페이스가 아닙니다 :

interface BadFoo {
    void bar();
    void quux(); // <-- Second method prevents lambda: which one should 
                 // be considered as lambda?
}

또한 메소드가 없으므로 기능적 인터페이스가 아닙니다.

interface BlankFoo2 { }

다음 사항에 유의하십시오. 네가 가지고 있다고 가정 해보자.

interface Parent { public int parentMethod(); }

interface Child extends Parent { public int ChildMethod(); }

그런 다음 두 가지 지정된 메소드가 있으므로 Child 기능 인터페이스가 될 수 없습니다 .

Java 8은 또한 java.util.function 패키지에 많은 일반 템플릿 기능 인터페이스를 제공합니다. 예를 들어, 내장 인터페이스 Predicate<T> 는 형식 T 의 값을 입력하고 boolean 반환하는 단일 메서드를 래핑합니다.


람다 식

람다 식의 기본 구조는 다음과 같습니다.

FunctionalInterface fi = () -> System.out.println ( "Hello");

fiFunctionalInterface 를 구현하는 익명 클래스와 비슷한 클래스의 싱글 톤 인스턴스를 보유 할 것이고, 한 메소드의 정의는 { System.out.println("Hello"); } . 즉 위의 내용은 다음과 거의 동일합니다.

FunctionalInterface fi = new FunctionalInterface() {
    @Override
    public void theOneMethod() {
        System.out.println("Hello");
    }
};

람다는 람다에서 this , super 또는 toString() 과 같은 표현식의 의미가 새롭게 생성 된 객체가 아니라 할당이 이루어지는 클래스를 참조하기 때문에 람다는 익명 클래스와 "거의 동등"합니다.

함수 인터페이스에는 하나의 추상 메소드가 있어야하기 때문에 람다를 사용할 때 메소드의 이름을 지정할 수는 없지만 그렇게해야 할 필요는 없습니다. Java가이를 오버라이드하므로

람다 유형이 확실하지 않은 경우 (예 : 오버로드 된 메소드) 람다에 캐스트를 추가하여 컴파일러에 유형을 지정하십시오. 예를 들면 다음과 같습니다.

Object fooHolder = (Foo1) () -> System.out.println("Hello");
System.out.println(fooHolder instanceof Foo1); // returns true

함수 인터페이스의 단일 메소드가 매개 변수를 사용하면 이들의 로컬 공식 이름은 람다의 대괄호 사이에 나타나야합니다. 매개 변수 유형을 선언 할 필요가 없으며 매개 변수 유형을 인터페이스에서 가져 왔기 때문에 리턴 할 수 있습니다 (원하는 경우 매개 변수 유형을 선언하는 것은 오류가 아 U니다). 따라서이 두 예제는 동일합니다.

Foo2 longFoo = new Foo2() {
    @Override
    public int bar(boolean baz) {
        return baz ? 1 : 0;
    }
};
Foo2 shortFoo = (x) -> { return x ? 1 : 0; };

함수가 하나의 인수 만 갖는 경우 인수 주위의} 호를 생략 할 수 있습니다.

Foo2 np = x -> { return x ? 1 : 0; }; // okay
Foo3 np2 = x, y -> x.toString() + y // not okay

암시 적 반환 값

람다 안에있는 코드가 명령문이 아닌 Java 표현식 이면 표현식의 값을 반환하는 메소드로 처리됩니다. 따라서 다음 2 개는 동일합니다.

IntUnaryOperator addOneShort = (x) -> (x + 1);
IntUnaryOperator addOneLong = (x) -> { return (x + 1); };

로컬 변수 액세스 (클로저 값)

람다는 익명 클래스에 대한 구문 상 축약 형이므로, 둘러싸는 범위에서 로컬 변수에 액세스하는 것과 동일한 규칙을 따릅니다. 변수는 final 로 취급되어야하며 람다 내부에서는 수정되지 않아야합니다.

IntUnaryOperator makeAdder(int amount) {
    return (x) -> (x + amount); // Legal even though amount will go out of scope
                                // because amount is not modified
}

IntUnaryOperator makeAccumulator(int value) {
    return (x) -> { value += x; return value; }; // Will not compile
}

이런 식으로 변화하는 변수를 감쌀 필요가 있다면, 변수의 복사본을 유지하는 정규 객체가 사용되어야합니다. 람다식이 있는 Java Closure 에서 자세히 읽으십시오 .


람다 수용

람다는 인터페이스의 구현이기 때문에 메소드가 람다를 받아들이도록하기 위해 특별히 할 필요는 없습니다 : 함수 인터페이스를 취하는 모든 함수는 람다를 받아 들일 수 있습니다.

public void passMeALambda(Foo1 f) {
    f.bar();
}
passMeALambda(() -> System.out.println("Lambda called"));

람다 식의 유형

람다 식은 그 자체로는 특정 유형을 가지고 있지 않습니다. 매개 변수의 유형 및 수와 반환 값의 유형이 일부 유형 정보를 전달할 수있는 것은 사실이지만 이러한 정보는 할당 할 수있는 유형 만 제한합니다. lambda는 다음 중 한 가지 방법으로 기능 인터페이스 유형에 할당 될 때 유형을 수신합니다.

  • 기능 유형에 직접 지정 (예 : myPredicate = s -> s.isEmpty()
  • 기능적 유형을 가진 매개 변수로 전달합니다 stream.filter(s -> s.isEmpty()) 예 : stream.filter(s -> s.isEmpty())
  • 함수 유형을 반환하는 함수에서 반환합니다 (예 : return s -> s.isEmpty()
  • 기능 유형 (예 : (Predicate<String>) s -> s.isEmpty()

기능 유형에 대한 그러한 할당이 이루어지기 전까지는 람다는 명확한 유형을 가지고 있지 않습니다. 설명하기 위해, 람다 표현식 o -> o.isEmpty() 고려하십시오. 동일한 λ 식은 다양한 기능 유형에 할당 될 수 있습니다.

Predicate<String> javaStringPred = o -> o.isEmpty();
Function<String, Boolean> javaFunc = o -> o.isEmpty();
Predicate<List> javaListPred = o -> o.isEmpty();
Consumer<String> javaStringConsumer = o -> o.isEmpty(); // return value is ignored!
com.google.common.base.Predicate<String> guavaPredicate = o -> o.isEmpty();

이것들이 할당되었으므로, 람다 표현식이 동일하게 보였지만 서로 할당 될 수는 없지만 표시된 예제는 완전히 다른 유형입니다.

방법 참조

메서드 참조를 사용하면 익명의 람다 식 대신 인수로 전달할 수있는 호환 가능한 기능 인터페이스를 준수하는 미리 정의 된 정적 또는 인스턴스 메서드를 사용할 수 있습니다.

모델이 있다고 가정합니다.

class Person {
    private final String name;
    private final String surname;

    public Person(String name, String surname){
        this.name = name;
        this.surname = surname;
    }

    public String getName(){ return name; }
    public String getSurname(){ return surname; }
}

List<Person> people = getSomePeople();

인스턴스 메소드 참조 (임의의 인스턴스로)

people.stream().map(Person::getName)

등가 람다 :

people.stream().map(person -> person.getName())

이 예에서는 Person 유형의 getName() 인스턴스 메소드에 대한 메소드 참조가 전달됩니다. 콜렉션 유형으로 알려져 있기 때문에 나중에 알려진 메소드의 메소드가 호출됩니다.


인스턴스 메소드 참조 (특정 인스턴스로)

people.forEach(System.out::println);

System.outPrintStream 의 인스턴스이므로,이 특정 인스턴스에 대한 메소드 참조가 인수로 전달됩니다.

등가 람다 :

people.forEach(person -> System.out.println(person));

정적 메서드 참조

또한 스트림을 변환하기 위해 정적 메소드에 대한 참조를 적용 할 수 있습니다.

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
numbers.stream().map(String::valueOf)

이 예제에서는 String 유형에 대한 정적 valueOf() 메서드에 대한 참조를 전달합니다. 따라서 컬렉션의 인스턴스 객체는 valueOf() 인수로 전달됩니다.

등가 람다 :

 numbers.stream().map(num -> String.valueOf(num))

생성자에 대한 참조

List<String> strings = Arrays.asList("1", "2", "3");
strings.stream().map(Integer::new)

컬렉션 요소를 수집하는 방법을 보려면 컬렉션 요소를 스트림으로 수집을 읽어보십시오.

Integer 형의 단일의 String 인수 생성자가 인수로서 제공된 캐릭터 라인을 지정해 정수를 구축하기 위해서 여기서 사용되고 있습니다. 이 경우 문자열이 숫자를 나타내는 한 스트림은 정수로 매핑됩니다. 등가 람다 :

strings.stream().map(s -> new Integer(s));

컨닝 지

메소드 참조 형식 암호 동등한 람다
정적 메소드 TypeName::method (args) -> TypeName.method(args)
비 정적 메소드 (인스턴스 *에서 ) instance::method (args) -> instance.method(args)
비 정적 메소드 (인스턴스 없음) TypeName::method (instance, args) -> instance.method(args)
생성자 ** TypeName::new (args) -> new TypeName(args)
배열 생성자 TypeName[]::new (int size) -> new TypeName[size]

* instanceinstance 에 대한 참조로 평가되는 모든 표현식이 될 수 있습니다 getInstance()::method 예 : getInstance()::method , this::method

** TypeName 이 비 정적 인 내부 클래스 인 경우, 생성자 참조는 외부 클래스 인스턴스의 범위 내에서만 유효합니다

다중 인터페이스 구현하기

때로는 둘 이상의 인터페이스를 구현하는 람다 표현식을 원할 수 있습니다. 이는 마커 인터페이스 (예 : java.io.Serializable )가 추상 메소드를 추가하지 않기 때문에 주로 유용합니다.

예를 들어, 당신은 만들 TreeSet 정의와 Comparator 다음 직렬화 및 네트워크를 통해 보낼 수 있습니다. 사소한 접근법 :

TreeSet<Long> ts = new TreeSet<>((x, y) -> Long.compare(y, x));

비교기의 람다가 Serializable 구현하지 않기 때문에 작동하지 않습니다. 교차 타입을 사용하고이 람다를 직렬화 할 필요가 있음을 명시 적으로 지정하여이 문제를 해결할 수 있습니다.

TreeSet<Long> ts = new TreeSet<>(
    (Comparator<Long> & Serializable) (x, y) -> Long.compare(y, x));

거의 모든 것이 직렬화 가능 해야하는 Apache Spark 와 같은 프레임 워크를 사용하는 경우와 같이 교차 유형을 자주 사용하는 경우 빈 인터페이스를 만들어 대신 코드에서 사용할 수 있습니다.

public interface SerializableComparator extends Comparator<Long>, Serializable {}

public class CustomTreeSet {
  public CustomTreeSet(SerializableComparator comparator) {}
}

이렇게하면 전달 된 비교자가 직렬화 될 수 있습니다.

람다와 실행 주위 패턴

간단한 시나리오에서 lambdas를 FunctionalInterface로 사용하는 몇 가지 좋은 예가 있습니다. 람다가 개선 할 수있는 일반적인 사용 사례는 Execute-Around 패턴입니다. 이 패턴에는 유스 케이스 별 코드를 둘러싼 여러 시나리오에 필요한 표준 설정 / 분해 코드 세트가 있습니다. 몇 가지 일반적인 예는 파일 io, 데이터베이스 io, try / catch 블록입니다.

interface DataProcessor {
    void process( Connection connection ) throws SQLException;;
}

public void doProcessing( DataProcessor processor ) throws SQLException{
    try (Connection connection = DBUtil.getDatabaseConnection();) {
        processor.process(connection);
        connection.commit();
    } 
}

그런 다음이 메서드를 람다 (lambda)와 함께 호출하면 다음과 같이 보일 수 있습니다.

public static void updateMyDAO(MyVO vo) throws DatabaseException {
    doProcessing((Connection conn) -> MyDAO.update(conn, ObjectMapper.map(vo)));
}

이것은 I / O 작업에만 국한되지 않습니다. 유사한 설정 / 해체 작업을 사소한 변형으로 적용 할 수있는 모든 시나리오에 적용 할 수 있습니다. 이 패턴의 주요 이점은 코드를 재사용하고 DRY (반복하지 말 것)를 시행하는 것입니다.

자신의 함수 인터페이스로 람다 식 사용하기

Lambda는 단일 메소드 인터페이스에 대한 인라인 구현 코드와 일반 변수로 수행 한 것처럼 전달할 수있는 기능을 제공합니다. 우리는 그것들을 기능적 인터페이스라고 부릅니다.

예를 들어, 익명 클래스에 Runnable을 작성해, Thread를 기동하면 (자) 다음과 같이됩니다.

//Old way
new Thread(
        new Runnable(){
            public void run(){
                System.out.println("run logic...");
            }
        }
).start();

//lambdas, from Java 8
new Thread(
        ()-> System.out.println("run logic...")
).start();

이제 위와 같이하면 사용자 정의 인터페이스가 있다고 가정 해 보겠습니다.

interface TwoArgInterface {
    int operate(int a, int b);
}

당신의 코드에서이 인터페이스의 구현을 제공하기 위해 어떻게 lambda를 사용합니까? 위에 표시된 Runnable 예제와 동일합니다. 아래의 드라이버 프로그램을 참조하십시오 :

public class CustomLambda {
    public static void main(String[] args) {

        TwoArgInterface plusOperation = (a, b) -> a + b;
        TwoArgInterface divideOperation = (a,b)->{
            if (b==0) throw new IllegalArgumentException("Divisor can not be 0");
            return a/b;
        };

        System.out.println("Plus operation of 3 and 5 is: " + plusOperation.operate(3, 5));
        System.out.println("Divide operation 50 by 25 is: " + divideOperation.operate(50, 25));

    }
}

`return`은 외부 메소드가 아니라 람다에서만 리턴합니다.

return 메서드는 람다에서만 반환되며 외부 메서드에서는 반환되지 않습니다.

스칼라와 코 틀린과는 다른 점에 유의하십시오!

void threeTimes(IntConsumer r) {
  for (int i = 0; i < 3; i++) {
    r.accept(i);
  }
}

void demo() {
  threeTimes(i -> {
    System.out.println(i);
    return; // Return from lambda to threeTimes only!
  });
}

등의 내장 구조에서와 같이 자신의 언어 구조를 작성하려고 할 때 예기치 않은 동작이 발생할 수 있습니다 for 루프가 return 다르게 작동합니다 :

void demo2() {
  for (int i = 0; i < 3; i++) {
    System.out.println(i);
    return; // Return from 'demo2' entirely
  }
}

Scala와 Kotlin에서 demodemo demo2 는 모두 0 인쇄합니다. 그러나 이것은 더 일관성이 없습니다 . Java 접근 방식은 리팩토링과 클래스 사용과 일관성이 있습니다. 즉 코드 맨 위의 return 이며 아래 코드는 동일하게 작동합니다.

void demo3() {
  threeTimes(new MyIntConsumer());
}

class MyIntConsumer implements IntConsumer {
  public void accept(int i) {
    System.out.println(i);
    return;
  }
}

따라서, 자바 return 더 클래스 메소드와 리팩토링과 일치하지만,에 이하 forwhile 내장 매크로,이 특별한 남아있다.

이 때문에 다음 두 가지는 Java에서 동일합니다.

IntStream.range(1, 4)
    .map(x -> x * x)
    .forEach(System.out::println);
IntStream.range(1, 4)
    .map(x -> { return x * x; })
    .forEach(System.out::println);

게다가, Java에서 try-with-resources를 사용하는 것이 안전합니다.

class Resource implements AutoCloseable {
  public void close() { System.out.println("close()"); }
}

void executeAround(Consumer<Resource> f) {
  try (Resource r = new Resource()) {
    System.out.print("before ");
    f.accept(r);
    System.out.print("after ");
  }
}

void demo4() {
  executeAround(r -> {
    System.out.print("accept() ");
    return; // Does not return from demo4, but frees the resource.
  });
}

before accept() after close() 인쇄 할 것입니다. Scala와 Kotlin 의미에서, try-with-resources는 닫히지 않을 것이지만, before accept() 인쇄 될 것이다.

람다식이있는 Java Closure.

람다 클로저는 람다식이 범위를 둘러싼 (글로벌 또는 로컬) 변수를 참조 할 때 만들어집니다. 이 작업을 수행하는 규칙은 인라인 메서드 및 익명 클래스의 규칙과 동일합니다.

람다 안에서 사용되는 엔 클로징 스코프의 로컬 변수final 이어야합니다. Java 8 (람다를 지원하는 가장 초기 버전)을 사용하면 외부 컨텍스트에서 final 선언 할 필요는 없지만 그렇게 처리해야합니다. 예 :

int n = 0; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};

n 변수의 값이 변경되지 않는 한 유효합니다. 람다 내부 또는 외부에서 변수를 변경하려고하면 다음과 같은 컴파일 오류가 발생합니다.

"람다 식에서 참조 된 지역 변수는 최종적 이거나 효과적 인 최종 변수 여야합니다."

예 :

int n = 0;
Runnable r = () -> { // Using lambda
    int i = n;
    // do something
};
n++; // Will generate an error.

람다 내에서 변경 변수를 사용해야하는 경우 일반적인 방법은 변수의 final 사본을 선언하고 사본을 사용하는 것입니다. 예를 들어

int n = 0;
final int k = n; // With Java 8 there is no need to explicit final
Runnable r = () -> { // Using lambda
    int i = k;
    // do something
};
n++;      // Now will not generate an error
r.run();  // Will run with i = 0 because k was 0 when the lambda was created

당연히 람다의 본문에는 원래 변수의 변경 내용이 표시되지 않습니다.

Java는 true closure를 지원하지 않습니다. 자바 람다는 인스턴스화 된 환경에서 변경 사항을 볼 수있는 방식으로 생성 할 수 없습니다. 환경을 관찰하거나 환경을 변경하는 클로저를 구현하려면 일반 클래스를 사용하여 시뮬레이션해야합니다. 예 :

// Does not compile ...
public IntUnaryOperator createAccumulator() {
    int value = 0;
    IntUnaryOperator accumulate = (x) -> { value += x; return value; };
    return accumulate;
}

위의 예제는 이전에 설명한 이유로 컴파일되지 않습니다. 다음과 같이 컴파일 오류를 해결할 수 있습니다.

// Compiles, but is incorrect ...
public class AccumulatorGenerator {
    private int value = 0;

    public IntUnaryOperator createAccumulator() {
        IntUnaryOperator accumulate = (x) -> { value += x; return value; };
        return accumulate;
    }
}

문제는 인스턴스가 기능적이어야하며 상태를 IntUnaryOperator 인터페이스의 디자인 계약을 IntUnaryOperator 한다는 것입니다. 그러한 클로저가 기능 객체를 받아들이는 내장 함수에 전달되면 충돌 또는 잘못된 동작이 발생할 수 있습니다. 변경 가능한 상태를 캡슐화하는 클로저는 일반 클래스로 구현되어야합니다. 예를 들어.

// Correct ...
public class Accumulator {
   private int value = 0;

   public int accumulate(int x) {
      value += x;
      return value;
   }
}

람다 - 리스너 예제

익명의 클래스 청취자

Java 8 이전에는 다음 코드와 같이 익명 클래스를 사용하여 JButton의 클릭 이벤트를 처리하는 것이 일반적입니다. 이 예제는 btn.addActionListener 의 범위 내에서 익명 수신기를 구현하는 방법을 보여줍니다.

JButton btn = new JButton("My Button");
btn.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("Button was pressed");
    }
});

람다 청취자

ActionListener 인터페이스는 하나의 actionPerformed() 메서드 만 정의하기 때문에 함수 인터페이스이므로 람다 식을 사용하여 상용구 코드를 대체 할 수 있습니다. 위의 예제는 다음과 같이 람다 식을 사용하여 다시 작성할 수 있습니다.

JButton btn = new JButton("My Button");
btn.addActionListener(e -> {
    System.out.println("Button was pressed");
});

전통적인 스타일의 람다 스타일

전통 방식

interface MathOperation{
    boolean unaryOperation(int num);
}

public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = new MathOperation() {
            @Override
            public boolean unaryOperation(int num) {
                return num%2 == 0;
            }
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}

람다 스타일

  1. 클래스 이름과 기능적 인터페이스 본문을 제거하십시오.
public class LambdaTry {
    public static void main(String[] args) {
        MathOperation isEven = (int num) -> {
            return num%2 == 0;
        };
        
        System.out.println(isEven.unaryOperation(25));
        System.out.println(isEven.unaryOperation(20));
    }
}
  1. 선택적 형식 선언
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. 매개 변수가 단일 매개 변수 인 경우 매개 변수 주위에 선택적 괄호
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. 선택적 중괄호 (function body에 한 줄만있는 경우)
  2. 함수 본문에 한 줄만있는 경우 선택적 return 키워드
MathOperation isEven = num -> num%2 == 0;

람다와 메모리 활용

Java lambda는 클로저이기 때문에 둘러싸는 어휘 범위의 변수 값을 "캡처"할 수 있습니다. 모든 lambdas가 아무것도 캡처하지는 않지만 s -> s.length() 와 같은 단순한 lambdas는 아무 것도 포착하지 않으며 stateless 라 불리는 - 람다 캡처에는 캡처 된 변수를 저장하는 임시 객체가 필요합니다. 이 코드 스 니펫에서 lambda () -> j 는 캡처 람다이며 평가 될 때 객체가 할당되도록 할 수 있습니다.

public static void main(String[] args) throws Exception {
    for (int i = 0; i < 1000000000; i++) {
        int j = i;
        doSomethingWithLambda(() -> j);
    }
}

new 키워드가 스 니펫의 아무 곳에 나 나타나지 않기 때문에 즉시 명확하지 않을 수도 있지만이 코드는 () -> j 람다 식의 인스턴스를 나타 내기 위해 1,000,000,000 개의 개별 개체를 만드는 경향이 있습니다. 그러나 Java 1 의 향후 버전에서는이를 최적화 하여 런타임시 람다 인스턴스가 재사용되거나 다른 방식으로 표현 될 수 있음에 유의해야합니다.


1 - 예를 들어 Java 9에서는 Java 빌드 시퀀스에 선택적 "링크"단계가 도입되어 전역 최적화를 수행 할 수있는 기회를 제공합니다.

람다 식과 술어를 사용하여 목록에서 특정 값 가져 오기

Java 8부터 람다 식과 술어를 사용할 수 있습니다.

예 : 람다 식과 술어를 사용하여 목록에서 특정 값을 가져옵니다. 이 예에서 모든 사람은 18 세 이상인 경우 사실로 인쇄됩니다.

사람 클래스 :

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public int getAge() { return age; }
    public String getName() { return name; }
}

java.util.function.Predicate 패키지의 내장 인터페이스 Predicate는 boolean test(T t) 메소드가있는 기능적 인터페이스입니다.

사용 예 :

import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

public class LambdaExample {
    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        personList.add(new Person("Jeroen", 20));
        personList.add(new Person("Jack", 5));
        personList.add(new Person("Lisa", 19));

        print(personList, p -> p.getAge() >= 18);
    }

    private static void print(List<Person> personList, Predicate<Person> checker) {
        for (Person person : personList) {
            if (checker.test(person)) {
                System.out.print(person + " matches your expression.");
            } else {
                System.out.println(person  + " doesn't match your expression.");
            }
        }
    }
}

print(personList, p -> p.getAge() >= 18); 메서드는 필요한 식을 정의 할 수있는 람다 식을 사용합니다 (Predicate는 매개 변수로 사용되기 때문에). 검사기의 검사 방법은이 표현식이 올바른지 여부를 확인합니다 : checker.test(person) .

이것을 다른 것으로 쉽게 변경할 수 있습니다. 예를 들어 print(personList, p -> p.getName().startsWith("J")); . 이 사람의 이름이 "J"로 시작하는지 확인합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow