...
Definisjonen av funksjonelle grensesnitt er i utgangspunktet enkel: Det er nemlig grensesnitt som har kun én (abstrakt) metode definert. Her er et eksempel på dette:
Code Block |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | title | DoubleValueComputer |
---|
|
public interface DoubleValueComputer {
public double compute(double x, double y);
} |
...
Denne direkte implementasjonen sparer oss mye kode, men det er fortsatt ganske tungvint. Lambda-uttrykk lar oss gjøre dette mye enklere. Høyre kolonne viser hvordan man kan gjøre nøyaktig det samme, på én linje.
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Direkte implementasjon (anonym klasse) |
---|
| DoubleValueComputer adder = new DoubleValueComputer() {
@Override
public double compute(double x, double y) {
return x + y;
}
};
DoubleValueComputer multiplier = new DoubleValueComputer() {
@Override
public double compute(double x, double y) {
return x * y;
}
};
|
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med lambda |
---|
| DoubleValueComputer adder = (x, y) -> x + y;
DoubleValueComputer multiplier = (x, y) -> x * y; |
|
...
Consumer-grensesnittet har metoden accept, som tar inn et objekt av hvilken som helst type (det vil si Object) som argument, og returnerer ingenting (void).
BinaryOparator<T>BinaryOperator<T>-grensesnittet har metoden apply, som tar inn to objekt av typen T, og og returnerer ett objekt av samme type. For eksempel addisjon: Tar inn to doubles, returnerer summen (én double).
...
Vi definerer en Person-klasse for å ha noe å leke med:
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Person.java |
---|
collapse | true |
---|
|
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; }
} |
Vi oppretter også en PersonMain-klasse som skal inneholde en liste over Person-objekter, så vi kan demonstrere bruken av streams:
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | PersonMain.java |
---|
collapse | true |
---|
|
import java.util.ArrayList;
import java.util.List;
public class PersonMain {
List<Person> persons = new ArrayList<Person>();
public void init() {
persons.add(new Person("Ola", 10, 'M'));
persons.add(new Person("Kari", 12, 'F'));
persons.add(new Person("Per", 22, 'M'));
persons.add(new Person("Pål", 17, 'M'));
persons.add(new Person("Espen", 19, 'M'));
}
public void run() {
}
public static void main(String[] args) {
PersonMain program = new PersonMain();
program.init();
program.run();
}
} |
...
Å sortere med Comparator blir veldig enkelt med lambda, da Comparator-grensesnittet er funksjonelt. La oss sortere personene på navn (eksempel 1) og på alder (eksempel 2), og skrive ut resultatet etterpå:
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Tradisjonell måte (anonym klasse) |
---|
| persons.sort(new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getName().compareTo(b.getName());
}
});
System.out.println(persons); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| persons.sort((a, b) -> a.getName().compareTo(b.getName()));
System.out.println(persons); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Resultat |
---|
| [Espen 19 M, Kari 12 F, Ola 10 M, Per 22 M, Pål 17 M] |
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Tradisjonell måte (anonym klasse) |
---|
| persons.sort(new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
return a.getAge() - b.getAge();
});
System.out.println(persons); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| persons.sort((a, b) -> a.getAge() - b.getAge());
System.out.println(persons); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Resultat |
---|
| [Ola 10 M, Kari 12 F, Pål 17 M, Espen 19 M, Per 22 M] |
|
...
For eksempel, finnes det en kvinne i lista vår?
Code Block |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | title | Tradisjonell måte |
---|
| boolean womanExists = false;
for (Person p : persons) {
if (p.getGender() == 'F') {
womanExists = true;
break;
}
}
System.out.println(womanExists); |
| Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream().anyMatch(p -> p.getGender() == 'F')); |
Code Block |
---|
language | java |
---|
title | 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 |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | title | Resultat |
---|
| 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.
...
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 |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | title | 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 |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream().filter(p -> p.getAge() >= 18).collect(Collectors.toList())); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Resultat |
---|
| [Per 22 M, Espen 19 M] |
|
...
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 |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Tradisjonell måte |
---|
| List<Integer> ages = new ArrayList<Integer>();
for (Person p : persons) {
ages.add(p.getAge());
}
System.out.println(ages); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream().map(Person::getAge).collect(Collectors.toList())); |
Code Block |
---|
language | java |
---|
title | 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 |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Resultat |
---|
| [10, 12, 22, 17, 19] |
|
...
Reduce brukes ofte sammen med map. La oss finne totalalderen i lista vår.
Code Block |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Tradisjonell måte |
---|
| int totalAge = 0;
for (Person p : persons) {
totalAge += p.getAge();
}
System.out.println(totalAge); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream().map(Person::getAge).reduce((a, b) -> a + b).get()); |
Code Block |
---|
language | java |
---|
title | 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 |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | 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:
Code Block |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | title | Tradisjonell måte |
---|
| int maxAge = 0;
for (Person p : persons) {
if (p.getAge() > maxAge)
maxAge = p.getAge();
}
System.out.println(maxAge); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream().map(Person::getAge).reduce(Math::max).get()); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | 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.
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Tradisjonell måte |
---|
| for (Person p : persons) {
p.setAge(p.getAge() + 1);
}
System.out.println(persons); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| persons.stream().forEach(p -> p.setAge(p.getAge() + 1));
System.out.println(persons); |
Code Block |
---|
language | java |
---|
title | Consumer-grensesnittet |
---|
| @FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*/
void accept(T t);
} |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language |
---|
java | 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:
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | 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 |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | 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 |
---|
language | java |
---|
theme | Eclipse |
---|
language | java |
---|
title | Resultat |
---|
| [Ola 11 M, Kari 12 F, Per 22 M, Pål 18 M, Espen 19 M] |
|
...
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 |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Tradisjonell 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 |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Med stream og lambda |
---|
| System.out.println(persons.stream()
.peek(p -> p.setAge(p.getAge() + 1))
.collect(Collectors.toList())); |
|
Code Block |
---|
language | java |
---|
theme | Eclipse | language | java |
---|
title | Resultat |
---|
| [Ola 11 M, Kari 13 F, Per 23 M, Pål 18 M, Espen 20 M] |
|
...