Lambda og funksjonelle grensesnitt

Relevante seksjoner i offisiell tutorial: lambda-uttrykk.


Hva er et funksjonelt grensesnitt?

Et grensesnitt er funksjonelt dersom det inneholder nøyaktig én metode som ikke er har en default-implementasjon. Man kan inkludere annotasjonen @FunctionalInterface når man definerer et grensesnitt man ønsker skal være funksjonelt, men dette er (teknisk sett) frivillig.

Hensikten med funksjonelle grensesnitt er at vi kan behandle metoder som om de var objekter, og dermed tenke oss at vi sender metoder rundt omkring mellom ulike deler av programmet, og ikke bare data. Legg merke til hvordan vi kan lagre metoder i variabler vist i main-metoden under.

// Et funksjonelt grensesnitt (har kun én metode)
@FunctionalInterface
interface NoiceMaker {
  String makeNoice();
}

// En klasse med noen metoder
class AnimalSounds {
  static String cow() {
    return "Moooooo";
  }

  String cat() {
    return "Meeeow";
  }

  String dog() {
    return "Voof";
  }
}

public class Main {
  public static void main(String[] args) {
    AnimalSounds animalSounds = new AnimalSounds();

    // Vi kan pakke inn enhver metode med lik returtype og parametertyper til
    // `makeNoice`-metoden som et NoiceMaker-objekt med `::` -syntaksen:
    NoiceMaker bark = animalSounds::dog;
    NoiceMaker meow = animalSounds::cat; // Instansmetode (objekt::metodeNavn)
    NoiceMaker moo = AnimalSounds::cow;  // Klassemetode (KlasseNavn::metodeNavn)
    printNoice(bark);
    printNoice(meow);
    printNoice(moo);
  }

  static void printNoice(NoiceMaker x) {
    System.out.println(x.makeNoice());
  }
}

Funksjonelle grensesnitt i Java sitt standardbibliotek

Mange grensesnitt i java sitt standardbibliotek er funksjonelle; for eksempel har vi tidligere sett Iterable og såvidt vært borti ActionListener (se også eksempelet ControllerBlink i kursnotatene om en blinkende ball).

I tillegg har vi en del grensesnitt som er laget for å være helt generelle funksjonelle grensesnitt, som vi kan importere fra pakken java.util.function (✱ eller som ligger i java.lang og derfor ikke trenger å importeres):

Navn på grensesnitt Metodesignatur
Runnable void run()
Consumer<T> void accept(T)
BiConsumer<T, U> void accept(T, U)
Predicate<T> boolean test(T)
BiPredicate<T, U> boolean test(T, U)
Supplier<R> R get()
Function<T, R> R apply(T)
BiFunction<T, U, R> R apply(T, U)

Lambda-uttrykk

Et lambda-uttrykk er en metode man pakker direkte inn i et funksjonelt grensesnitt uten å gi den et metode-navn først. Syntaksen for å angi et lambda-uttrykk er (parametre) -> { metodekropp }.

// Oppretter en metode uten navn ved hjelp av et lambda-uttrykk:
//   PS: run-metoden i Runnable har ingen parametre,
//   derfor er det ingenting i parantesene før `->`
Runnable myLambdaFunction = () -> {
  // Her er metode-kroppen
  System.out.println("This method does two things:");
  System.out.println("Prints first the line above, then prints this line.");
};

// Kjører metoden flere ganger
myLambdaFunction.run();
myLambdaFunction.run();

Lambda-uttrykk kan ha parametre og gi returner-verdier, akkurat som vanlige metoder. Til forskjell fra vanlige metoder er det imidlertid ikke nødvendig å angi typene til parameterne og retur-verdien, siden dette allerede er bestemt av typen til det funksjonelle grensesnittet.

// Oppretter en metode uten navn ved hjelp av et lambda-uttrykk:
BiFunction<Double, Double, Double> myAdd = (x, y) -> {
  Double result = x + y;
  return result;
};

// Kjører metoden flere ganger
System.out.println(myAdd.apply(0.5, 1.5)); // 2.0
System.out.println(myAdd.apply(0.25, 0.5)); // 0.75

Lambda-uttrykk der metode-kroppen i bunn og grunn bare består av én linje kan også skrives mer kompakt ved å droppe krøllparanteser og return-kodeordet. Samme kode som over kan altså skrives slik:

BiFunction<Double, Double, Double> myAdd = (x, y) -> (x + y);

System.out.println(myAdd.apply(0.5, 1.5)); // 2.0
System.out.println(myAdd.apply(0.25, 0.5)); // 0.75

Et lignende eksempel med andre typeparametre: lambda-uttrykket under sjekker om en streng er lengre enn en gitt lengde, og returnerer en boolsk verdi.

BiFunction<String, Integer, Boolean> longerThan = (s, lim) -> (s.length() > lim);

System.out.println(longerThan.apply("Foo", 2)); // true
System.out.println(longerThan.apply("Bar", 4)); // false