Kiedy stosować: domyślna metoda interfejsu Java 8+, a nie metoda abstrakcyjna


540

Java 8 pozwala na domyślną implementację metod w interfejsach zwanych metodami domyślnymi .

Nie jestem pewien interface default method, kiedy powinienem użyć tego rodzaju zamiast abstract class(z abstract method(s)).

Kiedy zatem należy stosować interfejs z metodami domyślnymi, a kiedy należy stosować klasę abstrakcyjną (z metodami abstrakcyjnymi)? Czy klasy abstrakcyjne są nadal przydatne w tym scenariuszu?


38
Może nadal nie możesz mieć pól, metod prywatnych itp. W interfejsach, podczas gdy możesz w klasie abstrakcyjnej?
sp00m

2
Zastanawiałem się wcześniej nad tym tematem, teraz wszystko jest jasne. Dzięki @Narendra Pathai. Chciałbym dodać link do innego wątku, który zadałeś w związku z tym samym tematem, ponieważ były to moje wątpliwości. stackoverflow.com/questions/19998309/…
Ashutosh Ranjan

Możesz znaleźć fajny post na ten temat tutaj: blog.codefx.org/java/everything-about-default-methods
Fabri

Nadal możesz czasami kodować klasę podstawową jako interfejs, nawet jeśli klasa podstawowa ma stan. Wystarczy, że interfejs musi zdefiniować obiekty ustawiające i pobierające stan, a konkretne klasy muszą je zaimplementować i zdefiniować pole. Jednym z ograniczeń jest to, że w klasie abstrakcyjnej właściwość bean może być prywatna lub chroniona. W interfejsach mają tylko metody publiczne. Jednym z powodów, dla których użyłbyś abstrakcyjnej klasy bazowej jest to, że twoje klasy mają właściwość, która musi być prywatna lub chroniona.
DaBlick,

@DaBlick Nie można rozwiązać problemu stanu w interfejsie za pomocą HashMap. Np .: jeśli chcesz mieć klasę Foo, która zawiera int a, b, ciąg c. a chcesz, aby miały stan, utwórz HashMap </ * nazwę obiektu Foo * / String, / * mapę pól * / Hashmap </ * nazwę specyficzną Field * / String, / * wartość pola * / Object >> map . Kiedy chcesz „utworzyć instancję” teoretycznej klasy Foo, masz metodę, instantiate (String nameOfFoo), która robi map.put (nameOfFoo, pola), gdzie pola to HashMap <String, Object> fields.put („a”, nowy int („5”)); fields.put („b”, new int („6”)); fields.put („c”, „bla”));
George Xavier

Odpowiedzi:


307

W klasach abstrakcyjnych jest znacznie więcej niż w domyślnych implementacjach metod (takich jak stan prywatny), ale od wersji Java 8, kiedy tylko masz do wyboru jedną z nich, powinieneś użyć metody defender (aka. default) W interfejsie.

Ograniczeniem metody domyślnej jest to, że można ją wdrożyć tylko w zakresie wywołań innych metod interfejsu, bez odniesienia do stanu konkretnej implementacji. Tak więc głównym przypadkiem użycia są metody wyższego poziomu i wygody.

Zaletą tej nowej funkcji jest to, że zanim wcześniej zmuszono Cię do użycia abstrakcyjnej klasy dla metod wygody, ograniczając w ten sposób implementator do pojedynczego dziedziczenia, teraz możesz mieć naprawdę czysty projekt z samym interfejsem i minimalną implementacją wysiłek wymuszony na programatorze.

Oryginalną motywacją do wprowadzenia defaultmetod do Java 8 była chęć rozszerzenia interfejsów Framework Framework o metody zorientowane na lambda bez przerywania jakichkolwiek istniejących implementacji. Chociaż jest to bardziej odpowiednie dla autorów bibliotek publicznych, ta sama funkcja może być przydatna również w twoim projekcie. Masz jedno scentralizowane miejsce, w którym możesz dodać nową wygodę, i nie musisz polegać na tym, jak wygląda reszta hierarchii typów.


34
Zgodnie z tym rozumowaniem następną rzeczą, którą dodaliby, były deklaracje metod domyślnych. Nadal nie jestem tego pewien, funkcja ta wydaje mi się raczej włamaniem, które jest narażone na niewłaściwe użycie.
pantera

3
Jedyne użycie klas abstrakcyjnych w erze Java 8, które widzę, to definiowanie pól nie-końcowych. W interfejsach pola są domyślnie końcowe, więc nie można ich zmienić po przypisaniu.
Anuroop,

7
@Anuroop Nie tylko domyślnie --- to jedyna opcja. Interfejsy nie mogą deklarować stanu instancji, dlatego klasy abstrakcyjne mają tu pozostać.
Marko Topolnik,

2
@PhilipRego Metody abstrakcyjne niczego nie wywołują, ponieważ nie mają implementacji. Zaimplementowane metody w klasie mogą uzyskać dostęp do stanu klasy (zmienne instancji). Interfejsy nie mogą ich deklarować, więc domyślne metody nie mogą uzyskać do nich dostępu. Muszą polegać na klasie zapewniającej zaimplementowaną metodę dostępu do stanu.
Marko Topolnik

2
Marko Topolnik, twoja odpowiedź jest martwa. Ale chciałbym polecić aktualizację twojej odpowiedzi. Możesz dodać, że piękno metod domyślnych polega na tym, że jeśli interfejs doda nowe metody domyślne, twoja poprzednia implementacja tego interfejsu nie ulegnie awarii. Nie było to prawdą przed Javą 8.
hfontanez

125

Istnieje kilka różnic technicznych. Klasy abstrakcyjne mogą nadal robić więcej w porównaniu z interfejsami Java 8:

  1. Klasa abstrakcyjna może mieć konstruktor.
  2. Klasy abstrakcyjne są bardziej ustrukturyzowane i mogą utrzymywać stan.

Pod względem koncepcyjnym głównym celem metod obrońcy jest zgodność wsteczna po wprowadzeniu nowych funkcji (jako funkcji lambda) w Javie 8.


20
Ta odpowiedź jest właściwie poprawna i ma sens szczególnie, że „Koncepcyjnie głównym celem metod obrońcy jest kompatybilność wsteczna”
Próbowanie

1
@UnKnown ta strona daje więcej informacji: docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
bernie

@UnKnown, w zasadzie pozwala dodawać metody do interfejsu, a klasy, które implementują ten interfejs, automatycznie uzyskują tę funkcjonalność.
LegendLength

3
Bardziej subtelny punkt dotyczący punktu nr. 2 powyżej o „Can Hold State is this”. Klasy abstrakcyjne mogą zawierać stan, który można później zmienić. Interfejsy mogą również utrzymywać stan, ale po przypisaniu stanu po utworzeniu instancji nie można go zmienić.
Anuroop,

3
@Anuroop Nie opisałbym public static finalpól interfejsu jako „stan”. Ta staticczęść oznacza, że ​​wcale nie są związane z konkretną instancją. Są one przypisywane przy tworzeniu instancji klasy , co nie jest tym samym, co po utworzeniu instancji .
geronimo

65

Jest to opisane w tym artykule . Pomyśl o forEachkolekcjach.

List<?> list = 
list.forEach(…);

Interfejs forEach nie został jeszcze zadeklarowany java.util.Listani przez java.util.Collectioninterfejs. Jednym oczywistym rozwiązaniem byłoby po prostu dodanie nowej metody do istniejącego interfejsu i zapewnienie implementacji tam, gdzie jest to wymagane w JDK. Jednak po opublikowaniu niemożliwe jest dodanie metod do interfejsu bez zerwania istniejącej implementacji.

Zaletą domyślnych metod jest to, że teraz można dodać nową domyślną metodę do interfejsu i nie psuje ona implementacji.


1
„dodawanie metod do interfejsu jest niemożliwe bez zerwania z istniejącą implementacją” - prawda?
Andrey Chaschev

26
@AndreyChaschev Jeśli dodasz nową metodę do interfejsu, wszyscy implementatorzy muszą ją wdrożyć. Dlatego psuje istniejące implementacje.
Marko Topolnik

4
@MarkoTopolnik dzięki, przegapiłem to. Wystarczy wspomnieć, że można częściowo tego uniknąć - prezentując tę ​​metodę w domyślnej implementacji abstrakcyjnej. W tym przykładzie byłoby to AbstractList::forEachrzucanie UnsupportedOperationException.
Andrey Chaschev

3
@AndreyChaschev Tak, to był stary sposób (khm ... jest obecny sposób :), z tym brakiem, że ogranicza on implementator do pojedynczego dziedziczenia z dostarczonej abstrakcyjnej implementacji.
Marko Topolnik

Nie złamie się, jeśli tak się stanie, że wcześniej wszystkie implementacje objęły tę metodę. Co jest mało prawdopodobne, ale możliwe.
George Xavier,

19

Te dwa są całkiem różne:

Domyślne metody to dodawanie funkcjonalności zewnętrznej do istniejących klas bez zmiany ich stanu.

A klasy abstrakcyjne są normalnym rodzajem dziedziczenia, są normalnymi klasami, które mają być rozszerzone.


18

Jak opisano w tym artykule,

Klasy abstrakcyjne a interfejsy w Javie 8

Po wprowadzeniu metody domyślnej wydaje się, że interfejsy i klasy abstrakcyjne są takie same. Jednak nadal są one odmienną koncepcją w Javie 8.

Klasa abstrakcyjna może definiować konstruktor. Są bardziej ustrukturyzowane i mogą mieć z nimi skojarzony stan. Natomiast w przeciwieństwie do metody domyślnej można zaimplementować tylko w zakresie wywoływania innych metod interfejsu, bez odniesienia do stanu określonej implementacji. W związku z tym oba służą do różnych celów, a wybór między nimi naprawdę zależy od kontekstu scenariusza.


Wierzę, że klasa abstrakcyjna ma konstruktora, który można zdefiniować inaczej niż w interfejsie. W Javie 8 również one się ze sobą różnią.
Hemanth Peela,

1
dlaczego klasa abstrakcyjna ma konstruktor, jeśli nie można jej utworzyć instancji?
George Xavier

Możemy wywołać super () z klasy potomnej, która wywoła konstruktora klasy abstrakcyjnej, co wpływa na stan klasy abstrakcyjnej.
Sujay Mohan

14

Jeśli chodzi o zapytanie

Kiedy zatem powinienem używać interfejsu z metodami domyślnymi, a kiedy klasy abstrakcyjnej? Czy klasy abstrakcyjne są nadal przydatne w tym scenariuszu?

Dokumentacja Java zapewnia doskonałą odpowiedź.

Klasy abstrakcyjne w porównaniu do interfejsów:

Klasy abstrakcyjne są podobne do interfejsów. Nie można ich utworzyć, a mogą one zawierać kombinację metod zadeklarowanych z implementacją lub bez.

Jednak za pomocą klas abstrakcyjnych można deklarować pola, które nie są statyczne i końcowe, oraz definiować publiczne, chronione i prywatne metody betonowe.

W przypadku interfejsów wszystkie pola są automatycznie publiczne, statyczne i końcowe, a wszystkie metody, które deklarujesz lub definiujesz (jako metody domyślne) są publiczne. Ponadto można rozszerzyć tylko jedną klasę, niezależnie od tego, czy jest ona abstrakcyjna, czy też można zaimplementować dowolną liczbę interfejsów.

Przypadki użycia dla każdego z nich zostały wyjaśnione w poniższym poście SE:

Jaka jest różnica między interfejsem a klasą abstrakcyjną?

Czy klasy abstrakcyjne są nadal przydatne w tym scenariuszu?

Tak. Nadal są przydatne. Mogą zawierać niestatyczne, niekończące się metody i atrybuty ( chronione, prywatne oprócz publicznego ), co nie jest możliwe nawet w przypadku interfejsów Java-8.


Teraz interfejsy mają również prywatne metody howtodoinjava.com/java9/java9-private-interface-methods
valijon

13

Ilekroć mamy wybór między klasą abstrakcyjną a interfejsem, zawsze (prawie) powinniśmy preferować metody domyślne (znane również jako obrońca lub rozszerzenia wirtualne).

  1. Metody domyślne położyły kres klasycznemu wzorowi interfejsu i klasie towarzyszącej, która implementuje większość lub wszystkie metody w tym interfejsie. Przykładem jest Collection and AbstractCollection. Teraz powinniśmy wdrożyć metody w samym interfejsie, aby zapewnić domyślną funkcjonalność. Klasy, które implementują interfejs, mogą zastąpić metody lub odziedziczyć domyślną implementację.
  2. Innym ważnym zastosowaniem metod domyślnych jest interface evolution. Załóżmy, że miałem klasową piłkę jako:

    public class Ball implements Collection { ... }

Teraz w Javie 8 wprowadzono nową funkcję. Możemy uzyskać strumień, używając streammetody dodanej do interfejsu. Gdyby streamnie była to metoda domyślna, wszystkie implementacje Collectioninterfejsu zostałyby zepsute, ponieważ nie wdrażałyby tej nowej metody. Dodanie niestandardowej metody do interfejsu nie jest source-compatible.

Załóżmy jednak, że nie dokonujemy ponownej kompilacji klasy i nie używamy starego pliku jar zawierającego tę klasę Ball. Klasa ładuje się dobrze bez tej brakującej metody, można tworzyć instancje i wygląda na to, że wszystko działa dobrze. ALE jeśli program wywoła streammetodę na przykład Ballotrzymamy AbstractMethodError. Ustawienie domyślnej metody rozwiązało oba problemy.


9

Domyślne metody interfejsu Java umożliwiają ewolucję interfejsu .

Biorąc pod uwagę istniejący interfejs, jeśli chcesz dodać do niego metodę bez naruszania binarnej zgodności ze starszymi wersjami interfejsu, masz dwie opcje: dodaj domyślną lub statyczną metodę. Rzeczywiście, każda abstrakcyjna metoda dodana do interfejsu musiałaby być zaimplementowana przez klasy lub interfejsy implementujące ten interfejs.

Metoda statyczna jest unikalna dla klasy. Domyślna metoda jest unikalna dla instancji klasy.

Jeśli dodasz domyślną metodę do istniejącego interfejsu, klasy i interfejsy, które implementują ten interfejs, nie muszą go implementować. Mogą

  • zaimplementuj domyślną metodę, która zastąpi implementację w zaimplementowanym interfejsie.
  • ponownie zadeklaruj metodę (bez implementacji), co czyni ją abstrakcyjną.
  • nic nie rób (wtedy domyślna metoda z zaimplementowanego interfejsu jest po prostu dziedziczona).

Więcej na ten temat tutaj .


7

Chociaż jest to stare pytanie, pozwólcie, że podzielę się również swoimi uwagami.

  1. Klasa abstrakcyjna: Wewnątrz klasy abstrakcyjnej możemy zadeklarować zmienne instancji, które są wymagane dla klasy potomnej

    Interfejs: Wewnątrz interfejsu każda zmienna jest zawsze publiczna statyczna i ostatecznie nie możemy zadeklarować zmiennych instancji

  2. Klasa abstrakcyjna: Klasa abstrakcyjna może mówić o stanie obiektu

    Interfejs: Interfejs nigdy nie może mówić o stanie obiektu

  3. Klasa abstrakcyjna: Wewnątrz Klasa abstrakcyjna możemy deklarować konstruktory

    Interfejs: Wewnątrz interfejsu nie możemy deklarować konstruktorów, ponieważ celem
    konstruktorów jest inicjowanie zmiennych instancji. Więc jaka jest potrzeba konstruktora, jeśli nie możemy mieć zmiennych instancji w interfejsach .

  4. Klasa abstrakcyjna: Wewnątrz klasy abstrakcyjnej możemy zadeklarować bloki instancji i statyczne

    Interfejs: Interfejsy nie mogą mieć bloków instancji i bloków statycznych.

  5. Klasa abstrakcyjna: Klasa abstrakcyjna nie może odwoływać się do wyrażenia lambda

    Interfejsy: Interfejsy z jedną metodą abstrakcyjną mogą odwoływać się do wyrażenia lambda

  6. Klasa abstrakcyjna : Wewnątrz klasy abstrakcyjnej możemy przesłonić metody OBJECT CLASS

    Interfejsy: Nie możemy przesłonić metod OBJECT CLASS w interfejsach.

Zakończę notatką, że:

Domyślne koncepcje metod / statyczne koncepcje metod w interfejsie zostały wprowadzone tylko w celu zapisania klas implementacji, ale nie w celu zapewnienia sensownej użytecznej implementacji. Metody domyślne / metody statyczne są rodzajem fikcyjnej implementacji, „jeśli chcesz, możesz ich użyć lub możesz je zastąpić (w przypadku metod domyślnych) w klasie implementacji”. Dzięki temu oszczędzamy nam wdrażania nowych metod w klasach implementacji za każdym razem, gdy nowe metody w interfejsach są dodane. Dlatego interfejsy nigdy nie mogą być równe klasom abstrakcyjnym.


5

Reguła Remi Forax jest taka, że nie projektujesz klasami abstrakcyjnymi. Projektujesz swoją aplikację za pomocą interfejsów . Niezależnie od wersji Java, niezależnie od języka. Jest on wspierany przez I zasady segregacji nterface w SOL I D zasad.

Później możesz użyć klas Abstract do podziału kodu na czynniki. Teraz w Javie 8 możesz to zrobić bezpośrednio w interfejsie. To jest obiekt, nie więcej.


2

kiedy należy stosować interfejs z metodami domyślnymi, a kiedy klasę abstrakcyjną?

Kompatybilność wsteczna: wyobraź sobie, że twój interfejs jest implementowany przez setki klas, modyfikacja tego interfejsu zmusi wszystkich użytkowników do implementacji nowo dodanej metody, nawet jeśli może nie być niezbędna dla wielu innych klas, które implementują twój interfejs, plus pozwala na twój interfejs być funkcjonalnym interfejsem

Fakty i ograniczenia:

1-może być zadeklarowany tylko w interfejsie, a nie w klasie lub klasie abstrakcyjnej.

2-musi zapewnić ciało

3-Nie zakłada się, że jest abstrakcyjny, jak inne normalne metody stosowane w interfejsie.


1

W Javie 8 interfejs wygląda jak klasa abstrakcyjna, chociaż mogą występować pewne różnice, takie jak:

1) Klasy abstrakcyjne to klasy, więc nie są ograniczone do innych ograniczeń interfejsu w Javie, np. Klasa abstrakcyjna może mieć stan , ale nie można mieć stanu na interfejsie w Javie.

2) Kolejną różnicą semantyczną między interfejsem z metodami domyślnymi a klasą abstrakcyjną jest to, że można definiować konstruktory wewnątrz klasy abstrakcyjnej , ale nie można definiować konstruktora w interfejsie w Javie


Zgadzam się z numerem 2, ale jeśli chodzi o numer 1, czy nie możesz po prostu zaimplementować interfejsu, a tym samym mieć stan za pośrednictwem klasy implementacji?
George Xavier

0

Domyślne metody w interfejsie Java powinny być używane raczej do zapewnienia fałszywej implementacji funkcji, oszczędzając w ten sposób każdą klasę implementacyjną tego interfejsu przed bólem wynikającym z deklarowania wszystkich metod abstrakcyjnych, nawet jeśli chcą one mieć do czynienia tylko z jedną. Metody domyślne w interfejsie są więc w pewnym sensie zastępstwem koncepcji klas adapterów.

Metody w klasie abstrakcyjnej powinny jednak dawać sensowną implementację, którą każda klasa potomna powinna zastąpić tylko w razie potrzeby zastąpienia wspólnej funkcjonalności.


0

Jak wspomniano w innych odpowiedziach, dodano możliwość dodania implementacji do interfejsu w celu zapewnienia kompatybilności wstecznej w ramach kolekcji. Twierdziłbym, że zapewnienie kompatybilności wstecznej jest potencjalnie jedynym dobrym powodem dodania implementacji do interfejsu.

W przeciwnym razie, jeśli dodasz implementację do interfejsu, łamiesz podstawowe prawo, dlaczego interfejsy zostały dodane w pierwszej kolejności. Java jest jednym językiem dziedziczenia, w przeciwieństwie do C ++, który pozwala na wielokrotne dziedziczenie. Interfejsy zapewniają korzyści związane z pisaniem w języku obsługującym wielokrotne dziedziczenie bez wprowadzania problemów związanych z wielokrotnym dziedziczeniem.

Mówiąc dokładniej, Java pozwala tylko na pojedyncze dziedziczenie implementacji, ale pozwala na wielokrotne dziedziczenie interfejsów. Na przykład następujący kod Java jest prawidłowy:

class MyObject extends String implements Runnable, Comparable { ... }

MyObject dziedziczy tylko jedną implementację, ale dziedziczy trzy umowy.

Java przekazała wielokrotne dziedziczenie implementacji, ponieważ wielokrotne dziedziczenie implementacji wiąże się z wieloma drażliwymi problemami, które są poza zakresem tej odpowiedzi. Dodano interfejsy, aby umożliwić wielokrotne dziedziczenie umów (inaczej interfejsy) bez problemów wielokrotnego dziedziczenia implementacji.

Aby wesprzeć moją tezę, oto cytat Kena Arnolda i Jamesa Goslinga z książki The Java Programming Language, wydanie 4 :

Pojedyncze dziedziczenie wyklucza niektóre użyteczne i poprawne projekty. Problemy wielokrotnego dziedziczenia wynikają z wielokrotnego dziedziczenia implementacji, ale w wielu przypadkach wielokrotne dziedziczenie służy do dziedziczenia szeregu abstrakcyjnych umów i być może jednej konkretnej implementacji. Zapewnienie sposobu na dziedziczenie abstrakcyjnej umowy bez dziedziczenia implementacji pozwala na korzyści wynikające z wielokrotnego dziedziczenia bez problemów z wielokrotnym dziedziczeniem implementacji. Dziedziczenie umowy abstrakcyjnej jest nazywane dziedziczeniem interfejsu . Język programowania Java obsługuje dziedziczenie interfejsu, umożliwiając zadeklarowanie interfacetypu


-1

Najpierw pomyśl o zasadzie otwarcia / zamknięcia. Domyślne metody w interfejsach NARUSZAJĄ to. Jest to zła funkcja w Javie. Zachęca do złego projektowania, złej architektury, niskiej jakości oprogramowania. Sugerowałbym, aby całkowicie unikać używania domyślnych metod.

Zadaj sobie kilka pytań: Dlaczego nie możesz umieścić swoich metod w klasie abstrakcyjnej? Czy potrzebujesz więcej niż jednej klasy abstrakcyjnej? Zastanów się, za co odpowiada twoja klasa. Czy jesteś pewien, że wszystkie metody, które zamierzasz zastosować w jednej klasie, naprawdę spełniają ten sam cel? Być może rozróżnisz kilka celów, a następnie podzielisz swoją klasę na kilka klas, dla każdego celu własną klasę.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.