Szukaj…


Wprowadzenie

Wyrażenia lambda zapewniają jasny i zwięzły sposób implementacji interfejsu jednoprocesowego za pomocą wyrażenia. Pozwalają zmniejszyć ilość kodu, który musisz stworzyć i utrzymać. Chociaż są podobne do klas anonimowych, same nie mają informacji o typie. Wnioskowanie typu musi się zdarzyć.

Odwołania do metod implementują interfejsy funkcjonalne przy użyciu istniejących metod zamiast wyrażeń. Należą również do rodziny lambda.

Składnia

  • () -> {wyrażenie zwrotne; } // Zero-arity z treścią funkcji, aby zwrócić wartość.
  • () -> wyrażenie // Stenografia dla powyższej deklaracji; dla wyrażeń nie ma średnika.
  • () -> {function-body} // Efekt uboczny w wyrażeniu lambda do wykonywania operacji.
  • parametrName -> expression // Jednorazowe wyrażenie lambda. W wyrażeniach lambda z jednym argumentem można usunąć nawias.
  • (Wpisz nazwa parametru, wpisz drugą nazwę parametru, ...) -> wyrażenie // lambda oceniające wyrażenie z parametrami wymienionymi po lewej stronie
  • (parametrName, secondParameterName, ...) -> wyrażenie // Stenografia, która usuwa typy parametrów dla nazw parametrów. Może być używany tylko w kontekstach, które można wywnioskować przez kompilator, w którym podany rozmiar listy parametrów odpowiada jednemu (i tylko jednemu) rozmiarowi oczekiwanego interfejsu funkcjonalnego.

Używanie wyrażeń lambda do sortowania kolekcji

Sortowanie list

Przed Javą 8 konieczne było zaimplementowanie interfejsu java.util.Comparator z anonimową (lub nazwaną) klasą podczas sortowania listy 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());
        }
    }
);

Począwszy od Java 8, anonimową klasę można zastąpić wyrażeniem lambda. Zauważ, że typy parametrów p1 i p2 można p2 , ponieważ kompilator automatycznie je wywnioskuje:

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

Przykład można uprościć za pomocą Comparator.comparing i referencje metod wyrażone za pomocą symbolu :: (podwójny dwukropek).

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

Import statyczny pozwala nam wyrazić to bardziej zwięźle, ale można dyskutować, czy poprawi to ogólną czytelność:

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

Tak zbudowane komparatory można również łączyć ze sobą. Na przykład po porównaniu osób według imienia, jeśli są osoby o tym samym imieniu, metoda thenComparing z porównaniem według nazwiska:

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

1 - Należy pamiętać, że kolekcja.sort (...) działa tylko w kolekcjach, które są podtypami List . Interfejsy API Set i Collection nie sugerują żadnego uporządkowania elementów.

Sortowanie map

Możesz sortować wpisy HashMap według wartości w podobny sposób. (Zauważ, że LinkedHashMap musi być użyty jako cel. Klucze w zwykłej HashMap są nieuporządkowane.)

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));

Wprowadzenie do lambd Java

Interfejsy funkcjonalne

Lambdas może działać tylko na interfejsie funkcjonalnym, który jest interfejsem z tylko jedną metodą abstrakcyjną. Interfejsy funkcjonalne mogą mieć dowolną liczbę default lub static metod. (Z tego powodu są one czasami nazywane interfejsami pojedynczej metody abstrakcyjnej lub interfejsami SAM).

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();
}

Deklarując funkcjonalny interfejs, można dodać adnotację @FunctionalInterface . Nie ma to żadnego efektu specjalnego, ale błąd kompilatora zostanie wygenerowany, jeśli adnotacja zostanie zastosowana do interfejsu, który nie działa, działając w ten sposób jako przypomnienie, że interfejs nie powinien zostać zmieniony.

@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
}

I odwrotnie, nie jest to interfejs funkcjonalny, ponieważ ma więcej niż jedną metodę abstrakcyjną :

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

To również nie jest funkcjonalny interfejs, ponieważ nie ma żadnych metod:

interface BlankFoo2 { }

Zwróć uwagę na następujące kwestie. Załóżmy, że masz

interface Parent { public int parentMethod(); }

i

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

