Versions Compared

Key

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

...

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 om minst ett av elementene i den aktuelle streamen tilfredsstiller predikatet. Predicate-grensesnittet representerer en funksjon som svarer ja eller nei på om et objekt tilfredsstiller et bestemt krav, og er gjengitt under.

For eksempel, finnes det en kvinne i lista vår? Vi skriver det ut.

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

 

 

 

Code Block
themeEclipse
languagejava
titleMed stream og lambda
 System.out.println(persons.stream().anyMatch(p -> p.getGender() == 'F'));
Code Block
themeEclipse
languagejava
titleResultat
true

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 metoder som ligner på anyMatch: allMatch (alle element i en stream tilfredsstiller predikatet), og noneMatch (ingen matcher). Det er lurt å benytte seg av ctrl + space for å bla gjennom metodene man kan bruke.

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 (instans av Predicate-grensesnittet nevnt tidligere). Den filtrerer med andre ord ut alle element som ikke matcher predikatet, og returnerer resulterende stream. For eksempel: Hvilke personer er over 18 år?

Predicate-grensesnittet
@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     * return true if the input argument matches the predicate, otherwise false
     */
    boolean test(T t);
}
Code Block
themeEclipse
languagejava
titleResultat
true

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 om den aktuelle personens kjønn (gender) er kvinne. Merk at siden Predicate sin test-metode og dermed også lambda-uttrykket bare tar ett parameter, så kan vi utelate parenteser rundt parameterlista.

Det er flere metoder som ligner på anyMatch: allMatch (alle element i en stream tilfredsstiller predikatet), og noneMatch (ingen matcher). Det er lurt å benytte seg av ctrl + space for å bla gjennom metodene man kan bruke.

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 (instans av Predicate-grensesnittet nevnt tidligere). 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
titleTradisjonell 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()));

 

Code Block
themeEclipse
languagejava
title
Resultat
[Per 22 M, Espen 19 M]

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.

 

List<Integer> ages = new ArrayList<Integer>(); for (Person p : persons) { ages.add(p.getAge()); } System.out.println(ages

 

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())
Code Block
themeEclipse
languagejava
titleTradisjonell måte
);

 

 

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

 

Code Block
themeEclipse
languagejava
titleResultat
[10, 12, 22, 17, 19]

 

Resultat
[Per 22 M, Espen 19 M]

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. Map Map tar en instans av typen Function-grensesnittet 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 ett enkelt resultat. Reduce tar en BinaryOperator-instans som man kaller akkumulator (akkumulere == samle) 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.

Function-grensesnittet representerer en alminnnelig funksjon som tar inn ett argument av én type og returnerer én verdi av en potensielt annen type.

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
theme
Code Block
theme
themeEclipse
Eclipse
languagejava
title
Tradisjonell måte
int totalAge = 0;
for (Person p : persons) {
    totalAge += p.getAge();
}
System.out.println(totalAge);
Code Block
Med stream og lambda
 System.out.println(persons.stream().map(Person::getAge).collect(Collectors.toList()));
Code Block
languagejava
title
Med stream og lambda
 System.out.println(persons.stream().map(Person::getAge).reduce((a, b) -> a + b).get());
Function-grensesnittet
@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     * returns the function result
     */
    R apply(T t);
}
Code Block
themeEclipse
languagejava
titleResultat
80

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, dersom den aktuelle metoden er en akkumulatorfunksjon som passer med typen til listeelementene. La oss finne maksimumsalderen:

[10, 12, 22, 17, 19]

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 spesialiserte 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 ett enkelt resultat. Reduce tar en BinaryOperator-instans som man kaller akkumulator (akkumulere == samle) som argument. Denne akkumulatoren tar to argument: Verdien så langt, og neste element. BinaryOperator-grensesnittet representerer en funksjon som tar inn to verdier av samme type og returnerer en verdi (også av samme type), og er gjengitt under (egentlig er grensesnittet definert på en litt annen måte, men dette er nokså nærme sannheten).

Reduce brukes ofte sammen med map. La oss finne totalalderen i lista vår.

 

maxAgeif (p.getAge() > maxAge) maxAge maxAge 

 

Math::max

 

 

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
int 
totalAge = 0;
for (Person p : persons) {
    
totalAge += p.getAge();
}
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());
Code Block
themeEclipse
languagejava
title
Resultat
22

 

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.

 

BinaryOperator-grensesnittet
@FunctionalInterface
public interface BinaryOperator<T> {
    /**
     * Applies this function to the given arguments.
     * returns the function result
     */
    T apply(T t1, T t2);
}

 

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

 

Code Block
theme
Code Block
theme
Eclipse
languagejava
title
Resultat
80

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, dersom den aktuelle metoden er en akkumulatorfunksjon som passer med typen til listeelementene. La oss finne maksimumsalderen:

 

 

 

Med stream og lambda
persons.stream().forEach(p -> p.setAge(p.getAge() + 1));
System.out.println(persons);
Code Block
Code Block
themeEclipse
languagejava
title
Resultat
[Ola 11 M, Kari 13 F, Per 23 M, Pål 18 M, Espen 20 M]

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

 

 

 

persons.stream() .filter(p -> p.getAge() < 18 && p.getGender() == 'M')
Tradisjonell måte
int maxAge = 0;
for (Person p : persons) {
    if (p.getAge() > maxAge)
        maxAge = p.getAge();
}
System.out.println(maxAge);
Code Block
themeEclipse
languagejava
title
Tradisjonell måte
Med stream og lambda
 System.out.println(persons.stream().map(Person::getAge).reduce(Math::max).get());
for (Person p : persons) {
    if (p.getAge() < 18 && p.getGender() == 'M')
        p.setAge(p.getAge() + 1);
}
System.out.println(persons);
Code Block
Code Block
themeEclipse
languagejava
title
Med stream og lambda
Resultat
22

 

forEach

forEach tar inn en Consumer-instans og kaller denne instansens ene metode på alle elementene i streamen. Consumer-grensesnittet representerer en funksjon som bare bruker (opp) argumentet, uten å gi noe resultat tilbake, og er gjengitt under.

La oss legge til et år på alle personenes alder.

 

 

Eclipse

 

Code Block
themeEclipse
languagejava
titleTradisjonell måte
for (Person p : persons) {
    p.setAge(p.getAge() + 1);
}
System.out.println(persons);
Code Block
themeEclipse
languagejava
titleMed stream og lambda
persons.stream().forEach(p -> p.setAge(p.getAge() + 1));
System.out.println(persons);
Code Block
theme
languagejava
title
Resultat
[Ola 11 M, Kari 12 F, Per 22 M, Pål 18 M, Espen 19 M]

 

Peek

Peek ligner veldig på forEach, men i stedet for å returnere void returnerer peek den resulterende streamen. Derfor kan man skrive ut resultatet direkte, uten å gjøre som i de to eksemplene over.

Consumer-grensesnittet
@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     */
    void accept(T t);
}
Code Block
themeEclipse
languagejava
titleResultat
[Ola 11 M, Kari 13 F, Per 23 M, Pål 18 M, Espen 20 M]

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

 

System.out.println(persons.stream() .peek(p -> p.setAge+ 1)) .collect(Collectors.toList()));

 

 

[Ola 11 M, Kari 13 F, Per 23 M, Pål 18 M, Espen 20 M]

 

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

 

Code Block
themeEclipse
languagejava
title
Med stream og lambda
Tradisjonell måte
for (Person p : persons) {
    if (p.getAge() 
< 18 && p.getGender() == 'M')
        p.setAge(p.getAge() + 1);
}
System.out.println(persons);
Code Block
themeEclipse
languagejava
title
Resultat
Med stream og lambda
persons.stream()
	.filter(p -> p.getAge() < 18 && p.getGender() == 'M')
	.forEach(p -> p.setAge(p.getAge() + 1));
System.out.println(persons);
Code Block
themeEclipse
languagejava
titleResultat
[Ola 11 M, Kari 12 F, Per 22 M, Pål 18 M, Espen 19 M]

 

Peek

Peek ligner veldig på forEach, men i stedet for å returnere void returnerer peek den resulterende streamen. Derfor kan man skrive ut resultatet direkte, uten å gjøre som i de to eksemplene over.

Code Block
themeEclipse
languagejava
titleTradisjonell måte
List<Person> agedPersons = new ArrayList<Person>();
for (Person p : persons) {
    p.setAge(p.getAge() + 1);
    agedPersons.add(p);
}
System.out.println(agedPersons);
Code Block
themeEclipse
languagejava
titleMed stream og lambda
System.out.println(persons.stream()
	.peek(p -> p.setAge(p.getAge() + 1))
	.collect(Collectors.toList()));
Code Block
themeEclipse
languagejava
titleResultat
[Ola 11 M, Kari 13 F, Per 23 M, Pål 18 M, Espen 20 M]

 

Oppgaver

Her er noen oppgaver du kan øve deg på. Alle tar utgangspunkt i følgende klasse for kort (i en kortstokk):

Code Block
public class Card {

   private final char suit;
   private final int face;

   // initialises with suit ('S'=spade, 'H'=heart, 'D'=diamonds, 'C'=clubs) and face (1=ace, 2, ... 10, 11=knight, 12=queen and 13=king).
   public Card(char suit, int face) {
      this.suit = suit;
      this.face = face;
   }

   @Override
   public String toString() {
      return String.format("%s%s", suit, face);
   }

   public char getSuit() {
      return suit;
   }

   public int getFace() {
      return face;
   }
}

For alle oppgavene trengs også en liste med kort, f.eks. Collection<Card> cards = Arrays.asList(new Card('S', 1), new Card('H', 2), new Card('D', 12), new Card('C', 13));

Prøv å kjøre uttrykket med forskjellige kort-lister, så du får sjekket at det virker som det skal (og som du forventer).

  • Skriv et uttrykk med filter og forEach som skriver ut alle spar-kort (suit = 'S').
  • Skriv et uttrykk med filter og collect som samler alle spar-kort (suit = 'H') i en ny liste.
  • Skriv et uttrykk med map som gir en ny list med kortenes kortfarge.
  • Skriv et uttrykk med reduce som gir summen av kortverdiene (face).
  • Skriv et uttrykk med anyMatch som sier om spar dame finnes i lista.
  • Skriv et uttrykk som sier om lista er en poker-flush, dvs. har fem kort hvor alle har samme kortfarge.
  • Skriv et uttrykk som sjekker om lista har kort av alle de fire kortfargene. Dette vil kreve bruk av to Stream-metoder som ikke er vist over. Hint: Finn og anvend metoder for å fjerne duplikater og telle elementer.