• 1. Wyrażenia Lambda
  • 
    Wyrażenie Lambda to anonimowe metody, czyli nie posiadają nazwy.
    Definiowane są przez funkcjonalny interface 
    (który posiada tylko jedną metodę abstrakcyjną). 
    Może posiadać publiczne metody definiowane przez Object takie jak equals().
    Interface funkcjonalny definiuje typ obieku lambda. 
    Wyrażenie Lambda wprowadza nowy operator -> nazywany lambda operator lub operator strzałki.
    Po lewej stronie zdefinowane są parametr lambdy ( puste nawiasy gdy brak parametrów )
    Po prawej stronie zdefiniowane jest ciało lambdy określające jej działanie.
    Operator ten można nazwać "staje się" lub "idzie do".
    Mamy dwa typy ciała Lambda: pojedyncze wyrażenie i blok kodu.
    
    Przejdzmy do przykładów. Oto najprostrzy przyklad wyrażenia lambda,
    stała wartość bez parametrów: 
    
    () -> 3.14;
    Lub bardziej dynamiczny przykład:
    () -> Math.random() * 100;
    A teraz przyjrzyjmy się wyrażeniu Lambda z parametrem:
    (a) -> ( a % 2 ) == 0;
    Dla parametrów przystych zwraca true. Można również określić jaki jest typ parametru (nie jset to wymagane):
    (int a) -> ( a % 2 ) == 0;
    Jeżeli mamy jeden parametr to możemy również opuścić nawiasy:
    a -> ( a % 2 ) == 0;
    Do przypisania wyrażenia Lambda potrzebujemy interface z jedną i tylko jedną metodą (wtedy nazywamy ją "funkconalnym interfacem"), nie potrzebujemy w tej metodzie użycia słowa kluczowego "abstract". W javie 8 dodana została defaultowa metoda, lecz jej użycie nie zmienia znaczenia interfacesu funkcjonalnego czyli jedna metoda abstrakcyjna i nawet kilka metod defaultowych może być użyta jako interfaces funkcjonalny.
    interface Liczba { double getWartosc(); }
    Jak mamy zdefinoiwany interface możemy przypisać do niego wyrażenie lambda:
    Liczba pi; pi = () -> 3.14;
    I teraz możemy się odwołać do naszego wyrażenia Lambda:
    System.out.println ( pi.getWartosc() );
    Typ argumentów lambdy musi być spójny z parametrami wywołania. Jeżeli do naszego interfaceu "Liczba" spróbujemy przypisać String wyskoczy błąd:
    pi = () -> "java.org.pl"; // błąd
    Wyrażenia lambda możemy przypisywać kilka razy tak jak do zmiennej:
    Liczba zmienna; zmienna = () -> 3.14; System.out.println( zmienna.getWartosc() ); zmienna = () -> 1.2345; System.out.println( zmienna.getWartosc() );
    Przejdźmy do lambdy z dwoma parametrami:
    interface Liczba2 { boolean test(int a, int b); } Liczba2 sumaWiekszaOd10 = (a,b) -> ( a + b ) > 10; if ( sumaWiekszaOd10.test(4,6) ) { System.out.println( " Suma liczb jest większa od 10 " ); }
    Możemy też wywołać wyrażenie lambda używając jawnych argumentów funkcji:
    Liczba2 sumaWiekszaOd10 = (int a,int b) -> ( a + b ) > 10;
    Ale jeżeli użyjemy jej w jednym argumencie to już trzeba we wszystkich argumentach ich użyć:
    Liczba2 sumaWiekszaOd10 = (int a,b) -> ( a + b ) > 10; // błąd
    Wcale nie musimy nazwać argumentów lambdy tak jak jest w intefasie funkcjonalnym
    Liczba2 sumaWiekszaOd10 = (int mojaNazwa1, int mojaNazwa2) -> (mojaNazwa1 + mojaNazwa2) > 10;
    Czasami potrzebujamy złożonego ciała lambdy, którą nazywamy "blok lambdy" i używamy do tego nawiasów klamrowych. Ważne jest aby użyć słowa kluczowego return na końcu wyrażenia.
    interface Liczba { int wartosc(int a); } class BlokLambda { public static void main(String args[]){ Liczba silnia = (n) -> { int r = 1; for ( int i=1;i<=n; i++ ) { r = i*r; } return r; }; System.out.println("Silnia z 5 to: silnia.wartosc(5)); } }
    Wewnątrz bloku lambdy możemy używać pętli, delkarować zmienne używać warunków "if" i "switch" podobnie jak w ciele metody. Nowe cało lambdy musi być zakończone średnikiem ;. Mamy też możliwość uzycia typów generycznych w interfacesie i użycia go przy użyciu Lambda:
    interface JakasLambda { T wartosc(T a); } class BlokLambda { public static void main(String args[]){ JakasLambda<Integer> silnia = (n) -> { int r = 1; for ( int i=1;i<=n; i++ ) { r = i*r; } return r; }; System.out.println("Silnia z 5 to: silnia.wartosc(5)); JakasLambda rewers= (s) -> { String odtylu= ""; for ( int i=s.length()-1;i>=i; i-- ) { odtylu += s.charAt(i); } return odtylu; }; System.out.println("Lambda od tylu to: rewers.wartosc("Lambda")); } }
    Lambda może być także użyta jako argument funkcji:
    interface JakasLambda { String wartosc(String a); } class ArgLambda { public String strMetoda(JakasLambda lba,String s) { return lba.wartosc(s); } public static void main(String args[]){ String res1 = strMetoda((str)-> str.toUpperCase(),"Jakie litery?"); System.out.println( res1 ); String res2 = strMedoda( (str) -> { String odtylu= ""; for ( int i=s.length()-1;i>=i; i-- ) { odtylu += s.charAt(i); } return odtylu; }, "blok odwrocony" ); System.out.println( res2 ); JakasLambda rewers= (str) -> { String odtylu= ""; for ( int i=s.length()-1;i>=i; i-- ) { odtylu += s.charAt(i); } return odtylu; }; System.out.println( strMetoda( (strMetoda(rewers, "Jaka kolejnosc?") ); } }
    Mamy możliwość wstawiania pojedynczego wyrażenia lambdy w parametrze metody lub bloku lambdy w parametrze metody, lub zdefiniowanej wartości lambda w parametrze funkcji. W wyrażeniach lambda można również używać wyjątków. Jeżeli rzucony wyjątek jest wyżej, trzeba użyć w interface funkcyjnym wyrażenia throws z listą wywołanych wyjątków.
    interface TablicaLiczb { double wartosc(double[] n) throws IllegalArgumentException; } class LambdaException { public static void main(String args[]) throws IllegalArgumentException { TablicaLiczb sumaElem = (n) -> { if ( n.length == 0 ) { throw new IllegalArgumentException(); } double sum = 0; for (int i=0; i < n.length; i++) { sum += n[i]; } return sum; }; System.out.println( sumaElem.wartosc(new double[]{1.0,3.0, 5.0}) ); double[] db = new double[]{1.0,3.0, 5.0} System.out.println( sumaElem.wartosc(db) ); System.out.println( sumaElem.wartosc(new double[0]) ); System.out.println( sumaElem.wartosc({1.0,3.0, 5.0}) ); // błąd System.out.println( sumaElem.wartosc(new double[]{1.0,3.0, 5.0}) ); } }
    W tym przykładzie widzimy też że tablice podajemy jako jedną zmienną (n). Gdybyśmy chcieli jawnie zadeklarować typ wtedy napisalibyśmy (double[] n). Jeżeli zmienna jest użyta wewnątrz lambdy a zadeklarowana zewnątrz lambdy to i tak staje się ona automatyczenie zmienną finalną ponieważ jest użyta wewnątrz lambdy.
    interface Liczba { int wartosc(int a); } class Zmienna { public static void main(String args[]) { int liczbaA = 10; Liczba lambda = (n) -> { int liczbaB = liczbaA + n; // użycie w lambdzie automatycznie robi ją final liczbaA = 3; // błąd: jest final }; liczbaA = 5; // błądL jest final } }
    W javie 8 powstała referencja do metod, jest to trochę powiązane z wyrażeniami lambda. odwołujemy się to statecznych metod przez :: (podwójny dwukropek) to statyczeniej metody Klasa::statecznaMetoda i używamy jej w interfajsie funkcyjnym jako referencji do funkcji:
    interface Wyrazenie { String wartosc(String str); } class MojaKlasa { static String strOdTylu(String str) { String odtylu= ""; for ( int i=str.length()-1;i>=0; i-- ) { odtylu += str.charAt(i); } return odtylu; } } class Demo { static String strOper(Wyrazenie wyr, String str) { return wyr.wartosc(str); } public static void main(String args[]){ System.out.println( strOper( MojaKlasa::strOdTylu , "To jest moj tekst!!") ); } }
    Można to samo zrobić z instancją klasy, dla metody która nie jest statyczna. w tym celu odwołujemy się podobnie jak we wcześniejszym przykładzie. objekt::mojaMetoda na stworzonym wcześniej obiekcie i nie statycznej metodzie.
    interface Wyrazenie { String wartosc(String str); } class MojaKlasa { String strOdTylu(String str) { String odtylu= ""; for ( int i=str.length()-1;i>=0; i-- ) { odtylu += str.charAt(i); } return odtylu; } } class Demo { static String strOper(Wyrazenie wyr, String str) { return wyr.wartosc(str); } public static void main(String args[]) { MojaKlasa moja = new MojaKlasa (); // to jest dodane aby się do tego odwołać System.out.println( strOper( moja::strOdTylu , "To jest moj tekst!!") ); } }
    Można również użyć referencji z generykami:
    interface Porownaj { int wartosc(T[] var, T v); } class Tablica { static int liczWystapienia(T[] var, T v) { int licznik = 0; for( T t : var ) { if ( t.equals(v) ) licznik++; } return licznik; } } class Demo { static int mojaMetoda(Porownaj f, T[] var, T v) { return f.wartosc(var,v); } public static void main(String args[]) { System.out.println( mojaMetoda( Tablica::liczWystapienia, new String[]{"a","b","c", "b" },"a") ); } }
    Możemy również zrobić referencje do konstruktora za pomocą interface-u funkcyjnego. Referencje do konstruktorów robimy za pomocą wyrazenia Klasa::new, działa ona również z parametrami. Referencja odnosi się do tego konstruktora jaki paramater jest zdefiniowany w interface funkcjonalnym;
    interface Wyrazenie { MojaKlasa wartosc(String str); } class MojaKlasa{ private String nazwa; MojaKlasa() { nazwa = "domyślna";} MojaKlasa(String name) { nazwa = name; } String nazwaKlasy(){ return nazwa;}; } public class JavaApplication1 { public static void main(String args[]) { Wyrazenie mojaRef = MojaKlasa::new; MojaKlasa moja = mojaRef.wartosc("Moja Klasa"); System.out.println( moja.nazwaKlasy() ); } }
    Możemy mieć również konstruktor z generykami, wtedy odwołujemy się do generyki tworząc referencje do konstruktora.
    interface Wyrazenie { MojaKlasa wartosc(T str); } class MojaKlasa{ private T nazwa; MojaKlasa() {nazwa = null;} MojaKlasa(T name) { nazwa = name; } T nazwaKlasy() {return nazwa;}; } public class JavaApplication1 { public static void main(String args[]) { Wyrazenie mojaRef = MojaKlasa::new; MojaKlasa moja = mojaRef.wartosc("Moja Klasa"); System.out.println( moja.nazwaKlasy() ); } }
    Jeżeli chcemy stworzyć referencje od tablicy obiektów odwołujemy się: MojaKlasa[]::new Ale czasami nie musimy tworzyć naszych własnych interfaceów funkcyjnych. JDK 8 udostępnia paczkę java.util.function: UnaryOperation - Obiekt T i zwraca obiekt T, metoda to apply(); BinaryOperator - operacja na dwóch obiektach T, zwraca obiekt T, metoda to apply(); Consumer - Operacja na obiekcie T, metoda to accept(); Supplier - zwraca obiekt T, metoda to get(); Function - Operacja na obiekcie T zwraca obiekt R, metoda to apply(); Predicate - jeśli obiekt T spełnia jakieś ograniczenia, zwraca boolean, metoda to test(); np:
    class Demo { public static void main(String args[]) { Function<Integer,Integer> func = (n) -> { int wynik = 1; for (int i = 1; i <= n; i++ ) { result = i * wynik; } return wynik; }; System.out.println( func.apply(4) ); } }