Java Language
Metody domyślne
Szukaj…
Wprowadzenie
Metoda domyślna wprowadzona w Javie 8 pozwala programistom dodawać nowe metody do interfejsu bez przerywania istniejących implementacji tego interfejsu. Zapewnia elastyczność pozwalającą interfejsowi zdefiniować implementację, która będzie używana domyślnie, gdy klasa implementująca ten interfejs nie zapewni implementacji tej metody.
Składnia
- public default void methodName () {/ * body method * /}
Uwagi
Metody domyślne
- Może być używany w interfejsie w celu wprowadzenia zachowania bez zmuszania istniejących podklas do jego wdrożenia.
- Może być zastąpiony przez podklasy lub pod-interfejs.
- Nie wolno zastępować metod w klasie java.lang.Object.
- Jeśli klasa implementująca więcej niż jeden interfejs dziedziczy metody domyślne z identycznymi sygnaturami metod z każdego interfejsu, wówczas musi zastąpić i udostępnić własny interfejs, tak jakby nie były to metody domyślne (w ramach rozwiązywania wielokrotnego dziedziczenia).
- Chociaż mają na celu wprowadzenie zachowania bez przerywania istniejących implementacji, istniejące podklasy z metodą statyczną z taką samą sygnaturą metody, jak nowo wprowadzona metoda domyślna, nadal będą zepsute. Jest to jednak prawdą nawet w przypadku wprowadzenia metody instancji do nadklasy.
Metody statyczne
- Może być używany w interfejsie, przeznaczony głównie do stosowania jako metoda narzędziowa dla metod domyślnych.
- Nie można go przesłonić podklasami ani pod-interfejsem (jest dla nich ukryty). Jednak tak jak w przypadku metod statycznych nawet teraz, każda klasa lub interfejs może mieć swoją własną.
- Nie wolno przesłonić metod instancji w klasie java.lang.Object (tak jak ma to obecnie miejsce również w przypadku podklas).
Poniżej znajduje się tabela podsumowująca interakcje między podklasą i superklasą.
- | SUPER_CLASS-INSTANCE-METHOD | SUPER_CLASS-STATIC-METHOD |
---|---|---|
METODA SUB_CLASS-INSTANCE | zastępuje | generuje błąd podczas kompilacji |
SUB_CLASS-STATIC-METHOD | generuje błąd podczas kompilacji | chowa się |
Poniżej znajduje się tabela podsumowująca interakcję między interfejsem a klasą implementacyjną.
- | DOMYŚLNA METODA INTERFEJSU | METODA INTERFEJS-STATYCZNA |
---|---|---|
IMPL_CLASS-INSTANCE-METHOD | zastępuje | chowa się |
IMPL_CLASS-STATIC-METHOD | generuje błąd podczas kompilacji | chowa się |
Bibliografia :
- http://www.journaldev.com/2752/java-8-interface-changes-static-method-default-method
- https://docs.oracle.com/javase/tutorial/java/IandI/override.html
Podstawowe użycie metod domyślnych
/**
* Interface with default method
*/
public interface Printable {
default void printString() {
System.out.println( "default implementation" );
}
}
/**
* Class which falls back to default implementation of {@link #printString()}
*/
public class WithDefault
implements Printable
{
}
/**
* Custom implementation of {@link #printString()}
*/
public class OverrideDefault
implements Printable {
@Override
public void printString() {
System.out.println( "overridden implementation" );
}
}
Poniższe stwierdzenia
new WithDefault().printString();
new OverrideDefault().printString();
Wyprodukuje ten wynik:
default implementation
overridden implementation
Dostęp do innych metod interfejsu w ramach metody domyślnej
Możesz również uzyskać dostęp do innych metod interfejsu z poziomu metody domyślnej.
public interface Summable {
int getA();
int getB();
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {
@Override
public int getA() {
return 1;
}
@Override
public int getB() {
return 2;
}
}
Następująca instrukcja wydrukuje 3 :
System.out.println(new Sum().calculateSum());
Można również użyć domyślnych metod wraz ze statycznymi metodami interfejsu:
public interface Summable {
static int getA() {
return 1;
}
static int getB() {
return 2;
}
default int calculateSum() {
return getA() + getB();
}
}
public class Sum implements Summable {}
Następująca instrukcja wypisze również 3:
System.out.println(new Sum().calculateSum());
Dostęp do zastąpionych domyślnych metod z klasy implementującej
W klasach super.foo()
będzie wyglądać tylko w superklasach. Jeśli chcesz wywołać domyślną implementację z superinterface, musisz zakwalifikować super
z nazwą interfejsu: Fooable.super.foo()
.
public interface Fooable {
default int foo() {return 3;}
}
public class A extends Object implements Fooable {
@Override
public int foo() {
//return super.foo() + 1; //error: no method foo() in java.lang.Object
return Fooable.super.foo() + 1; //okay, returns 4
}
}
Dlaczego warto korzystać z metod domyślnych?
Prosta odpowiedź jest taka, że pozwala ewoluować istniejący interfejs bez przerywania istniejących implementacji.
Na przykład masz interfejs Swim
, który opublikowałeś 20 lat temu.
public interface Swim {
void backStroke();
}
Wykonaliśmy świetną robotę, nasz interfejs jest bardzo popularny, istnieje wiele implementacji tego na całym świecie i nie masz kontroli nad ich kodem źródłowym.
public class FooSwimmer implements Swim {
public void backStroke() {
System.out.println("Do backstroke");
}
}
Po 20 latach zdecydowałeś się dodać nową funkcjonalność do interfejsu, ale wygląda na to, że nasz interfejs jest zawieszony, ponieważ zepsuje istniejące implementacje.
Na szczęście Java 8 wprowadza zupełnie nową funkcję o nazwie Metoda domyślna.
Możemy teraz dodać nową metodę do interfejsu Swim
.
public interface Swim {
void backStroke();
default void sideStroke() {
System.out.println("Default sidestroke implementation. Can be overridden");
}
}
Teraz wszystkie istniejące implementacje naszego interfejsu mogą nadal działać. Ale co najważniejsze, mogą wdrożyć nowo dodaną metodę we własnym czasie.
Jednym z największych powodów tej zmiany i jednym z jej największych zastosowań jest środowisko Java Collections. Oracle nie mógł dodać metody foreach
do istniejącego interfejsu Iterable bez zerwania całego istniejącego kodu, który zaimplementował Iterable. Dodając domyślne metody, istniejąca implementacja Iterable odziedziczy domyślną implementację.
Pierwszeństwo klasy, klasy abstrakcyjnej i metody interfejsu
Implementacje w klasach, w tym deklaracje abstrakcyjne, mają pierwszeństwo przed wszystkimi domyślnymi ustawieniami interfejsu.
- Metoda klasy abstrakcyjnej ma pierwszeństwo przed domyślną metodą interfejsu .
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
public void backStroke() {
System.out.println("AbstractSwimmer.backStroke");
}
}
public class FooSwimmer extends AbstractSwimmer {
}
Poniższe oświadczenie
new FooSwimmer().backStroke();
Będzie produkować
AbstractSwimmer.backStroke
- Metoda klasy ma pierwszeństwo przed domyślną metodą interfejsu
public interface Swim {
default void backStroke() {
System.out.println("Swim.backStroke");
}
}
public abstract class AbstractSwimmer implements Swim {
}
public class FooSwimmer extends AbstractSwimmer {
public void backStroke() {
System.out.println("FooSwimmer.backStroke");
}
}
Poniższe oświadczenie
new FooSwimmer().backStroke();
Będzie produkować
FooSwimmer.backStroke
Domyślna metoda kolizji wielokrotnego dziedziczenia
Rozważ następny przykład:
public interface A {
default void foo() { System.out.println("A.foo"); }
}
public interface B {
default void foo() { System.out.println("B.foo"); }
}
Oto dwa interfejsy deklarujące default
metodę foo
z tym samym podpisem.
Jeśli spróbujesz extend
oba interfejsy w nowym interfejsie, musisz wybrać dwa, ponieważ Java zmusza cię do jawnego rozwiązania tej kolizji.
Po pierwsze , możesz zadeklarować metodę foo
z taką samą sygnaturą jak abstract
, która zastąpi zachowanie A
i B
public interface ABExtendsAbstract extends A, B {
@Override
void foo();
}
A kiedy implement
ABExtendsAbstract
w class
, będziesz musiał zapewnić implementację foo
:
public class ABExtendsAbstractImpl implements ABExtendsAbstract {
@Override
public void foo() { System.out.println("ABImpl.foo"); }
}
Po drugie , możesz podać zupełnie nową default
implementację. Możesz także ponownie użyć kodu metod foo
A
i B
uzyskując dostęp do zastąpionych metod domyślnych z klasy implementującej .
public interface ABExtends extends A, B {
@Override
default void foo() { System.out.println("ABExtends.foo"); }
}
A kiedy implement
ABExtends
w class
, not
będziesz musiał zapewniać implementacji foo
:
public class ABExtendsImpl implements ABExtends {}