Child nie może być funkcjonalnym interfejsem, ponieważ ma dwie określone metody.

Java 8 zapewnia również szereg ogólnych interfejsów funkcjonalnych w szablonie w pakiecie java.util.function . Na przykład wbudowany interfejs Predicate<T> otacza pojedynczą metodę, która wprowadza wartość typu T i zwraca wartość boolean .


Wyrażenia lambda

Podstawowa struktura wyrażenia Lambda to:

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

fi będzie wówczas przechowywać instancję singletona klasy, podobną do klasy anonimowej, która implementuje interfejs FunctionalInterface i gdzie definicją jednej metody jest { System.out.println("Hello"); } . Innymi słowy, powyższe jest w większości równoważne z:

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

Lambda jest tylko „głównie odpowiednik” anonimowy klasy, ponieważ w lambda, znaczenie wyrażeń takich jak this , super lub toString() odwoływać się do klasy, w którym odbywa się przydział, a nie nowo utworzony obiekt.

Nie możesz podać nazwy metody, gdy używasz lambda - ale nie musisz, ponieważ interfejs funkcjonalny musi mieć tylko jedną metodę abstrakcyjną, więc Java zastępuje tę.

W przypadkach, w których typ lambda nie jest pewien (np. Metody przeciążone), możesz dodać rzut do lambda, aby poinformować kompilator, jaki powinien być typ, na przykład:

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

Jeśli pojedyncza metoda interfejsu funkcjonalnego przyjmuje parametry, lokalne formalne nazwy powinny pojawić się między nawiasami lambda. Nie ma potrzeby deklarowania typu parametru ani zwracania, ponieważ są one pobierane z interfejsu (chociaż deklarowanie typów parametrów nie jest błędem, jeśli chcesz). Zatem te dwa przykłady są równoważne:

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

Nawiasy wokół argumentu można pominąć, jeśli funkcja ma tylko jeden argument:

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

Implikowane zwroty

Jeśli kod umieszczony w lambda jest wyrażeniem Java, a nie instrukcją , jest traktowany jako metoda zwracająca wartość wyrażenia. Zatem dwa następujące są równoważne:

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

Dostęp do zmiennych lokalnych (zamknięcia wartości)

Ponieważ lambdy są skrótami składniowymi dla anonimowych klas, przestrzegają tych samych zasad dostępu do zmiennych lokalnych w otaczającym zakresie; zmienne muszą być traktowane jako final i nie mogą być modyfikowane w lambda.

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
}

Jeśli konieczne jest zawijanie zmieniającej się zmiennej w ten sposób, należy użyć zwykłego obiektu, który przechowuje kopię zmiennej. Przeczytaj więcej w Java Closures z wyrażeniami lambda.


Akceptuje Lambdas

Ponieważ lambda jest implementacją interfejsu, nie trzeba nic specjalnego robić, aby metoda zaakceptowała lambda: każda funkcja, która przyjmuje interfejs funkcjonalny, może również zaakceptować lambda.

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

Typ wyrażenia lambda

Samo wyrażenie lambda nie ma określonego typu. Chociaż prawdą jest, że typy i liczba parametrów, wraz z rodzajem wartości zwracanej, mogą przenosić pewne informacje o typie, informacje takie będą ograniczały tylko to, do których typów można przypisać. Lambda odbiera typ, gdy jest przypisany do funkcjonalnego typu interfejsu na jeden z następujących sposobów:

  • Bezpośrednie przypisanie do typu funkcjonalnego, np. myPredicate = s -> s.isEmpty()
  • Przekazanie go jako parametru o typie funkcjonalnym, np. stream.filter(s -> s.isEmpty())
  • Zwracanie go z funkcji zwracającej typ funkcjonalny, np. return s -> s.isEmpty()
  • Rzutowanie na typ funkcjonalny, np. (Predicate<String>) s -> s.isEmpty()

Dopóki takie przypisanie do typu funkcjonalnego nie zostanie wykonane, lambda nie ma określonego typu. Aby to zilustrować, rozważ wyrażenie lambda o -> o.isEmpty() . To samo wyrażenie lambda można przypisać do wielu różnych typów funkcjonalnych:

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();

