Liigu peamise sisu juurde

Staatilised ja default meetodid liidestes

Sissejuhatus

Enne Java 8-t võisid liidesed sisaldada ainult abstraktseid meetodeid. Meetodite implementatsioone polnud võimalik liideses endas hoida.
Praktikas see tähendas seda, et kui liides oli juba loodud, avaldatud ning paljud klassid implementeerisid seda, siis uue meetodi lisamine antud liidesele tekitas kompileerimisvea (kuna kõik liidese meetodid peavad implementeeritud olema klassis).

Java 8 lahendas selle probleemi ära, tuues liidestesse sisse default ja staatilised meetodid.

Default meetodid

Default-meetod on liidese meetod, millel on implementatsioon. Implementeerivad klassid pärivad selle automaatselt ega pea seda ise defineerima.

interface Drawable {
void draw();

default void drawWithBorder() {
System.out.println("--- border ---");
draw();
System.out.println("--- border ---");
}
}

Iga klass, mis implementeerib Drawable liidest, saab drawWithBorder() meetodi automaatselt kaasa:

class Circle implements Drawable {
private double radius;

public Circle(double radius) { this.radius = radius; }

@Override
public void draw() {
System.out.println("Drawing circle with radius " + radius);
}
// drawWithBorder() is inherited automatically
}
Circle c = new Circle(5.0);
c.draw(); // Drawing circle with radius 5.0
c.drawWithBorder();
// --- border ---
// Drawing circle with radius 5.0
// --- border ---

Vajadusel võib klass default-meetodi üle kirjutada ja pakkuda teistsugust käitumist:

class DashedCircle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing dashed circle");
}

@Override
public void drawWithBorder() {
System.out.println("=== border ===");
draw();
System.out.println("=== border ===");
}
}

Staatilised meetodid

Lisaks default-meetoditele võivad liidesed sisaldada ka staatilisi meetodeid. Erinevalt default-meetoditest ei päri implementeerivad klassid neid meetodeid.

Staatiline meetod kuulub liidesele endale ning seda saab välja kutsuda ainult liidese nime kaudu.

interface Validator {
boolean validate(String input);

static Validator nonEmpty() {
return input -> input != null && !input.isEmpty();
}
}
Validator v = Validator.nonEmpty();
System.out.println(v.validate("hello")); // true
System.out.println(v.validate("")); // false

Antud näites toimib nonEmpty() tehase-meetodina, mis loob konkreetse Validator implementatsiooni.

teade

Meetodi kehas kasutatav input -> ... süntaks on lambda-avaldis — lühike viis liidese implementatsiooni kirjutamiseks. Lambda-avaldisi käsitleme eraldi hilisemas peatükis.

Staatilised liidese meetodid sobivad hästi tehase-stiilis abimeetodite jaoks, mis on kontseptuaalselt liidese lepingu osa. See tähendab, et meetod on loogiliselt seotud liidese lepinguga, kuid ei kuulu ühegi objekti konkreetse käitumise hulka ning seda ei ole vaja üle kirjutada.

"Teemantprobleem" ehk mitmikpärilikkus

Kui klass implementeerib kahte liidest, millel mõlemal on sama signatuuriga default-meetod, ei suuda kompilaator otsustada, kumba implementatsiooni kasutada. Seda nimetatakse teemantprobleemiks (diamond problem).

Olgu meil järgmise struktuuriga kood:

Ning selle teostus:

interface A {
default void greet() {
System.out.println("Hello from A");
}
}

interface B {
default void greet() {
System.out.println("Hello from B");
}
}

class C implements A, B {
// Compile error: C inherits unrelated defaults for greet() from A and B
}

Klass C ei tea, kumma liidese greet() meetodit peaks kasutama. Sellisel juhul peab klass konflikti lahendama meetodi üle kirjutamisega:

class C implements A, B {
@Override
public void greet() {
System.out.println("Hello from C");
// Optionally delegate to one of the interfaces:
// A.super.greet();
// B.super.greet();
}
}

A.super.greet() ja B.super.greet() on erisüntaks, mille abil saab ülekirjutava klassi sees kutsuda konkreetse liidese default-meetodit.

hoiatus

Teemantprobleem tekib ainult siis, kui kaks liidest pakuvad konfliktseid default-implementatsioone. Kui ainult ühel liidesel on default-implementatsioon ja teine deklareerib meetodi abstraktsena, konflikti ei teki. default-implementatsiooni kasutatakse automaatselt üle teiste.

Privaatsed meetodid (Java 9+)

Alates Java 9-st võivad liidesed sisaldada ka privaatseid meetodeid. Need on abimeetodid, mida kasutatakse liidese sees (nt default-meetodites) ning mis ei ole implementeerivatele klassidele nähtavad.

interface Drawable {
void draw();

default void drawWithBorder() {
printLine();
draw();
printLine();
}

private void printLine() {
System.out.println("---");
}
}

Privaatsed meetodid võimaldavad vältida koodi kordamist default-meetodite sees, ilma et abimeetodeid liidesest väljapoole nähtavaks tehtaks.