Tłumaczenie artykułu Petera Verhasa z kwietnia 2014 r. Od tłumacza: termin „ metoda domyślna ” właśnie pojawił się w Javie i nie jestem pewien, czy istnieje jego tłumaczenie na język rosyjski. Będę używał terminu „metoda domyślna”, chociaż nie sądzę, że jest idealna. Zapraszam do dyskusji na temat bardziej udanego tłumaczenia.
Jaka jest metoda domyślna
Teraz, wraz z wydaniem Java 8, można dodawać nowe metody do interfejsów, dzięki czemu interfejs pozostaje kompatybilny z klasami, które go implementują. Jest to bardzo ważne, jeśli tworzysz bibliotekę używaną przez wielu programistów od Kijowa po Nowy Jork. Przed wersją Java 8, jeśli interfejs był definiowany w bibliotece, nie można było dodawać do niego metod bez ryzyka, że jakaś aplikacja uruchamiająca interfejs ulegnie awarii podczas aktualizacji. Czyli w Javie 8 nie można się już tego bać? Nie, nie możesz. Dodanie metody domyślnej do interfejsu może sprawić, że niektóre klasy staną się bezużyteczne. Przyjrzyjmy się najpierw dobrym stronom metod domyślnych. W Javie 8 metodę można zaimplementować bezpośrednio w interfejsie. (Teraz można także implementować metody statyczne w interfejsie, ale to już inna historia.) Metoda zaimplementowana w interfejsie nazywana jest metodą domyślną i jest oznaczana słowem kluczowym default . Jeśli klasa implementuje interfejs, może, ale nie musi, implementować metody zaimplementowane w interfejsie. Klasa dziedziczy domyślną implementację. Dlatego nie jest konieczne modyfikowanie klas przy zmianie implementowanego przez nie interfejsu.Dziedziczenie wielokrotne?
Sprawy stają się bardziej skomplikowane, jeśli klasa implementuje więcej niż jeden (powiedzmy dwa) interfejsy i implementuje tę samą domyślną metodę. Którą metodę odziedziczy klasa? Odpowiedź brzmi: żadna. W takim przypadku klasa musi sama zaimplementować metodę (bezpośrednio lub dziedzicząc ją z innej klasy). Podobnie sytuacja wygląda, jeśli tylko jeden interfejs ma metodę domyślną, a w drugim ta sama metoda jest abstrakcyjna. Java 8 stara się zachować dyscyplinę i unikać niejednoznacznych sytuacji. Jeżeli metody są zadeklarowane w więcej niż jednym interfejsie, to klasa nie dziedziczy żadnej domyślnej implementacji - pojawi się błąd kompilacji. Chociaż błąd kompilacji może nie zostać wyświetlony, jeśli Twoja klasa jest już skompilowana. Java 8 nie jest pod tym względem wystarczająco solidna. Są ku temu powody, których nie chcę wdawać się w dyskusję (przykładowo: wydanie Java zostało już wydane, a czas na dyskusje dawno minął i w ogóle to nie miejsce na nie).- Załóżmy, że masz dwa interfejsy i klasa implementuje oba z nich.
- Jeden z interfejsów implementuje domyślną metodę m().
- Kompilujesz wszystkie interfejsy i klasę.
- Zmieniasz interfejs, który nie ma metody m(), deklarując go jako metodę abstrakcyjną.
- Kompilujesz tylko zmodyfikowany interfejs.
- Rozpocznij zajęcia.
- zmień interfejs za pomocą abstrakcyjnej metody m() i dodaj domyślną implementację.
- Skompiluj zmodyfikowany interfejs.
- Uruchom klasę: błąd.
Przykładowy kod
Aby to zademonstrować utworzyłem katalog testowy dla klasy C.java oraz 3 podkatalogi dla interfejsów w plikach I1.java i I2.java. Katalog główny testu zawiera kod źródłowy klasy C.java. Katalog bazowy zawiera wersję interfejsów odpowiednią do wykonania i kompilacji: interfejs I1 ma domyślną metodę m(); Interfejs I2 nie ma jeszcze żadnych metod. Klasa ma metodę,main
dzięki czemu możemy ją wykonać w celu przetestowania. Sprawdza, czy istnieją argumenty wiersza poleceń, dzięki czemu możemy go łatwo wykonać z wywołaniem metody m()
.
~/github/test$ cat C.java
public class C implements I1, I2 {
public static void main(String[] args) {
C c = new C();
if( args.length == 0 ){
c.m();
}
}
}
~/github/test$ cat base/I1.java
public interface I1 {
default void m(){
System.out.println("hello interface 1");
}
}
~/github/test$ cat base/I2.java
public interface I2 {
}
Możesz skompilować i uruchomić klasę z wiersza poleceń.
~/github/test$ javac -cp .:base C.java
~/github/test$ java -cp .:base C
hello interface 1
Kompatybilny katalog zawiera wersję interfejsu I2, która deklaruje, że metoda m() jest abstrakcyjna, a także, ze względów technicznych, niezmodyfikowaną kopię I1.java.
~/github/test$ cat compatible/I2.java
public interface I2 {
void m();
}
Takiego zestawu nie można użyć do skompilowania klasy C:
~/github/test$ javac -cp .:compatible C.java
C.java:1: error: C is not abstract and does not override abstract method m() in I2
public class C implements I1, I2 {
^
1 error
Komunikat o błędzie jest bardzo dokładny. Mamy jednak C.class z poprzedniej kompilacji i jeśli skompilujemy interfejsy do kompatybilnego katalogu, będziemy mieli dwa interfejsy, których nadal będzie można używać do uruchamiania klasy:
~/github/test$ javac compatible/I*.java
~/github/test$ java -cp .:compatible C
hello interface 1
Trzeci katalog - wrong
- zawiera wersję I2, która również deklaruje metodę m()
:
~/github/test$ cat wrong/I2.java
public interface I2 {
default void m(){
System.out.println("hello interface 2");
}
}
Nie musisz się nawet martwić o kompilację. Mimo że metoda została zadeklarowana dwukrotnie, klasa może być nadal używana i uruchamiana do czasu wywołania metody m(). Do tego właśnie potrzebujemy argumentu wiersza poleceń:
~/github/test$ javac wrong/*.java
~/github/test$ java -cp .:wrong C
Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: I1.m I2.m
at C.m(C.java)
at C.main(C.java:5)
~/github/test$ java -cp .:wrong C x
~/github/test$
GO TO FULL VERSION