JavaRush /Blog Java /Random-PL /Metody domyślne w Javie 8: co mogą, a czego nie mogą zrob...
Spitfire
Poziom 33

Metody domyślne w Javie 8: co mogą, a czego nie mogą zrobić?

Opublikowano w grupie Random-PL
Tłumaczenie artykułu Petera Verhasa z kwietnia 2014 r. Metody domyślne w Javie 8: co mogą, a czego nie mogą zrobić?  - 1Od 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.
Metody domyślne w Javie 8: co mogą, a czego nie mogą zrobić?  - 2W tym przypadku klasa działa. Nie można go skompilować ze zaktualizowanymi interfejsami, ale został on skompilowany ze starszymi wersjami i dlatego działa. Teraz
  • zmień interfejs za pomocą abstrakcyjnej metody m() i dodaj domyślną implementację.
  • Skompiluj zmodyfikowany interfejs.
  • Uruchom klasę: błąd.
Jeśli istnieją dwa interfejsy zapewniające domyślną implementację metody, tej metody nie można wywołać w klasie, chyba że zostanie ona zaimplementowana przez samą klasę (ponownie albo samodzielnie, albo odziedziczona z innej klasy). Metody domyślne w Javie 8: co mogą, a czego nie mogą zrobić?  - 3Zgodny z klasą. Można go załadować ze zmodyfikowanym interfejsem. Może nawet działać do czasu wywołania metody, która ma domyślną implementację w obu interfejsach.

Przykładowy kod

Metody domyślne w Javie 8: co mogą, a czego nie mogą zrobić?  - 4Aby 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ę, maindzię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$

Wniosek

Kiedy przeniesiesz swoją bibliotekę do Java 8 i zmienisz interfejsy tak, aby zawierały metody domyślne, prawdopodobnie nie będziesz mieć żadnych problemów. Przynajmniej na to mają nadzieję programiści bibliotek Java 8, dodając funkcjonalność. Aplikacje korzystające z Twojej biblioteki nadal korzystają z niej w Javie 7, gdzie nie ma metod domyślnych. Jeśli jednocześnie używanych jest kilka bibliotek, istnieje możliwość wystąpienia konfliktu. Jak tego uniknąć? Zaprojektuj interfejs API swojej biblioteki w taki sam sposób jak poprzednio. Nie popadaj w samozadowolenie, polegając na możliwościach metod domyślnych. Są ostatecznością. Wybieraj nazwy ostrożnie, aby uniknąć kolizji z innymi interfejsami. Zobaczmy, jak rozwinie się rozwój języka Java przy użyciu tej funkcji.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION