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 obiektu lambda.
Wyrażenie Lambda wprowadza nowy operator -> nazywany lambda operator lub operator strzałki.
Po lewej stronie zdefiniowane 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.
Przejdźmy do przykładów. Oto najprostszy przykład wyrażenia lambda, stała wartość bez parametrów:
Lub bardziej dynamiczny przykład:
() -> Math.random() * 100;
A teraz przyjrzyjmy się wyrażeniu Lambda z parametrem:
Dla parametrów psrzystych zwraca true.
Można również określić jaki jest typ parametru (nie jest to wymagane):
(int a) -> ( a % 2 ) == 0;
Jeżeli mamy jeden parametr to możemy również opuścić nawiasy:
Do przypisania wyrażenia Lambda potrzebujemy interface z jedną i tylko jedną metodą (wtedy nazywamy ją "funkcjonalnym interfac-em"),
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 interface-su 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;
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.edu.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 wieksza 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 potrzebujemy złożonego ciała lambdy, który 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 ciało lambdy musi być zakończone średnikiem ;.
Mamy też możliwość użycia typów generycznych w interface-sie i użycia go przy użyciu Lambda:
interface JakasLambda<T> {
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<String> rewers = (s) -> {
String odTylu = "" ;
for ( int i=s.length()-1;i>=0; 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 automatycznie 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łąd 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 interface-sie 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<T> {
int wartosc(T[] var, T v);
}
class Tablica {
static <T> int liczWystapienia(T[] var, T v) {
int licznik = 0;
for( T t : var ) {
if ( t.equals(v) ) licznik++;
}
return licznik;
}
}
class Demo {
static <T> int mojaMetoda(Porownaj<T> f, T[] var, T v) {
return f.wartosc(var,v);
}
public static void main(String args[]) {
System.out.println( mojaMetoda( Tablica::<String>liczWystapienia, newString[]{"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ą wyrażenia 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 = "domyslna";}
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<T> {
MojaKlasa<T> wartosc(T str);
}
class MojaKlasa<T>{
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<String> mojaRef = MojaKlasa<String>::new;
MojaKlasa<String> 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<T> - Obiekt T i zwraca obiekt T, metoda to <T> T apply(<T> t);
BinaryOperator<T> - operacja na dwóch obiektach T, zwraca obiekt T, metoda to <T> T apply(T t2,T t2);
Consumer<T> - Operacja na obiekcie T, metoda to <T> void accept(T t);
Supplier<T> - zwraca obiekt T, metoda to <T> T get();
Function<T,R> - Operacja na obiekcie T zwraca obiekt R, metoda to <R,T> R apply(T t);
Predicate<T> - jeżli obiekt T spełnia jakieś ograniczenia, zwraca boolean, metoda to <T> boolean test(T t);
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) );
}
}
|