Teraz, gdy są już przypisane, pokazane przykłady są zupełnie różnych typów, mimo że wyrażenia lambda wyglądały tak samo i nie można ich przypisać do siebie.

Referencje metod

Odwołania do metod umożliwiają przekazywanie predefiniowanych metod statycznych lub instancji zgodnych z kompatybilnym interfejsem funkcjonalnym jako argumentów zamiast anonimowego wyrażenia lambda.

Załóżmy, że mamy model:

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();

Odwołanie do metody instancji (do dowolnej instancji)

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

Odpowiednik lambda:

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

W tym przykładzie przekazywane jest odwołanie do metody instancji getName() typu Person . Ponieważ wiadomo, że jest to typ kolekcji, zostanie wywołana metoda instancji (znana później).


Odwołanie do metody instancji (do konkretnej instancji)

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

Ponieważ System.out jest instancją PrintStream , odwołanie do metody do tej konkretnej instancji jest przekazywane jako argument.

Odpowiednik lambda:

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

Odniesienie do metody statycznej

Również do przekształcania strumieni możemy zastosować odniesienia do metod statycznych:

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

W tym przykładzie przekazano odwołanie do metody static valueOf() typu String . Dlatego obiekt instancji w kolekcji jest przekazywany jako argument wartości valueOf() .

Odpowiednik lambda:

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

Odniesienie do konstruktora

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

Przeczytaj Zbieranie elementów strumienia do kolekcji, aby zobaczyć, jak zbierać elementy do kolekcji.

Używa się tutaj konstruktora pojedynczego ciągu typu Integer , aby skonstruować liczbę całkowitą, biorąc pod uwagę ciąg podany jako argument. W takim przypadku, dopóki ciąg reprezentuje liczbę, strumień będzie mapowany na liczby całkowite. Odpowiednik lambda:

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

Ściągawka

Format referencyjny metody Kod Odpowiednik Lambda
Metoda statyczna TypeName::method (args) -> TypeName.method(args)
Metoda niestatyczna (na przykład * ) instance::method (args) -> instance.method(args)
Metoda niestatyczna (bez wystąpienia) TypeName::method (instance, args) -> instance.method(args)
Konstruktor ** TypeName::new (args) -> new TypeName(args)
Konstruktor tablic TypeName[]::new (int size) -> new TypeName[size]

* instance może być dowolnym wyrażeniem, które getInstance()::method odwołanie do instancji, np. getInstance()::method , this::method

** Jeśli TypeName jest niestatyczną klasą wewnętrzną, odwołanie do konstruktora jest prawidłowe tylko w zakresie wystąpienia klasy zewnętrznej

Implementowanie wielu interfejsów

Czasami możesz chcieć mieć wyrażenie lambda implementujące więcej niż jeden interfejs. Jest to szczególnie przydatne w przypadku interfejsów znaczników (takich jak java.io.Serializable ), ponieważ nie dodają one metod abstrakcyjnych.

Na przykład chcesz utworzyć zestaw TreeSet za pomocą niestandardowego Comparator a następnie zserializować go i wysłać przez sieć. Trywialne podejście:

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

nie działa, ponieważ lambda dla komparatora nie implementuje Serializable . Możesz to naprawić, używając typów skrzyżowań i wyraźnie określając, że lambda musi być możliwa do serializacji:

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

Jeśli często używasz typów skrzyżowań (na przykład jeśli używasz frameworka, takiego jak Apache Spark, w którym prawie wszystko musi być możliwe do serializacji), możesz utworzyć puste interfejsy i użyć ich w kodzie:

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

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

W ten sposób masz gwarancję, że przekazany komparator będzie można serializować.

Lambda i wzór wykonania

Istnieje kilka dobrych przykładów użycia lambdas jako interfejsu funkcjonalnego w prostych scenariuszach. Dość powszechnym przypadkiem użycia, który można ulepszyć za pomocą lambdas, jest tak zwany wzorzec wykonywania. W tym wzorcu masz zestaw standardowego kodu konfiguracji / porzucenia, który jest potrzebny w wielu scenariuszach otaczających kod specyficzny dla przypadku użycia. Kilka typowych tego przykładów to bloki pliku io, bazy danych, 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();
    } 
}

Następnie wywołanie tej metody za pomocą lambda może wyglądać następująco:

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

Nie ogranicza się to do operacji we / wy. Może mieć zastosowanie do każdego scenariusza, w którym podobne zadania konfiguracji / rozbiórki mają zastosowanie z niewielkimi zmianami. Główną zaletą tego Wzorca jest ponowne użycie kodu i wymuszanie OSUSZANIA (Don't Repeat Yourself).

Używanie wyrażenia lambda z własnym interfejsem funkcjonalnym

Lambdy mają zapewniać wbudowany kod implementacyjny dla interfejsów pojedynczej metody i możliwość przekazywania ich tak, jak robiliśmy to przy normalnych zmiennych. Nazywamy je interfejsem funkcjonalnym.

Na przykład napisanie Runnable w anonimowej klasie i rozpoczęcie wątku wygląda następująco:

//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();

Teraz, zgodnie z powyższym, załóżmy, że masz jakiś niestandardowy interfejs:

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

Jak używasz lambda do implementacji tego interfejsu w kodzie? Taki sam jak pokazany powyżej przykład Runnable. Zobacz program sterownika poniżej:

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” zwraca tylko lambda, a nie zewnętrzną metodę

Metoda return zwraca tylko z lambda, a nie z metody zewnętrznej.

Uważaj, że różni się to od Scali i Kotlin!

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!
  });
}

Może to prowadzić do nieoczekiwanego zachowania podczas próby napisać własne konstrukcje języka, jak na polecenie wbudowane konstrukcje takie jak for pętle return zachowuje się inaczej:

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

W Scali i Kotlin zarówno demo i demo2 wydrukowałyby tylko 0 . Ale to nie jest bardziej spójne. Podejście Java jest spójne z refaktoryzacją i użyciem klas - return w kodzie u góry, a poniższy kod zachowuje się tak samo:

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

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

Dlatego Java return jest bardziej spójna z metod klasy i refaktoringu, ale mniej z for i while builtins, pozostają one wyjątkowe.

Z tego powodu następujące dwa są równoważne w Javie:

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);

Ponadto użycie try-with-resources jest bezpieczne w Javie:

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.
  });
}

wydrukuje before accept() after close() . W semantyce Scali i Kotlina try-with-resources nie zostałby zamknięty, ale zostałby wydrukowany before accept() .

Zamknięcia Java z wyrażeniami lambda.

Zamknięcie lambda jest tworzone, gdy wyrażenie lambda odwołuje się do zmiennych obejmującego zakres (globalny lub lokalny). Zasady wykonywania tego są takie same jak w przypadku metod wbudowanych i klas anonimowych.

Zmienne lokalne z zakresu obejmującego, które są używane w lambda, muszą być final . W Javie 8 (najwcześniejszej wersji, która obsługuje lambdas), nie muszą one być deklarowane jako final w kontekście zewnętrznym, ale muszą być traktowane w ten sposób. Na przykład:

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

Jest to legalne, dopóki wartość zmiennej n nie zostanie zmieniona. Jeśli spróbujesz zmienić zmienną, wewnątrz lub na zewnątrz lambda, pojawi się następujący błąd kompilacji:

„zmienne lokalne, do których odwołuje się wyrażenie lambda, muszą być ostateczne lub faktycznie ostateczne ”.

Na przykład:

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

Jeśli konieczne jest użycie zmieniającej się zmiennej w lambdzie, normalnym podejściem jest zadeklarowanie final kopii zmiennej i użycie tej kopii. Na przykład

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

Oczywiście ciało lambda nie widzi zmian oryginalnej zmiennej.

Pamiętaj, że Java nie obsługuje prawdziwych zamknięć. Lambda Java nie może zostać utworzona w sposób, który pozwala jej zobaczyć zmiany w środowisku, w którym została utworzona. Jeśli chcesz wdrożyć zamknięcie, które obserwuje lub wprowadza zmiany w swoim otoczeniu, powinieneś symulować je przy użyciu zwykłej klasy. Na przykład:

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

Powyższy przykład nie zostanie skompilowany z powodów omówionych wcześniej. Możemy obejść błąd kompilacji w następujący sposób:

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

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

Problem polega na tym, że powoduje to IntUnaryOperator kontraktu projektowego dla interfejsu IntUnaryOperator , który stwierdza, że instancje powinny być funkcjonalne i bezstanowe. Jeśli takie zamknięcie zostanie przekazane wbudowanym funkcjom, które akceptują obiekty funkcjonalne, może to spowodować awarie lub błędne zachowanie. Zamknięcia, które zawierają stan zmienny, powinny być implementowane jako zwykłe klasy. Na przykład.

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

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

Lambda - przykład słuchacza

Anonimowy detektor klas

W wersjach wcześniejszych niż Java 8 bardzo często anonimowa klasa jest używana do obsługi zdarzenia kliknięcia JButton, jak pokazano w poniższym kodzie. Ten przykład pokazuje, jak zaimplementować anonimowy detektor w zakresie btn.addActionListener .

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

Słuchacz Lambda

Ponieważ interfejs ActionListener definiuje tylko jedną metodę actionPerformed() , jest to interfejs funkcjonalny, co oznacza, że istnieje miejsce do użycia wyrażeń Lambda w celu zastąpienia kodu actionPerformed() . Powyższy przykład można przepisać przy użyciu wyrażeń Lambda w następujący sposób:

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

Styl tradycyjny do stylu Lambda

Tradycyjny sposób

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));
    }
}

Styl Lambda

  1. Usuń nazwę klasy i funkcjonalny interfejs.
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. Opcjonalna deklaracja typu
MathOperation isEven = (num) -> {
    return num%2 == 0;
};
  1. Opcjonalny nawias wokół parametru, jeśli jest to pojedynczy parametr
MathOperation isEven = num -> {
    return num%2 == 0;
};
  1. Opcjonalne nawiasy klamrowe, jeśli w treści funkcji jest tylko jedna linia
  2. Opcjonalne słowo kluczowe powrotu, jeśli w treści funkcji jest tylko jeden wiersz
MathOperation isEven = num -> num%2 == 0;

Lambda i wykorzystanie pamięci

Ponieważ lambda Java są zamknięciami, mogą „uchwycić” wartości zmiennych w otaczającym zakresie leksykalnym. Chociaż nie wszystkie lambdy przechwytują cokolwiek - proste lambdy takie jak s -> s.length() nie przechwytują niczego i są nazywane bezstanowymi - przechwytywanie lambd wymaga tymczasowego obiektu do przechowywania przechwyconych zmiennych. W tym fragmencie kodu lambda () -> j jest lambda przechwytującą i może powodować przydzielenie obiektu podczas jego oceny:

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

Chociaż może nie być to od razu oczywiste, ponieważ new słowo kluczowe nie pojawia się nigdzie we fragmencie, kod ten może utworzyć 1 000 000 000 oddzielnych obiektów reprezentujących instancje wyrażenia () -> j lambda. Należy jednak zauważyć, że przyszłe wersje Java 1 mogą być w stanie zoptymalizować to, aby w czasie wykonywania instancje lambda były ponownie wykorzystywane lub były reprezentowane w inny sposób.


1 - Na przykład Java 9 wprowadza opcjonalną fazę „linku” do sekwencji kompilacji Java, która zapewni możliwość globalnej optymalizacji takiej jak ta.

Używanie wyrażeń i predykatów lambda w celu uzyskania określonych wartości z listy

Począwszy od Java 8, możesz używać wyrażeń i predykatów lambda.

Przykład: użyj wyrażeń lambda i predykatu, aby uzyskać określoną wartość z listy. W tym przykładzie każda osoba zostanie wydrukowana z faktem, czy ma 18 lat i więcej, czy nie.

Klasa osoby:

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; }
}

Wbudowany interfejs Predicate z pakietów java.util.function.Predicate to funkcjonalny interfejs z metodą boolean test(T t) .

Przykładowe użycie:

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); Metoda przyjmuje wyrażenie lambda (ponieważ predykat jest używany jako parametr), w którym można zdefiniować potrzebne wyrażenie. Metoda testowa sprawdzania sprawdza, czy to wyrażenie jest poprawne, czy nie: checker.test(person) .

Możesz łatwo zmienić to na coś innego, na przykład print(personList, p -> p.getName().startsWith("J")); . Spowoduje to sprawdzenie, czy imię osoby zaczyna się od litery „J”.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow