Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Code Block
themeEclipse
languagejava
titleMultiplier
public interface MultiplierDoubleValueComputer {
    public double multiplycompute(double x, double y);
}

Grensesnittet over er et funksjonelt grensesnitt, for det har bare én metode, nemlig multiplynemlig compute. Metoden tar to argumenter, utfører en operasjon på disse, og returnerer resultatet. Forskjellige implementasjoner kan være for eksempel addisjon eller multiplikasjon.

Vi kan ta i bruk dette grensesnittet på den tradisjonelle måten ved å opprette en egen klasse som implementerer MultiplierDoubleValueComptuer, og deretter opprette en instans av denne klassen, som vi kaller multiply fra compute fra. Men dette blir utrolig tungvint for å bruke en så enkel metode som multiplysom compute. For å slippe dette lar Java oss definere et objekt som implementerer grensesnittet direkte, ved at man definerer metoden(e) til grensesnittet i initaliseringen av objektet (se venstre kolonne under). Merk at dette fungerer også for grensesnitt med flere metoder.

...

 

Code Block
themeEclipse
languagejava
titleDirekte implementasjon
MultiplierDoubleValueComputer multiplieradder = new MultiplierDoubleValueComputer() {
    @Override
    public double multiplycompute(double x, double y) {
        return x *+ y;
    }
};

 

 

Code Block
themeEclipse
languagejava
titleMed lambda
Multiplier
 
DoubleValueComputer multiplier = new DoubleValueComputer() {
    @Override
    public double compute(double x, double y) ->{
        return x * y;
    }
};
 
 

 

Lambda-uttrykk

 

Code Block
themeEclipse
languagejava
titleMed lambda
DoubleValueComputer adder = (x, y) -> x + y;
DoubleValueComputer multiplier = (x, y) -> x * y;

 

Lambda-uttrykk

Den direkte implementasjonen over er på seks linjer, men det er kun linje Den direkte implementasjonen over er på seks linjer, men det er kun linje fire som er interessant: Hva er det multiply compute skal returnere? Resten av linjene er unødvendige, for vi vet dem fra før av – det samme står i definisjonen av grensesnittet. Vi vet at en implementasjon av Multiplier må DoubleValueComputer må ha en metode som heter multiplyheter compute, den må være public, den må ta to doubles som argumenter, og returnere en double. Lambda-operatoren (->) lar oss forenkle dette. Til venstre for pila står argumentene (dersom det er flere, eller ingen, må de omsluttes med parentes og separeres med komma), og til høyre for pila står hva som skal returneres. Merk at dette fungerer kun for funksjonelle grensesnitt, for ellers vil ikke lambda-operatoren vite hvilken metode som skal kallesimplementeres.

Innebygde funksjonelle grensesnitt i Java 8

For å kunne utnytte kraften i lambda til det fulle, er de oftest brukte funksjonelle grensesnittene implementert i Java, så man slipper å definere dem selv. Vi skal snakke litt om grensesnittene Predicate og Consumer.:

Predicate

...

Predicate-grensesnittet har metoden test, som tar inn et objekt av hvilken som helst type som argument, og returnerer en boolean.

Consumer-grensesnittet har metoden accept, som tar inn et objekt av hvilken som helst type som argument, og returnerer ingenting (void).

Bruken av disse skal vi demonstrere senere.

 

Stream

Streams gjør det veldig enkelt for oss å utføre operasjoner på lister på en kort og elegant måte, i kombinasjon med lambda og ofte Predicate og Consumer. I stedet for å forklare hva streams er, skal vi heller vise eksempler på bruken av det. Vi definerer en Person-klasse for å ha noe å leke med:

Eksempler

Vi definerer en Person-klasse for å ha noe å leke med:

Code Blockcode
themeEclipse
languagejava
titlePerson.java
public class Person {
    private String name;
    private int age;
    private char gender;

    public Person(String name, int age, char gender) {
        setName(name);
        setAge(age);
        setGender(gender);
    }

    public String getName() { return name; }

    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }

    public void setAge(int age) { this.age = age; }

    public char getGender() { return gender; }

    public void setGender(char gender) { this.gender = gender; }

    public String toString() { return name + " " + age + " " + gender; }
}

...

Eksemplene under vil være forskjellige implementasjoner av run()-metoden til PersonMain.java.

 

anyMatch

Sortere med Comparable

Å sortere med Comparable blir veldig enkelt med lambda, da Comparable-grensesnittet er funksjonelt. La oss sortere personene på navn (eksempel 1) og på alder (eksempel 2):Vi begynner med et enkelt og nyttig eksempel på bruk av Predicate-grensesnittet og streams. anyMatch er en metode som tar inn en Predicate-instans og returnerer true dersom minst ett av elementene i den aktuelle streamen tilfredsstiller predikatet. For eksempel, finnes det en kvinne i lista vår?

Code Block
themeEclipse
languagejava
titleTradisjonell måte
boolean womanExists = false; 
for (Person p : persons) {
    if (p.getGender() == 'F'persons.sort(new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        womanExists = true;
		breakreturn a.getName().compareTo(b.getName());
    }
});
Code Block
themeEclipse
languagejava
titleMed stream og lambda
 boolean womanExists = persons.streampersons.sort((a, b) -> a.getName().anyMatch(p -> p.getGender() == 'F');

Til høyre tar vi lista vår persons, kaller metoden stream() på den for å gjøre den til en stream og få tak i den innebygde anyMatch-metoden. anyMatch tar som kjent et predikatobjekt som argument, som vi definerer på lambdavis. Predicate-instansen vi oppretter får inn et Person-objekt (p) som argument (den vil bli kalla for alle elementene i lista, som er personer), og returnerer true dersom den aktuelle personens kjønn er kvinne. 

Det er flere andre metoder som ligner på anyMatch: allMatch (alle element i en stream tilfredsstiller predikatet), noneMatch (ingen matcher), findAny (returnerer et element som tilfredsstiller predikatet), findFirst (returnerer det første elementet som tilfredsstiller predikatet).

 

forEach

forEach tar inn en Consumer-instans og kaller denne instansens ene metode på alle elementene i streamen. La oss legge til et år på alle personenes alder.

compareTo(b.getName()));
Code Block
themeEclipse
languagejava
titleTradisjonell måte
persons.sort(new Comparator<Person>() {
    @Override
    public int compare(Person a, Person b) {
        return a.getAge() - b.getAge();
});
Code Block
themeEclipse
languagejava
titleMed stream og lambda
persons.sort((a, b) -> a.getAge() - b.getAge());

 

Streams

Streams gjør det veldig enkelt for oss å utføre operasjoner på lister på en kort og elegant måte, i kombinasjon med lambda og ofte Predicate- og Consumer-grensesnittene. I stedet for å forklare hva streams er, skal vi heller vise eksempler på bruken av det.

anyMatch

Vi begynner med et enkelt og nyttig eksempel på bruk av Predicate-grensesnittet og streams. anyMatch er en metode som tar inn en Predicate-instans og returnerer true dersom minst ett av elementene i den aktuelle streamen tilfredsstiller predikatet. For eksempel, finnes det en kvinne i lista vår?

 

codeforEachsetAgep.getAge(+ 1));

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
boolean womanExists = false; 
for (Person p : persons) {
    if (p.getGender() == 'F') {
        womanExists = true;
		break;
    }
}
Code Block

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
for (Person p : persons) {
    p.setAge(p.getAge() + 1);
}

 

themeEclipse
languagejava
titleMed stream og lambda
 boolean womanExists = persons.stream().
anyMatch(p -> p.
getGender(
) 

Vi kan også kombinere funksjoner på streams, for eksempel filter og forEach. La oss si vi vil legge til et år på alderen til alle gutter under 18:

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
for (Person p : persons) {
    if (p.getAge() < 18 && p.getGender() == 'M')
        p.setAge(p.getAge() + 1);
}
== 'F');

Til høyre tar vi lista vår persons, kaller metoden stream() på den for å gjøre den til en stream og få tak i den innebygde anyMatch-metoden. anyMatch tar som kjent et predikatobjekt som argument, som vi definerer på lambdavis. Predicate-instansen vi oppretter får inn et Person-objekt (p) som argument (den vil bli kalla for alle elementene i lista, som er personer), og returnerer true dersom den aktuelle personens kjønn er kvinne. 

Det er flere andre metoder som ligner på anyMatch: allMatch (alle element i en stream tilfredsstiller predikatet), noneMatch (ingen matcher), findAny (returnerer et element som tilfredsstiller predikatet), findFirst (returnerer det første elementet som tilfredsstiller predikatet).

Filter

Filter er en svært vanlig operasjon på lister (i likhet med map og reduce som vi nevner senere), som mange programmeringsspråk har støtte for. Filter kalles på en liste, og returnerer en ny liste med kun de elementene som tilfredsstiller et gitt predikat. Den filtrerer med andre ord ut alle element som ikke matcher predikatet, og returnerer resulterende stream. For eksempel: Hvilke personer er over 18 år?

...

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
persons.stream()
	.filter(p -> p.getAge() < 18 && p.getGender() == 'M')
	.forEach(p -> p.setAge(p.getAge() + 1));

 

Filter

Filter er en svært vanlig operasjon på lister (i likhet med map og reduce som vi nevner senere), som mange programmeringsspråk har støtte for. Filter kalles på en liste, og returnerer en ny liste med kun de elementene som tilfredsstiller et gitt predikat. Den filtrerer med andre ord ut alle element som ikke matcher predikatet. For eksempel: Hvilke personer er over 18 år?

Tradisjonell måte
List<Person> overEighteen = new ArrayList<Person>();
for (Person p : persons) {
    if (p.getAge() >= 18) {
        overEighteen.add(p);
    }
}
System.out.println(overEighteen);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream().filter(p -> p.getAge() >= 18).collect(Collectors.toList()));

 

Siden filter-funksjonen returnerer en stream, bruker vi collect for å gjøre den til en List.

 

Map

Map brukes for å danne en ny liste av en annen liste, der en gitt funksjon blir kalt på alle elementene i lista. For eksempel, for å få en liste over alle aldrene til personene, vil vi kalle getAge-funksjonen på alle personene i lista, og legge aldrene i en ny liste.

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
List<Integer> ages = new ArrayList<Integer>();
for (Person p : persons) {
    ages.add(p.getAge());
}
System.out.println(ages);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream().map(Person::getAge).collect(Collectors.toList()));

 

Map tar en instans av typen Function som argument. Du ser kanskje det doble kolonet. Det er en ny operator introdusert i Java 8 som lar en referere til en metode i seg selv, i stedet for returverdien til den metoden. Slik kan vi enkelt gi den metoden vi ønsker som argument. 

Det finnes spesielle map-funksjoner også, som mapToInt og mapToDouble. Disse returnerer henholdsvis IntegerStream og DoubleStream, som har hendige metoder som average, sum, min og max.

Reduce

Reduce brukes for å redusere en liste til et enkelt svar. Reduce tar en akkumulatorfunksjon som argument. Denne akkumulatoren tar to argument: Verdien så langt, og neste element. Reduce brukes ofte sammen med map. La oss finne totalalderen i lista vår.

 

Code Block
themeEclipse
language

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
List<Person>int overEighteentotalAge = new ArrayList<Person>()0;
for (Person p : persons) {
    totalAge if+= (p.getAge() >= 18) {
        overEighteen.add(p);
    }
}
System.out.println(overEighteentotalAge);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream().filter(p.stream().map(Person::getAge).reduce((a, b) -> p.getAge() >= 18).collect(Collectors.toList()));

 

Siden filter-funksjonen returnerer en stream, bruker vi collect for å gjøre den til en List.

 

Map

a + b).get());

 

I tilfellet over er argumentet a den midlertidige summen av aldre, og b er alderen til neste person i lista. Grunnen til at vi kaller .get() på slutten er fordi reduce returnerer en objekt av typen Optional. Det er en container-type som kan inneholde et eksisterende objekt, eller null. Dersom det inneholder et faktisk objekt, vil isPresent() returnere true, og get() vil returnere verdien. Reduce kan også ta i bruk det doble kolonet. La oss finne maksimumsalderen:Map brukes for å danne en ny liste av en annen liste, der en gitt funksjon blir kalt på alle elementene i lista. For eksempel, for å få en liste over alle aldrene til personene, vil vi kalle getAge-funksjonen på alle personene i lista, og legge aldrene i en ny liste.

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
List<Integer>int agesmaxAge = new ArrayList<Integer>()0;
for (Person p : persons) {
    ages.add( persons) {
    if (p.getAge() > maxAge)
        maxAge = p.getAge());
}
System.out.println(agesmaxAge);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream().map(Person::getAge).collect(Collectors.toList()reduce(Math::max).get());

 

 

Map tar en instans av typen Function som argument. Du ser kanskje det doble kolonet. Det er en ny operator introdusert i Java 8 som lar en referere til en metode i seg selv, i stedet for returverdien til den metoden. Slik kan vi enkelt gi den metoden vi ønsker som argument. 

Det finnes spesielle map-funksjoner også, som mapToInt og mapToDouble. Disse returnerer henholdsvis IntegerStream og DoubleStream, som har hendige metoder som average(), sum(), min() og max().

Reduce

forEach

forEach tar inn en Consumer-instans og kaller denne instansens ene metode på alle elementene i streamen. La oss legge til et år på alle personenes alder.

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
for (Person p : persons) {
    p.setAge(p.getAge() + 1);
}

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
persons.stream().forEach(p -> p.setAge(p.getAge() + 1));

 

Vi kan også kombinere funksjoner på streams, for eksempel filter og forEach. La oss si vi vil legge til et år på alderen til alle gutter under 18:Reduce brukes for å redusere en liste til et enkelt svar. Reduce tar en akkumulatorfunksjon som argument. Denne akkumulatoren tar to argument: Verdien så langt, og neste element. Reduce brukes ofte sammen med map. La oss finne totalalderen i lista vår.

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
int totalAge = 0;
for (Person p : persons) {
for (Person p : persons) {
    if (p.getAge() < 18 && p.getGender() == 'M')
      totalAge += p.setAge(p.getAge() + 1);
}
System.out.println(totalAge);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream()
	.map(Person::getAge).reduce((a, b) -> a + b).get());

 

filter(p -> p.getAge() < 18 && p.getGender() == 'M')
	.forEach(p -> p.setAge(p.getAge() + 1));

 

 

Peek

Peek ligner veldig på forEach, men i stedet for å returnere void returnerer peek den resulterende streamen.I tilfellet over er argumentet a den midlertidige summen av aldre, og b er alderen til neste person i lista. Grunnen til at vi kaller .get() på slutten er fordi reduce returnerer en objekt av typen Optional. Det er en container-type som kan inneholde et eksisterende objekt, eller null. Dersom det inneholder et faktisk objekt, vil isPresent() returnere true, og get() vil returnere verdien. Reduce kan også ta i bruk det doble kolonet. La oss finne maksimumsalderen:

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
intList<Person> maxAgeagedPersons = new 0ArrayList<Person>();
for (Person p : persons) {
    if p.setAge(p.getAge() >+ maxAge1);
        maxAge = p.getAge(agedPersons.add(p);
}
System.out.println(maxAgeagedPersons);

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 SystemSystem.out.println(persons.stream().map(Person::getAge).reduce(Math::max).get(
	.peek(p -> p.setAge(p.getAge() + 1))
	.collect(Collectors.toList()));