Jak powinienem wyjaśnić różnicę między interfejsem a klasą abstrakcyjną?


469

W jednym z moich wywiadów poproszono mnie o wyjaśnienie różnicy między klasą interfejsu a klasą abstrakcyjną .

Oto moja odpowiedź:

Metody interfejsu Java są domyślnie abstrakcyjne i nie mogą mieć implementacji. Klasa abstrakcyjna Java może mieć metody instancji, które implementują domyślne zachowanie.

Zmienne zadeklarowane w interfejsie Java są domyślnie ostateczne. Klasa abstrakcyjna może zawierać nie-końcowe zmienne.

Członkowie interfejsu Java są domyślnie publiczni. Klasa abstrakcyjna Java może mieć zwykłe smaki członków klasy, takie jak prywatny, chroniony itp.

Interfejs Java powinien zostać zaimplementowany przy użyciu słowa kluczowego „implements”; Klasa abstrakcyjna Java powinna zostać rozszerzona za pomocą słowa kluczowego „extends”.

Interfejs może rozszerzyć tylko inny interfejs Java, klasa abstrakcyjna może rozszerzyć inną klasę Java i zaimplementować wiele interfejsów Java.

Klasa Java może implementować wiele interfejsów, ale może rozszerzyć tylko jedną klasę abstrakcyjną.

Ankieter nie był jednak usatysfakcjonowany i powiedział mi, że ten opis reprezentuje „ wiedzę książkową ”.

Poprosił mnie o bardziej praktyczną odpowiedź, wyjaśniając, kiedy wybiorę klasę abstrakcyjną zamiast interfejsu, używając praktycznych przykładów .

Gdzie popełniłem błąd?


32
Może twoja odpowiedź wyglądała, jakbyś mówił coś, czego nie rozumiesz? Możliwe, że po prostu musisz zmienić styl mówienia na taki, który bardziej przypomina twoje własne słowa.
Kirill Kobelev

19
Odpowiedziałeś wykazem (całkiem poprawnych) różnic technicznych. Ankieter najprawdopodobniej szukał bardziej konceptualnej odpowiedzi (np. Na jakiej podstawie można by wybierać między użyciem interfejsu a klasą abstrakcyjną).
Ted Hopp,

15
Zapomniałeś powiedzieć, że klasy abstrakcyjne mają konstruktory, nawet jeśli nie możesz utworzyć instancji klasy abstrakcyjnej, const. jest używany przez klasy potomne. Interfejsy wskazują „co”, ale nie „jak”, ponieważ definiują kontrakt (listę metod) podczas abst. klasa może również wskazywać „jak” (zaimplementować metodę). Korzystanie int. możesz emulować wielokrotne dziedziczenie (klasa może implementować wiele int., ale rozszerzyć tylko jedną klasę). Korzystanie int. możesz mieć typ podstawowy dla dif. rodziny: Ulotka f = nowy samolot (); Ulotka f2 = nowy ptak (); Bird i Plane nie odpowiadają tej samej rodzinie, ale oba mogą latać (są ulotkami).
Francisco Goldenstein,

7
Począwszy od interfejsów java8, mogą zawierać metody .. więc poza koncepcją OO te tak zwane „różnice” mogą ulec zmianie każdego dnia.
okaziciela pierścienia

15
Nie mam żadnego problemu z twoją odpowiedzią i nie sądzę, żeby osoba przeprowadzająca wywiad miała jakiś szyderczy wyraz „wiedzy książkowej”. Ankieterzy nie zawsze znają prawidłowe odpowiedzi na zadawane pytania, a niektóre wywiady służą jedynie ostrzeżeniu, abyś tam nie pracował.
Markiz Lorne

Odpowiedzi:


513

Najpierw dam wam przykład:

public interface LoginAuth{
   public String encryptPassword(String pass);
   public void checkDBforUser();
}

Załóżmy, że masz 3 bazy danych w swojej aplikacji. Następnie każda implementacja dla tej bazy danych musi zdefiniować powyższe 2 metody:

public class DBMySQL implements LoginAuth{
          // Needs to implement both methods
}
public class DBOracle implements LoginAuth{
          // Needs to implement both methods
}
public class DBAbc implements LoginAuth{
          // Needs to implement both methods
}

Ale co jeśli encryptPassword()nie jest zależny od bazy danych i jest taki sam dla każdej klasy? Zatem powyższe podejście nie byłoby dobrym podejściem.

Zamiast tego rozważ to podejście:

public abstract class LoginAuth{
   public String encryptPassword(String pass){
            // Implement the same default behavior here 
            // that is shared by all subclasses.
   }

   // Each subclass needs to provide their own implementation of this only:
   public abstract void checkDBforUser();
}

Teraz w każdej klasie potomnej musimy zaimplementować tylko jedną metodę - metodę zależną od bazy danych.


97
Nie jestem pewien, czy to naprawdę wyjaśnia różnicę ... na pewno jest to fajna technika. Przypuszczam, że warto również zauważyć, że Java 8 wreszcie przyznało, że C ++ miał rację i że można wykonać wielokrotne dziedziczenie i można je wykorzystać, dzięki czemu interfejsy mogą teraz definiować nie tylko sygnatury funkcji, ale także domyślną implementację. W związku z tym preferowane byłoby użycie interfejsu.
thecoshman

1
@thecoshman Jaką różnicę by to zrobiło, gdybym podszedł do problemu jak w odpowiedzi (klasa abstrakcyjna z jedną zaimplementowaną metodą i drugą metodą abstrakcyjną) lub zdefiniowała interfejs z domyślną implementacją metody? Zasadniczo próbuję powiedzieć, że napisałeś, że „lepsze byłoby użycie interfejsu”, a moje pytanie brzmi - dlaczego?
Neutrino

1
Sądzę więc, że należy powiedzieć, że w przypadku interfejsów implementacja tego, co zostało zdefiniowane, zależy od klasy, która faktycznie implementuje interfejs, podczas gdy elementy w klasie abstrakcyjnej są „rdzeniem” dla klas, które ją rozszerzają; tzn. to się nie zmienia.
orrymr

4
@Neutrino Mimo że Java pozwala na implementację wielu interfejsów, z których każdy oferuje domyślne implementacje funkcji, nadal można rozszerzyć tylko jedną klasę. Jako takie, korzystanie z interfejsu może zapewnić większą elastyczność dla tych, którzy chcą go używać, wraz z innymi interfejsami.
thecoshman

3
@HiradNikoo Przepraszam za późny komentarz, ale natknąłem się na ten wątek. Można również traktować dziedziczenie klas jako relację IS-A, podczas gdy interfejsy oznaczają „ma pewną funkcjonalność”.
Alexander Jank,

206

Na tym świecie nic nie jest idealne. Być może oczekiwali bardziej praktycznego podejścia.

Ale po wyjaśnieniu możesz dodać te wiersze z nieco innym podejściem.

  1. Interfejsy to reguły (reguły, ponieważ należy je wdrożyć, których nie można zignorować lub których należy unikać, aby były narzucone jak reguły), co działa jako wspólny dokument dla różnych zespołów tworzących oprogramowanie.

  2. Interfejsy dają wyobrażenie o tym, co należy zrobić, ale nie w jaki sposób. Tak więc implementacja całkowicie zależy od programisty, zgodnie z podanymi regułami (oznacza dany podpis metod).

  3. Klasy abstrakcyjne mogą zawierać deklaracje abstrakcyjne, konkretne implementacje lub oba.

  4. Deklaracje abstrakcyjne są jak reguły, których należy przestrzegać, a konkretne implementacje są jak wytyczne (możesz użyć go takim, jaki jest, lub możesz go zignorować, zastępując go i nadając mu własną implementację).

  5. Ponadto, które metody z tą samą sygnaturą mogą zmienić zachowanie w różnych kontekstach, są dostarczane jako deklaracje interfejsu jako reguły do ​​odpowiedniej implementacji w różnych kontekstach.

Edycja: Java 8 ułatwia zdefiniowanie domyślnych i statycznych metod w interfejsie.

public interface SomeInterfaceOne {

    void usualAbstractMethod(String inputString);

    default void defaultMethod(String inputString){
        System.out.println("Inside SomeInterfaceOne defaultMethod::"+inputString);
    }
}

Teraz, gdy klasa zaimplementuje SomeInterface, nie jest obowiązkowe zapewnienie implementacji domyślnych metod interfejsu.

Jeśli mamy inny interfejs z następującymi metodami:

public interface SomeInterfaceTwo {

    void usualAbstractMethod(String inputString);

    default void defaultMethod(String inputString){
        System.out.println("Inside SomeInterfaceTwo defaultMethod::"+inputString);
    }

}

Java nie pozwala na rozszerzanie wielu klas, ponieważ powoduje to „problem diamentowy”, w którym kompilator nie jest w stanie zdecydować, której metody nadklasy użyć. W przypadku metod domyślnych problem z diamentem pojawi się również w przypadku interfejsów. Ponieważ jeśli klasa implementuje oba

SomeInterfaceOne and SomeInterfaceTwo

i nie implementuje wspólnej domyślnej metody, kompilator nie może zdecydować, który wybrać. Aby uniknąć tego problemu, w java 8 obowiązkowe jest wdrożenie typowych domyślnych metod różnych interfejsów. Jeśli jakakolwiek klasa implementuje oba powyższe interfejsy, musi zapewnić implementację metody defaultMethod (), w przeciwnym razie kompilator zgłosi błąd czasu kompilacji.


11
+1, to naprawdę dobra odpowiedź, aby uniknąć zamieszania. Ale nie widziałem żadnego linku i nie mam pojęcia, dlaczego zacytowałeś te cenne linie. Ustaw je jako punkty, jeśli to możliwe :).
Suresh Atta

Przeczytaj mój komentarz powyżej na temat emulacji wielokrotnego dziedziczenia za pomocą interfejsów i używania interfejsów do posiadania typu podstawowego dla klas różnych rodzin. Myślę, że ankieter chce usłyszeć tego rodzaju odpowiedzi od ankietowanego.
Francisco Goldenstein,

Twój komentarz wskazuje również na dobry przykład użycia interfejsu. Napisałem, co czuję podczas codziennej pracy. Te słowa mogą nie być profesjonalne ani dokładne. Ale to właśnie poznałem po ścisłej współpracy z abstrakcyjnymi klasami i interfejsami w codziennym programowaniu.
Shailesh Saxena

4. Konkretne implementacje są również zasadami, które mają implementację domyślną.
Luten

@Luten: Zgodnie z moją wiedzą, jeśli możesz uniknąć / zignorować regułę bez żadnego problemu, musi to być wytyczna, a nie reguła. Proszę, popraw mnie jeśli się mylę.
Shailesh Saxena

168

Zrobiłeś dobre podsumowanie praktycznych różnic w użyciu i implementacji, ale nie powiedziałeś nic o różnicy w znaczeniu.

interfejs jest opis zachowania klasa wykonawczy będzie musiał. Klasa implementacyjna zapewnia, że ​​będzie miała te metody, których można na niej użyć. Zasadniczo jest to umowa lub obietnica złożona przez klasę.

Klasa abstrakcyjna jest podstawą dla różnych podklas że zachowanie zakładowego, który nie muszą być wielokrotnie tworzone. Podklasy muszą uzupełnić zachowanie i mieć możliwość zastąpienia predefiniowanego zachowania (o ile nie jest zdefiniowane jako finallub private).

Znajdziesz dobre przykłady w java.utilpakiecie, który zawiera interfejsy podobne Listi klasy abstrakcyjne, AbstractListktóre już implementują interfejs. Oficjalna dokumentacja opisuje AbstractListnastępująco:

Ta klasa zapewnia szkieletową implementację interfejsu List w celu zminimalizowania wysiłku wymaganego do implementacji tego interfejsu wspieranego przez magazyn danych „losowy dostęp” (taki jak tablica).


16
To powinna być odpowiedź. Nie jest to lista szczegółów, ale podstawowa koncepcja, która czyni różnicę między interfejsem a klasą abstrakcyjną, nie tylko w Javie, ale ogólnie.
edc65

1
To jest naprawdę dobre. Oczywiście inne odpowiedzi też są dobre. Mówi to jednak o dużym abstractsłowie kluczowym dotyczącym słowa kluczowego, to znaczy, gdy kompilator to widzi, wiedzą, że następujące informacje są niekompletne i wymagają implementacji . Interfejsy są zawsze niekompletne, ale klasy abstrakcyjne są abstrakcyjne, ponieważ musiały mieć incomplete (abstract)metody.
Rakib

85

Interfejs składa się ze zmiennych singletonowych (public static final) i publicznych metod abstrakcyjnych. Zwykle wolimy korzystać z interfejsu w czasie rzeczywistym, gdy wiemy, co robić, ale nie wiemy, jak to zrobić .

Pojęcie to można lepiej zrozumieć na przykładzie:

Rozważ klasę płatności. Płatności można dokonać na wiele sposobów, takich jak PayPal, karta kredytowa itp. Dlatego zazwyczaj bierzemy Payment jako nasz interfejs zawierający makePayment()metodę, a CreditCard i PayPal to dwie klasy implementacji.

public interface Payment
{
    void makePayment();//by default it is a abstract method
}
public class PayPal implements Payment
{
    public void makePayment()
    {
        //some logic for PayPal payment
        //e.g. Paypal uses username and password for payment
    }
}
public class CreditCard implements Payment
{
    public void makePayment()
    {
        //some logic for CreditCard payment
        //e.g. CreditCard uses card number, date of expiry etc...
    }
}

W powyższym przykładzie CreditCard i PayPal to dwie klasy / strategie implementacji. Interfejs pozwala nam również na koncepcję wielokrotnego dziedziczenia w Javie, której nie można osiągnąć za pomocą klasy abstrakcyjnej.

Wybieramy klasę abstrakcyjną, gdy istnieją pewne funkcje, dla których wiemy, co robić, i inne funkcje, które wiemy, jak wykonać .

Rozważ następujący przykład:

public abstract class Burger
{
    public void packing()
    {
        //some logic for packing a burger
    }
    public abstract void price(); //price is different for different categories of burgers
}
public class VegBerger extends Burger
{
    public void price()
    {
        //set price for a veg burger.
    }
}
public class NonVegBerger extends Burger
{
    public void price()
    {
        //set price for a non-veg burger.
    }
}

Jeśli w przyszłości dodamy metody (konkretne / abstrakcyjne) do danej klasy abstrakcyjnej, klasa implementacyjna nie będzie wymagać zmiany jej kodu. Jeśli jednak w przyszłości dodamy metody do interfejsu, musimy dodać implementacje do wszystkich klas, które zaimplementowały ten interfejs, w przeciwnym razie wystąpią błędy czasu kompilacji.

Istnieją inne różnice, ale są to główne, które mogły być takie, jakich oczekiwał Twój ankieter. Mam nadzieję, że było to pomocne.


1
Cóż, ta odpowiedź ma sens i jest całkiem jasna na przykładzie, kiedy wybieramy pomiędzy interfacei abstract class.
MAC

„co robić, ale nie wiem jak to zrobić”, ponieważ definiujemy metodę bez dokonywania w niej żadnych implementacji „void makePayment ();”, jednocześnie definiując implementacje metody w klasie, która implementuje interfejs.
Abdel-Raouf

45

Różnica między klasą Abstact a interfejsem

  1. Klasy abstrakcyjne a interfejsy w Javie 8
  2. Różnica koncepcyjna:

Domyślne metody interfejsu w Javie 8

  1. Co to jest metoda domyślna?
  2. Błąd kompilacji metody ForEach rozwiązany przy użyciu metody domyślnej
  3. Metoda domyślna i problemy z wieloznacznością dziedziczenia wielokrotnego
  4. Ważne informacje na temat domyślnych metod interfejsu Java:

Metoda statyczna interfejsu Java

  1. Metoda statyczna interfejsu Java, przykład kodu, metoda statyczna a metoda domyślna
  2. Ważne informacje dotyczące statycznej metody interfejsu Java:

Interfejsy funkcjonalne Java



Klasy abstrakcyjne a interfejsy w Javie 8

Zmiany interfejsu Java 8 obejmują metody statyczne i metody domyślne w interfejsach. W wersjach wcześniejszych niż Java 8 w interfejsach mogliśmy mieć tylko deklaracje metod. Ale w Javie 8 możemy mieć domyślne interfejsy i metody statyczne w interfejsach.

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.

Różnica koncepcyjna:

Klasy abstrakcyjne są poprawne dla szkieletowych (tj. Częściowych) implementacji interfejsów, ale nie powinny istnieć bez pasującego interfejsu.

Więc kiedy klasy abstrakcyjne zostaną skutecznie zredukowane do niewidoczności, szkieletowe implementacje interfejsów, czy metody domyślne również to zabiorą? Zdecydowanie: nie! Implementacja interfejsów prawie zawsze wymaga niektórych lub wszystkich narzędzi do budowania klas, których brakuje metod domyślnych. A jeśli jakiś interfejs tego nie robi, jest to wyraźnie przypadek szczególny, który nie powinien cię sprowadzać na manowce.

Domyślne metody interfejsu w Javie 8

Java 8 wprowadza „ metodę domyślną nową funkcję ” lub (metody Defender), która pozwala programistom dodawać nowe metody do interfejsów bez przerywania istniejącej implementacji tych interfejsów. Zapewnia elastyczność umożliwiającą implementację interfejsu, która będzie domyślnie używana w sytuacji, gdy konkretna klasa nie zapewni implementacji dla tej metody.

Rozważmy mały przykład, aby zrozumieć, jak to działa:

public interface OldInterface {
    public void existingMethod();
 
    default public void newDefaultMethod() {
        System.out.println("New default method"
               + " is added in interface");
    }
}

Następująca klasa pomyślnie skompiluje się w Javie JDK 8,

public class OldInterfaceImpl implements OldInterface {
    public void existingMethod() {
     // existing implementation is here…
    }
}

Jeśli utworzysz instancję OldInterfaceImpl:

OldInterfaceImpl obj = new OldInterfaceImpl ();
// print “New default method add in interface”
obj.newDefaultMethod(); 

Metoda domyślna:

Metody domyślne nigdy nie są ostateczne, nie mogą być synchronizowane i nie mogą zastępować metod Object. Są one zawsze publiczne, co poważnie ogranicza możliwość pisania krótkich metod wielokrotnego użytku.

Do interfejsu można podać domyślne metody bez wpływu na implementację klas, ponieważ obejmuje on implementację. Jeśli każda dodana metoda w interfejsie zdefiniowanym z implementacją nie wpłynie na żadną klasę implementującą. Klasa implementująca może zastąpić domyślną implementację zapewnianą przez interfejs.

Domyślne metody pozwalają dodawać nowe funkcje do istniejących interfejsów bez przerywania starszej implementacji tych interfejsów.

Po rozszerzeniu interfejsu zawierającego metodę domyślną możemy wykonać następujące czynności,

  1. Nie zastępuj metody domyślnej i odziedziczy metodę domyślną.
  2. Zastąp domyślną metodę podobną do innych metod, które zastępujemy w podklasie.
  3. Ponownie określ domyślną metodę jako abstrakcyjną, która zmusza podklasę do zastąpienia jej.

Błąd kompilacji metody ForEach rozwiązany przy użyciu metody domyślnej

W Javie 8 kolekcje JDK zostały rozszerzone, a metoda forEach została dodana do całej kolekcji (która działa w połączeniu z lambdami). W tradycyjny sposób kod wygląda jak poniżej,

public interface Iterable<T> {
    public void forEach(Consumer<? super T> consumer);
}

Ponieważ wynik ten powoduje, że każda klasa implementująca ma błędy kompilacji, dodano domyślną metodę z wymaganą implementacją, aby istniejąca implementacja nie została zmieniona.

Interfejs Iterable z metodą domyślną znajduje się poniżej,

public interface Iterable<T> {
    public default void forEach(Consumer
                   <? super T> consumer) {
        for (T t : this) {
            consumer.accept(t);
        }
    }
}

Ten sam mechanizm został użyty do dodania strumienia w interfejsie JDK bez przerywania klas implementujących.


Metoda domyślna i problemy z wieloznacznością dziedziczenia wielokrotnego

Ponieważ klasa Java może implementować wiele interfejsów, a każdy interfejs może zdefiniować domyślną metodę z tą samą sygnaturą metody, dlatego odziedziczone metody mogą ze sobą kolidować.

Rozważ poniższy przykład,

public interface InterfaceA {  
       default void defaultMethod(){  
           System.out.println("Interface A default method");  
    }  
}
 
public interface InterfaceB {
   default void defaultMethod(){
       System.out.println("Interface B default method");
   }
}
 
public class Impl implements InterfaceA, InterfaceB  {
}

Powyższy kod nie zostanie skompilowany z następującym błędem,

java: class Impl dziedziczy niepowiązane wartości domyślne dla defaultMethod () z typów InterfaceA i InterfaceB

Aby naprawić tę klasę, musimy zapewnić domyślną implementację metody:

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
    }
}

Ponadto, jeśli chcemy wywołać domyślną implementację zapewnianą przez którykolwiek z super interfejsów, a nie naszą własną implementację, możemy to zrobić w następujący sposób,

public class Impl implements InterfaceA, InterfaceB {
    public void defaultMethod(){
        // existing code here..
        InterfaceA.super.defaultMethod();
    }
}

Możemy wybrać dowolną domyślną implementację lub obie w ramach naszej nowej metody.

Ważne informacje na temat domyślnych metod interfejsu Java:

  1. Domyślne metody interfejsu Java pomogą nam w rozszerzeniu interfejsów bez obawy przed zerwaniem klas implementacyjnych.
  2. Domyślne metody interfejsu Java zmniejszyły różnice między interfejsami a klasami abstrakcyjnymi.
  3. Domyślne metody interfejsu Java 8 pomogą nam uniknąć klas narzędzi, takich jak wszystkie metody klasy Kolekcje mogą być dostarczone w samych interfejsach.
  4. Domyślne metody interfejsu Java pomogą nam w usunięciu podstawowych klas implementacji, możemy zapewnić implementację domyślną, a klasy implementacji mogą wybrać, którą z nich zastąpić.
  5. Jednym z głównych powodów wprowadzenia domyślnych metod w interfejsach jest udoskonalenie interfejsu API kolekcji w Javie 8 w celu obsługi wyrażeń lambda.
  6. Jeśli jakakolwiek klasa w hierarchii ma metodę o tym samym podpisie, wówczas metody domyślne stają się nieistotne. Metoda domyślna nie może zastąpić metody z java.lang.Object. Rozumowanie jest bardzo proste, ponieważ obiekt jest klasą bazową dla wszystkich klas java. Więc nawet jeśli mamy metody klasy Object zdefiniowane jako domyślne w interfejsach, będzie to bezużyteczne, ponieważ metoda klasy Object będzie zawsze używana. Dlatego, aby uniknąć nieporozumień, nie możemy mieć domyślnych metod, które zastępują metody klasy Object.
  7. Domyślne metody interfejsu Java są również określane jako Metody Defendera lub Metody rozszerzania wirtualnego.

Link do zasobu:

  1. Interfejs z metodami domyślnymi vs Klasa abstrakcyjna w Javie 8
  2. Klasa abstrakcyjna a interfejs w erze JDK 8
  3. Ewolucja interfejsu za pomocą metod rozszerzania wirtualnego

Metoda statyczna interfejsu Java

Metoda statyczna interfejsu Java, przykład kodu, metoda statyczna a metoda domyślna

Metoda statyczna interfejsu Java jest podobna do metody domyślnej, z tym wyjątkiem, że nie możemy ich przesłonić w klasach implementacji. Ta funkcja pomaga nam uniknąć niepożądanych wyników w przypadku złej implementacji w klasach implementacyjnych. Spójrzmy na to na prostym przykładzie.

public interface MyData {

    default void print(String str) {
        if (!isNull(str))
            System.out.println("MyData Print::" + str);
    }

    static boolean isNull(String str) {
        System.out.println("Interface Null Check");

        return str == null ? true : "".equals(str) ? true : false;
    }
}

Zobaczmy teraz klasę implementacji, która ma metodę isNull () ze słabą implementacją.

public class MyDataImpl implements MyData {

    public boolean isNull(String str) {
        System.out.println("Impl Null Check");

        return str == null ? true : false;
    }

    public static void main(String args[]){
        MyDataImpl obj = new MyDataImpl();
        obj.print("");
        obj.isNull("abc");
    }
}

Zauważ, że isNull (String str) to prosta metoda klasy, nie zastępująca metody interfejsu. Na przykład, jeśli dodamy adnotację @Override do metody isNull (), spowoduje to błąd kompilatora.

Teraz, kiedy uruchomimy aplikację, otrzymamy następujące dane wyjściowe.

Kontrola zerowa interfejsu

Impl Null Check

Jeśli zmienimy metodę interfejsu ze statycznej na domyślną, otrzymamy następujące dane wyjściowe.

Impl Null Check

MyData Print ::

Impl Null Check

Metoda statyczna interfejsu Java jest widoczna tylko dla metod interfejsu, jeśli usuniemy metodę isNull () z klasy MyDataImpl, nie będziemy mogli jej użyć do obiektu MyDataImpl. Jednak podobnie jak inne metody statyczne, możemy używać metod statycznych interfejsu przy użyciu nazwy klasy. Na przykład poprawna instrukcja to:

boolean result = MyData.isNull("abc");

Ważne informacje dotyczące statycznej metody interfejsu Java:

  1. Metoda statyczna interfejsu Java jest częścią interfejsu, nie możemy jej używać do obiektów klasy implementacji.
  2. Metody statyczne interfejsu Java są dobre do dostarczania metod narzędziowych, na przykład sprawdzania wartości zerowej, sortowania kolekcji itp.
  3. Metoda statyczna interfejsu Java pomaga nam zapewnić bezpieczeństwo, nie pozwalając klasom implementacji na ich zastąpienie.
  4. Nie możemy zdefiniować metody statycznej interfejsu dla metod klasy Object, otrzymamy błąd kompilatora jako „Ta metoda statyczna nie może ukryć metody instancji przed Object”. Jest tak, ponieważ nie jest to dozwolone w Javie, ponieważ Object jest klasą bazową dla wszystkich klas i nie możemy mieć jednej metody statycznej na poziomie klasy i innej metody instancji o tej samej sygnaturze.
  5. Możemy użyć metod statycznych interfejsu java do usunięcia klas narzędzi, takich jak Kolekcje, i przenieść wszystkie jego metody statyczne do odpowiedniego interfejsu, który byłby łatwy do znalezienia i użycia.

Interfejsy funkcjonalne Java

Zanim zakończę ten post, chciałbym krótko przedstawić interfejsy funkcjonalne. Interfejs z dokładnie jedną metodą abstrakcyjną jest znany jako interfejs funkcjonalny.

Wprowadzono nową adnotację @FunctionalInterface, aby oznaczyć interfejs jako interfejs funkcjonalny. @FunctionalInterfaceadnotacja to funkcja pozwalająca uniknąć przypadkowego dodania metod abstrakcyjnych w interfejsach funkcjonalnych. Korzystanie z niego jest opcjonalne, ale dobrą praktyką.

Interfejsy funkcjonalne są długo wyczekiwaną i bardzo poszukiwaną funkcją Java 8, ponieważ pozwala nam używać wyrażeń lambda do ich tworzenia. Dodano nowy pakiet java.util.function z szeregiem funkcjonalnych interfejsów, aby zapewnić typy docelowe dla wyrażeń lambda i odwołań do metod. W przyszłych postach przyjrzymy się interfejsom funkcjonalnym i wyrażeniom lambda.

Lokalizacja zasobu:

  1. Zmiany interfejsu Java 8 - metoda statyczna, metoda domyślna

8
Dokładnie szukam tego rodzaju zaktualizowanych odpowiedzi. Dzięki za szybką odpowiedź.
Ravindra babu

41

Wszystkie twoje instrukcje są ważne z wyjątkiem pierwszej instrukcji (po wydaniu Java 8):

Metody interfejsu Java są domyślnie abstrakcyjne i nie mogą mieć implementacji

Z dokumentacji stronie :

Interfejs jest typem odniesienia, podobnym do klasy, który może zawierać tylko stałe, sygnatury metod, metody domyślne, metody statyczne i typy zagnieżdżone

Ciała metod istnieją tylko dla metod domyślnych i metod statycznych.

Metody domyślne:

Interfejs może mieć domyślne metody , ale różni się od metod abstrakcyjnych w klasach abstrakcyjnych.

Domyślne metody pozwalają dodawać nowe funkcje do interfejsów bibliotek i zapewniają zgodność binarną z kodem napisanym dla starszych wersji tych interfejsów.

Po rozszerzeniu interfejsu zawierającego metodę domyślną możesz wykonać następujące czynności:

  1. W ogóle nie wspominaj o domyślnej metodzie, która pozwala twojemu rozszerzonemu interfejsowi dziedziczyć domyślną metodę.
  2. Ponownie określ domyślną metodę, która sprawia, że ​​jest abstract.
  3. Przedefiniuj domyślną metodę, która ją zastępuje.

Metody statyczne:

Oprócz metod domyślnych można definiować metody statyczne w interfejsach. (Metoda statyczna to metoda powiązana z klasą, w której jest zdefiniowana, a nie z dowolnym obiektem. Każda instancja klasy ma wspólne metody statyczne).

Ułatwia to organizowanie metod pomocniczych w bibliotekach;

Przykładowy kod ze strony dokumentacji o interfaceposiadaniu statici defaultmetodach.

import java.time.*;

public interface TimeClient {
    void setTime(int hour, int minute, int second);
    void setDate(int day, int month, int year);
    void setDateAndTime(int day, int month, int year,
                               int hour, int minute, int second);
    LocalDateTime getLocalDateTime();

    static ZoneId getZoneId (String zoneString) {
        try {
            return ZoneId.of(zoneString);
        } catch (DateTimeException e) {
            System.err.println("Invalid time zone: " + zoneString +
                "; using default time zone instead.");
            return ZoneId.systemDefault();
        }
    }

    default ZonedDateTime getZonedDateTime(String zoneString) {
        return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString));
    }
}

Skorzystaj z poniższych wskazówek, aby wybrać interfejs lub klasę abstrakcyjną.

Berło:

  1. Aby zdefiniować kontrakt (najlepiej bezstanowy - mam na myśli brak zmiennych)
  2. Aby połączyć niepowiązane klasy z ma możliwości.
  3. Aby zadeklarować publiczne zmienne stałe ( stan niezmienny )

Klasa abstrakcyjna:

  1. Współdziel kod między kilkoma blisko powiązanymi klasami. Ustanawia ona jest relacja.

  2. Udostępnij wspólny stan między pokrewnymi klasami (stan można zmodyfikować w konkretnych klasach)

Powiązane posty:

Interfejs vs Klasa abstrakcyjna (ogólne OO)

Implementuje vs rozszerza: kiedy używać? Co za różnica?

Przechodząc przez te przykłady, możesz to zrozumieć

Klasy niezwiązane mogą mieć możliwości poprzez interfejs, ale klasy pokrewne zmieniają zachowanie poprzez rozszerzenie klas podstawowych.


Co masz na myśli mówiąc „umowa bezpaństwowa”? Jest to pozycja 1 dotycząca interfejsów
Maksim Dmitriev

1
Brak stanu zmiennego. Ponieważ interfejs może mieć stałe, dane mogą być mutowane w przeciwieństwie do klas abstrakcyjnych
Ravindra babu

1
Korekta w powyższym oświadczeniu. W interfejsie danych nie można
mutować w

2
to najlepsza odpowiedź. Nie tylko odnosi się do Java8, ale także wyjaśnia, w jakich konkretnych sytuacjach używałbyś jednego z nich.
Shuklaswag

Koncepcja statelessinterfejsu jest miłym hitem. Interfejs nie może mieć żadnego stanu (interfejs może mieć stałe, ale są one ostateczne / statyczne, a zatem niezmienne).
Kaihua,

22

Twoje wyjaśnienie wygląda na przyzwoite, ale może wyglądało na to, że czytałeś to wszystko z podręcznika? : - /

Bardziej niepokoi mnie to, jak solidny był twój przykład? Czy starałeś się uwzględnić prawie wszystkie różnice między abstrakcją a interfejsami?

Osobiście sugerowałbym ten link: http://mindprod.com/jgloss/interfacevsabstract.html#TABLE

dla wyczerpującej listy różnic ..

Mam nadzieję, że pomoże to Tobie i wszystkim innym czytelnikom w przyszłych wywiadach


1
udostępniony link jest naprawdę niesamowity
Premraj

Możesz zapewnić domyślną implementację w interfejsach Java za pomocą domyślnego słowa kluczowego
Ogen,

21

Wielu młodszych programistów popełnia błąd, uważając interfejsy, klasy abstrakcyjne i konkretne za niewielkie odmiany tego samego, i wybiera jeden z nich wyłącznie ze względów technicznych: Czy potrzebuję wielokrotnego dziedziczenia? Czy potrzebuję miejsca, aby zastosować wspólne metody? Czy muszę zawracać sobie głowę czymś innym niż konkretną klasą? Jest to błędne i ukryty w tych pytaniach jest główny problem: „ja” . Pisząc kod dla siebie, rzadko myślisz o innych obecnych lub przyszłych programistach pracujących nad tym kodem lub z nim.

Interfejsy i klasy abstrakcyjne, choć pozornie podobne z technicznego punktu widzenia, mają zupełnie inne znaczenia i cele.

Podsumowanie

  1. Interfejs definiuje umowę, którą zrealizuje dla Ciebie niektóre wdrożenie .

  2. Klasa abstrakcyjna zapewnia domyślne zachowanie, którego implementacja może ponownie użyć.

Te dwa punkty powyżej są tym, czego szukam podczas wywiadu, i jest to wystarczająco zwięzłe streszczenie. Czytaj dalej, aby uzyskać więcej informacji.

Alternatywne podsumowanie

  1. Interfejs służy do definiowania publicznych interfejsów API
  2. Klasa abstrakcyjna służy do użytku wewnętrznego i do definiowania wskaźników SPI

Przez przykład

Innymi słowy: konkretna klasa wykonuje konkretną pracę w bardzo specyficzny sposób. Na przykład, ArrayListwykorzystuje ciągły obszar pamięci do przechowywania listy obiektów w zwarty sposób, który oferuje szybki losowy dostęp, iterację i zmiany w miejscu, ale jest straszny przy wstawianiu, usuwaniu, a czasem nawet dodawaniu; w międzyczasieLinkedList używa podwójnie połączonych węzłów do przechowywania listy obiektów, która zamiast tego oferuje szybką iterację, zmiany w miejscu oraz wstawianie / usuwanie / dodawanie, ale jest straszna przy przypadkowym dostępie. Te dwa typy list są zoptymalizowane pod kątem różnych przypadków użycia i bardzo ważne jest, jak je wykorzystasz. Kiedy próbujesz wycisnąć wydajność z listy, z którą intensywnie wchodzisz w interakcję, i gdy wybór rodzaju listy zależy od ciebie, powinieneś ostrożnie wybrać, który z nich tworzysz.

Z drugiej strony, użytkownicy wysokiego poziomu listy tak naprawdę nie dbają o to, jak jest ona faktycznie implementowana i powinni być odizolowani od tych szczegółów. Wyobraźmy sobie, że Java nie ujawnia Listinterfejsu, ale ma tylko konkretną Listklasę, która jest właśnie tym, co LinkedListjest teraz. Wszyscy programiści Java dostosowaliby swój kod do szczegółów implementacji: unikają losowego dostępu, dodają pamięć podręczną, aby przyspieszyć dostęp, lub po prostu dokonują ponownej implementacji ArrayListsamodzielnie, chociaż byłoby to niezgodne z całym innym kodem, który faktycznie działa Listtylko z . To byłoby okropne ... Ale teraz wyobraź sobie, że mistrzowie Javy faktycznie zdają sobie sprawę, że połączona lista jest okropna w większości rzeczywistych przypadków użycia, i postanowili przełączyć się na listę tablic tylko dla siebieListklasa dostępna. Wpłynęłoby to na wydajność każdego programu Java na świecie i ludzie nie byliby z tego powodu zadowoleni. A głównym winowajcą jest to, że szczegóły implementacji były dostępne, a programiści założyli, że są to stałe umowy, na których mogą polegać. Dlatego ważne jest, aby ukryć szczegóły implementacji i zdefiniować tylko abstrakcyjną umowę. Jest to cel interfejsu: zdefiniowanie, jaki rodzaj danych wejściowych akceptuje metoda i jakiego rodzaju danych wyjściowych oczekuje się, bez ujawniania wszystkich odwagi, która skusiłaby programistów do poprawienia kodu w celu dopasowania do wewnętrznych szczegółów, które mogą ulec zmianie przy każdej przyszłej aktualizacji .

Klasa abstrakcyjna znajduje się pośrodku między interfejsami a konkretnymi klasami. Ma to pomóc implementacjom we wspólnym lub nudnym kodzie. Na przykład, AbstractCollectionzapewnia, że ​​podstawowe implementacje isEmptyoparte na rozmiarze są containsrówne 0, gdy iterują i porównują, addAlljak powtórzono additd. Pozwala to implementacjom skoncentrować się na kluczowych elementach, które je odróżniają: jak faktycznie przechowywać i odzyskiwać dane.

Inna perspektywa: interfejsy API a interfejsy SPI

Interfejsy są bramami o niskiej spójności między różnymi częściami kodu. Pozwalają one na istnienie i ewolucję bibliotek bez rozbijania każdego użytkownika biblioteki, gdy coś zmienia się wewnętrznie. To się nazywa interfejs programowania aplikacji , a nie klasy programowania aplikacji. Na mniejszą skalę pozwalają także wielu programistom z powodzeniem współpracować przy projektach na dużą skalę, oddzielając różne moduły za pomocą dobrze udokumentowanych interfejsów.

Klasy abstrakcyjne są pomocnikami o wysokiej spójności, które można stosować podczas implementacji interfejsu, przy założeniu pewnego poziomu szczegółów implementacji. Alternatywnie klasy abstrakcyjne są używane do definiowania SPI, interfejsów dostawcy usług.

Różnica między interfejsem API a interfejsem SPI jest subtelna, ale ważna: w przypadku interfejsu API skupia się na tym, kto go używa , aw przypadku interfejsu SPI koncentruje się na tym, kto go implementuje .

Dodawanie metod do interfejsu API jest łatwe, wszyscy obecni użytkownicy interfejsu API będą się nadal kompilować. Dodawanie metod do SPI jest trudne, ponieważ każdy dostawca usług (konkretne wdrożenie) będzie musiał wdrożyć nowe metody. Jeśli interfejsy są używane do definiowania SPI, dostawca będzie musiał wydać nową wersję za każdym razem, gdy zmieni się umowa SPI. Jeśli zamiast tego zostaną użyte klasy abstrakcyjne, nowe metody można zdefiniować w kategoriach istniejących metod abstrakcyjnych lub jako puste throw not implemented exceptionkody pośredniczące, co pozwoli przynajmniej kompilować i uruchamiać starszą wersję implementacji usługi.

Uwaga na temat Java 8 i metod domyślnych

Chociaż Java 8 wprowadziła domyślne metody interfejsów, co sprawia, że ​​linia między interfejsami a klasami abstrakcyjnymi jest jeszcze bardziej rozmyta, nie było to tak, że implementacje mogą ponownie wykorzystywać kod, ale aby ułatwić zmianę interfejsów, które służą zarówno jako interfejs API, jak i SPI (lub są niewłaściwie używane do definiowania SPI zamiast klas abstrakcyjnych).

„Wiedza książkowa”

Szczegóły techniczne podane w odpowiedzi PO są uważane za „wiedzę książkową”, ponieważ jest to zwykle podejście stosowane w szkole i w większości książek o technologii na temat języka: co to jest, a nie jak używać go w praktyce, zwłaszcza w aplikacjach na dużą skalę .

Oto analogia: przypuszczano, że pytanie brzmiało:

Co lepiej wynająć na bal maturalny, samochód lub pokój hotelowy?

Odpowiedź techniczna brzmi:

W samochodzie możesz to zrobić wcześniej, ale w pokoju hotelowym możesz to zrobić wygodniej. Z drugiej strony pokój hotelowy znajduje się tylko w jednym miejscu, podczas gdy w samochodzie można to zrobić w większej liczbie miejsc, na przykład, powiedzmy, że możesz udać się do punktu widokowego, aby uzyskać ładny widok, lub w teatrze z samochodem, lub wiele innych miejsc, a nawet w więcej niż jednym miejscu. Ponadto w pokoju hotelowym znajduje się prysznic.

To wszystko prawda, ale całkowicie pomija argumenty, że są to dwie zupełnie różne rzeczy, i obie mogą być używane jednocześnie do różnych celów, a aspekt „robienia tego” nie jest najważniejszy w żadnej z tych dwóch opcji . Odpowiedź nie ma perspektywy, pokazuje niedojrzały sposób myślenia, a jednocześnie poprawnie przedstawia prawdziwe „fakty”.


Miałeś na myśli „low-sprzęgło”?
user2418306,

@ user2418306 Nie, spójność jest bardziej ogólnym terminem, który obejmuje sprzężenie, chociaż są bliskimi synonimami, i każdy z nich działałby.
Sergiu Dumitriu

9

A co z myśleniem w następujący sposób:

  • Związek między klasą a klasą abstrakcyjną jest typu „is-a”
  • Związek między klasą a interfejsem jest typu „has-a”

Więc jeśli masz abstrakcyjną klasę Mammals, podklasę Human i interfejs Driving, możesz powiedzieć

  • każdy człowiek jest ssakiem
  • każdy człowiek prowadzi samochód (zachowanie)

Moja sugestia jest taka, że ​​fraza wiedzy książkowej wskazuje, że chciał usłyszeć semantyczną różnicę między nimi (tak jak inni już tutaj zasugerowali).


9

Interfejs to „umowa”, w której klasa implementująca umowę obiecuje wdrożenie metod. Przykładem, w którym musiałem napisać interfejs zamiast klasy, było uaktualnienie gry z 2D do 3D. Musiałem stworzyć interfejs do dzielenia klas między wersją 2D i 3D gry.

package adventure;
import java.awt.*;
public interface Playable {
    public void playSound(String s);
    public Image loadPicture(String s);    
}

Następnie mogę zaimplementować metody oparte na środowisku, wciąż będąc w stanie wywoływać te metody z obiektu, który nie wie, która wersja gry się ładuje.

public class Adventure extends JFrame implements Playable

public class Dungeon3D extends SimpleApplication implements Playable

public class Main extends SimpleApplication implements AnimEventListener, ActionListener, Playable

Zazwyczaj w świecie gry świat może być klasą abstrakcyjną, która wykonuje metody w grze:

public abstract class World...

    public Playable owner;

    public Playable getOwner() {
        return owner;
    }

    public void setOwner(Playable owner) {
        this.owner = owner;
    }

6

Klasy abstrakcyjne nie są czystą abstrakcją, lecz zbiorem konkretnych (zaimplementowanych metod), jak również metod niezaimplementowanych. Ale interfejsy są czystą abstrakcją, ponieważ istnieją tylko metody niezaimplementowane, a nie konkretne.

Dlaczego klasy abstrakcyjne?

  1. Jeśli użytkownik chce napisać wspólną funkcjonalność dla wszystkich obiektów.
  2. Klasy abstrakcyjne są najlepszym wyborem do ponownej implementacji w przyszłości, aby dodać więcej funkcji bez wpływu na użytkownika końcowego.

Dlaczego interfejsy?

  1. Jeśli użytkownik chce napisać inną funkcjonalność, będzie to inna funkcjonalność na obiektach.
  2. Interfejsy to najlepszy wybór, jeśli nie trzeba modyfikować wymagań po opublikowaniu interfejsu.

5

interfejs jest jak zestaw genów, które są publicznie udokumentowanych mieć jakiś efekt: DNA badanie powie mi, czy mam je - i jeśli to zrobię, mogę publicznie oświadczyć, że jestem „nośnik ”i część mojego zachowania lub stanu będzie z nimi zgodna. (Ale oczywiście mogę mieć wiele innych genów, które zapewniają cechy poza tym zakresem).

Klasa abstrakcyjna jest jak martwego przodka z niekoedukacyjnego gatunki (*): Ona nie może być powołany do życia, ale życia (czyli non-streszczenie ) potomnych dziedziczy wszystkie jej genów.

(*) Aby rozciągnąć tę metaforę, powiedzmy, że wszyscy członkowie gatunku żyją w tym samym wieku. Oznacza to, że wszyscy przodkowie zmarłego przodka również muszą być martwi - podobnie wszyscy potomkowie żywego przodka muszą żyć.


4

Robię wywiady do pracy i również źle wyglądam na twoją odpowiedź (przepraszam, ale jestem bardzo szczery). Brzmi tak, jakbyś przeczytał o różnicy i poprawił odpowiedź, ale być może nigdy nie używałeś jej w praktyce.

Dobre wyjaśnienie, dlaczego warto użyć każdego z nich, może być znacznie lepsze niż precyzyjne wyjaśnienie różnicy. Pracodawcy ultimatley chcą, aby programiści robili rzeczy, których nie znają, co może być trudne do wykazania w wywiadzie. Odpowiedź, którą podasz, byłaby dobra, gdybyś ubiegał się o pracę w oparciu o dokumentację techniczną lub dokumentację, ale nie byłby programistą.

Powodzenia w wywiadach w przyszłości.

Moja odpowiedź na to pytanie dotyczy raczej techniki wywiadu, a nie dostarczonego materiału technicznego. Może warto przeczytać o tym. https://workplace.stackexchange.com/ może być doskonałym miejscem na tego typu rzeczy.


1
Czy możesz mi powiedzieć, jak odpowiedziałeś? Może to może mi pomóc.
code_fish 10.10.13

udzielenie odpowiedzi oferuje o wiele mniej niż pomoc w jej wypracowaniu, w zasadzie daj praktyczny przykład tego, kiedy chcesz użyć każdego z nich, i wyjaśnij, dlaczego każdy z nich jest odpowiedni do różnych zadań.
Adrian

4

Wybieramy Interfejs w Javie, aby uniknąć Diamentowego Problemu podczas wielokrotnego dziedziczenia .

Jeśli chcesz, aby wszystkie metody zostały zaimplementowane przez klienta, wybierz interfejs. Oznacza to, że projektujesz całą aplikację w sposób abstrakcyjny.

Wybieracie klasę abstrakcyjną, jeśli już wiecie, co jest wspólne. Na przykład Weź klasę abstrakcyjną Car. Na wyższym poziomie wdrażasz typowe metody samochodowe, takie jak calculateRPM(). Jest to powszechna metoda i pozwalasz klientowi wdrożyć własne zachowanie, takie jak
calculateMaxSpeed()itp. Prawdopodobnie wyjaśniłbyś, podając kilka przykładów w czasie rzeczywistym, które napotkałeś w codziennej pracy.



3

Główną różnicą, którą zaobserwowałem, było to, że klasa abstrakcyjna zapewnia nam pewne typowe zachowania już zaimplementowane, a podklasy muszą jedynie zaimplementować odpowiadającą im funkcjonalność. gdzie jak dla interfejsu określa tylko zadania, które należy wykonać, a interfejs nie podaje żadnych implementacji. Mogę powiedzieć, że określa umowę między sobą a zaimplementowanymi klasami.


3

Nawet ja spotkałem się z tym samym pytaniem w wielu wywiadach i wierzcie mi, że przekonywanie rozmówcy sprawia, że ​​wasz czas jest nieszczęśliwy. Jeśli wpisuję wszystkie odpowiedzi z góry, muszę dodać jeszcze jeden kluczowy punkt, aby był bardziej przekonujący i wykorzystywał OO w najlepszym wydaniu

W przypadku, gdy nie planujesz żadnych modyfikacji w regułach , dla podklasy, która ma być przestrzegana, w długiej przyszłości wybierz interfejs, ponieważ nie będziesz mógł go modyfikować, a jeśli to zrobisz, musisz przejść do zmiany we wszystkich pozostałych podklasach, a jeśli uważasz, że chcesz ponownie użyć tej funkcji, ustawić pewne reguły, a także umożliwić jej modyfikację , przejdź do klasy Abstract.

Pomyśl w ten sposób, że korzystałeś z usługi eksploatacyjnej lub podałeś jakiś kod światu i masz szansę zmodyfikować coś, przypuśćmy kontrolę bezpieczeństwa. A jeśli jestem konsumentem kodu i któregoś ranka po aktualizacji, I znajdź wszystkie znaki odczytu w moim Eclipse, cała aplikacja jest wyłączona. Aby zapobiec takim koszmarom, użyj opcji Streszczenie zamiast interfejsów

Myślę, że to może do pewnego stopnia przekonać Ankietera ... Szczęśliwe wywiady z wyprzedzeniem.


2

Kiedy próbuję podzielić zachowanie między 2 blisko spokrewnionymi klasami, tworzę abstrakcyjną klasę, która zawiera wspólne zachowanie i służy jako rodzic dla obu klas.

Kiedy próbuję zdefiniować typ, listę metod, które użytkownik mojego obiektu może rzetelnie wywołać, a następnie tworzę interfejs.

Na przykład nigdy nie stworzyłbym klasy abstrakcyjnej z 1 konkretną podklasą, ponieważ klasy abstrakcyjne dotyczą zachowania współużytkowania. Ale równie dobrze mogę stworzyć interfejs z tylko jedną implementacją. Użytkownik mojego kodu nie będzie wiedział, że istnieje tylko jedna implementacja. Rzeczywiście, w przyszłej wersji może być kilka implementacji, z których wszystkie są podklasami jakiejś nowej klasy abstrakcyjnej, która nawet nie istniała, kiedy tworzyłem interfejs.

To też może wydawać się trochę zbyt książkowe (choć nigdy nie widziałem, żeby było to tak nigdzie przypominam). Gdyby osoba przeprowadzająca wywiad (lub OP) naprawdę chciała więcej moich osobistych doświadczeń w tym zakresie, byłbym gotowy z anegdotami dotyczącymi interfejsu, który ewoluował z konieczności i odwrotnie.

Jeszcze jedna rzecz. Java 8 pozwala teraz umieścić domyślny kod w interfejsie, dodatkowo zacierając linię między interfejsami a klasami abstrakcyjnymi. Ale z tego, co widziałem, ta funkcja jest nadużywana nawet przez twórców podstawowych bibliotek Java. Ta funkcja została dodana, i słusznie, aby umożliwić rozszerzenie interfejsu bez tworzenia binarnej niezgodności. Ale jeśli tworzysz nowy typ, definiując interfejs, interfejs powinien być TYLKO interfejsem. Jeśli chcesz również podać wspólny kod, to z całą pewnością stwórz klasę pomocniczą (abstrakcyjną lub konkretną). Nie zaśmiecaj interfejsu od samego początku funkcjonalnością, którą możesz zmienić.


2

Podstawowa różnica między interfejsem a klasą abstrakcyjną polega na tym, że interfejs obsługuje wielokrotne dziedziczenie, ale klasa abstrakcyjna nie.

W klasie abstrakcyjnej można również udostępnić wszystkie metody abstrakcyjne, takie jak interfejs.

dlaczego wymagana jest klasa abstrakcyjna?

W niektórych scenariuszach podczas przetwarzania żądania użytkownika klasa abstrakcyjna nie wie, jaki jest zamiar użytkownika. W tym scenariuszu zdefiniujemy jedną metodę abstrakcyjną w klasie i poprosimy użytkownika, który rozszerza tę klasę, o podanie swojej intencji w metodzie abstrakcyjnej. W tym przypadku klasy abstrakcyjne są bardzo przydatne

Dlaczego interfejs jest wymagany?

Powiedzmy, że mam pracę, której nie mam doświadczenia w tej dziedzinie. Na przykład, jeśli chcesz zbudować budynek lub tamę, to co zrobisz w tym scenariuszu?

  1. określisz swoje wymagania i zawrzesz z nimi umowę.
  2. Następnie zadzwoń do Przetargów, aby zbudować swój projekt
  3. Kto kiedykolwiek zbuduje projekt, który powinien spełnić Twoje wymagania. Ale logika budowy różni się od jednego dostawcy do drugiego dostawcy.

Tutaj nie zawracam sobie głowy logiką ich budowy. Ostateczny obiekt spełnił moje wymagania lub nie, to tylko mój kluczowy punkt.

Tutaj twoje wymagania zwane interfejsem, a konstruktory są nazywane implementatorem.


2

W kilku słowach odpowiedziałbym w ten sposób:

  • dziedziczenie poprzez hierarchię klas implikuje dziedziczenie stanu ;
  • mając na uwadze, że dziedziczenie za pośrednictwem interfejsów oznacza dziedziczenie zachowania ;

Klasy abstrakcyjne mogą być traktowane jako coś pomiędzy tymi dwoma przypadkami (wprowadza pewien stan, ale także zobowiązuje do zdefiniowania zachowania), klasa w pełni abstrakcyjna jest interfejsem (jest to dalszy rozwój klas składających się z metod wirtualnych tylko w C ++, ponieważ o ile wiem o jego składni).

Oczywiście, począwszy od Javy 8, sprawy nieco się zmieniły, ale pomysł jest taki sam.

Myślę, że to wystarcza do typowego wywiadu w języku Java, jeśli nie przeprowadzamy wywiadu z zespołem zajmującym się kompilacją.


1

Z tego, co rozumiem, interfejs, który składa się z końcowych zmiennych i metod bez implementacji, jest implementowany przez klasę w celu uzyskania grupy metod lub metod, które są ze sobą powiązane. Z drugiej strony klasa abstrakcyjna, która może zawierać nie-końcowe zmienne i metody z implementacjami, jest zwykle używana jako przewodnik lub nadklasa, z której dziedziczą wszystkie klasy pokrewne lub podobne. Innymi słowy, klasa abstrakcyjna zawiera wszystkie metody / zmienne, które są wspólne dla wszystkich jej podklas.


1

W klasie abstrakcyjnej możesz napisać domyślną implementację metod! Ale w interfejsie nie możesz. Zasadniczo w interfejsie istnieją metody czysto wirtualne, które muszą zostać zaimplementowane przez klasę implementującą interfejs.


1

Tak, twoje odpowiedzi były technicznie poprawne, ale to, że popełniłeś błąd, nie pokazywało im, że rozumiesz wady i zalety wyboru jednej z drugiej. Dodatkowo, prawdopodobnie martwili się / dziwili się na temat zgodności ich bazy kodu z aktualizacjami w przyszłości. Ten rodzaj odpowiedzi mógł pomóc (oprócz tego, co powiedziałeś):

„Wybór klasy abstrakcyjnej zamiast klasy interfejsu zależy od tego, jak przewidujemy przyszłość kodu.

Klasy abstrakcyjne pozwalają na lepszą kompatybilność w przód, ponieważ możesz kontynuować dodawanie zachowania do klasy abstrakcyjnej również w przyszłości bez zerwania z istniejącym kodem -> nie jest to możliwe w przypadku klasy interfejsu.

Z drugiej strony klasy interfejsu są bardziej elastyczne niż klasy abstrakcyjne. Wynika to z faktu, że mogą implementować wiele interfejsów . Rzecz w tym, że Java nie ma wielu dziedziczeń, więc użycie klas abstrakcyjnych nie pozwoli na użycie żadnej innej struktury hierarchii klas ...

Tak więc ostatecznie ogólną zasadą jest: preferuj używanie klas interfejsów, gdy nie ma żadnych istniejących / domyślnych implementacji w bazie kodu. I użyj klas abstrakcyjnych, aby zachować zgodność, jeśli wiesz, że będziesz aktualizować swoją klasę w przyszłości. ”

Powodzenia w kolejnym wywiadzie!


1

Spróbuję odpowiedzieć, używając praktycznego scenariusza, aby pokazać różnicę między nimi.

Interfejsy są dostarczane z zerowym obciążeniem, tzn. Nie trzeba utrzymywać żadnego stanu, a zatem są lepszym wyborem, aby powiązać kontrakt (zdolność) z klasą.

Powiedzmy na przykład, że mam klasę Task, która wykonuje jakąś akcję, teraz aby wykonać zadanie w osobnym wątku, tak naprawdę nie muszę rozszerzać klasy Thread, raczej lepszym wyborem jest sprawienie, aby Task implementował interfejs Runnable (tj. Zaimplementował metodę run () ), a następnie przekaż obiekt tej klasy Task do instancji Thread i wywołaj jej metodę start ().

Teraz możesz zapytać, co jeśli Runnable była klasą abstrakcyjną?

Technicznie było to możliwe, ale pod względem projektowym byłby to zły wybór:

  • Runnable nie ma powiązanego z nim stanu i ani nie „oferuje” żadnej domyślnej implementacji dla metody run ()
  • Zadanie musiałoby go rozszerzyć, aby nie mogło rozszerzyć żadnej innej klasy
  • Zadanie nie ma nic do zaoferowania jako specjalizacja dla klasy Runnable, wszystko czego potrzebuje to zastąpienie metody run ()

Innymi słowy, klasa Task wymagała zdolności do uruchomienia w wątku, który osiągnęła dzięki zaimplementowaniu wersetów interfejsu Runnable rozszerzających klasę Thread, które uczyniłyby z niej wątek.

Po prostu umieść nam interfejs do zdefiniowania zdolności (kontraktu), podczas gdy użyj abstrakcyjnej klasy do zdefiniowania szkieletowej (wspólnej / częściowej) jej implementacji.

Uwaga: głupi przykład poniżej, staraj się nie oceniać :-P

interface Forgiver {
    void forgive();
}

abstract class GodLike implements Forgiver {
    abstract void forget();
    final void forgive() {
        forget();
    }
}

Teraz masz wybór, aby być Bogiem, ale możesz wybrać tylko Przebaczenie (tj. Nie Godlike) i wykonać:

class HumanLike implements Forgiver {
    void forgive() {
       // forgive but remember    
    }
}

Lub możesz wybrać bycie Bogiem i wykonać:

class AngelLike extends GodLike {
    void forget() {
       // forget to forgive     
    }
}

PS z interfejsem java 8 może również posiadać metody statyczne i domyślne (implementacja nadpisywalna), dzięki czemu różnica między czarno-białym interfejsem a klasą abstrakcyjną jest jeszcze bardziej zawężona.


1

Wydaje się, że już prawie wszystko zostało tutaj omówione. Dodanie jeszcze jednego punktu na temat praktycznej implementacji abstractklasy:

abstractsłowo kluczowe jest również używane, aby zapobiec tworzeniu instancji klasy. Jeśli masz konkretną klasę, której nie chcesz tworzyć instancji - Zrób to abstract.


1

hmm, teraz ludzie są głodni praktycznego podejścia, masz rację, ale większość ankieterów wygląda zgodnie z ich obecnymi wymaganiami i chce praktycznego podejścia.

po zakończeniu odpowiedzi powinieneś skoczyć na przykład:

Abstrakcyjny:

na przykład mamy funkcję wynagrodzenia, która ma pewien parametr wspólny dla wszystkich pracowników. wtedy możemy mieć klasę abstrakcyjną o nazwie CTC z częściowo zdefiniowanym ciałem metody, która zostanie rozszerzona przez wszystkich typów pracowników i zostanie ponownie zaprojektowana zgodnie z ich dodatkowymi cechami. Dla wspólnej funkcjonalności.

public abstract class CTC {

    public int salary(int hra, int da, int extra)
    {
        int total;
        total = hra+da+extra;
        //incentive for specific performing employee
        //total = hra+da+extra+incentive;
        return total;
    }
}

class Manger extends CTC
{
}


class CEO extends CTC
{
}

class Developer extends CTC
{   
}

Berło

Interfejs w java pozwala mieć funkcjonalność interfejsu bez rozszerzania tej i musisz być przejrzysty dzięki implementacji sygnatury funkcjonalności, którą chcesz wprowadzić w swojej aplikacji. zmusi cię do posiadania definicji. Dla różnych funkcji.

public interface EmployeType {

    public String typeOfEmployee();
}

class ContarctOne implements EmployeType
{

    @Override
    public String typeOfEmployee() {
        return "contract";
    }

}

class PermanentOne implements EmployeType
{

    @Override
    public String typeOfEmployee() {
        return "permanent";
    }

}

możesz mieć taką wymuszoną aktywność również z klasą abstrakcyjną przez zdefiniowane metody jako abstrakcyjne, teraz klasa, która rozszerza klasę abstrakcyjną, reminuje abstrakcyjną, dopóki nie nadpisze tej funkcji abstrakcyjnej.


1

Z tego, co rozumiem i jak podchodzę,

Interfejs jest jak specyfikacja / umowa, każda klasa implementująca klasę interfejsu musi implementować wszystkie metody zdefiniowane w klasie abstrakcyjnej (z wyjątkiem metod domyślnych (wprowadzonych w Javie 8))

Podczas gdy definiuję streszczenie klasy, gdy wiem, że implementacja jest wymagana dla niektórych metod klasy i niektórych metod, nadal nie wiem, co będzie implementacją (możemy znać sygnaturę funkcji, ale nie implementację). Robię to, aby później, w części programistycznej, kiedy wiem, jak te metody mają zostać zaimplementowane, mogę po prostu rozszerzyć tę abstrakcyjną klasę i zaimplementować te metody.

Uwaga: Nie można mieć treści funkcji w metodach interfejsu, chyba że metoda jest statyczna lub domyślna.


0

Uważam, że to, o co pytał ankieter, było prawdopodobnie różnicą między interfejsem a implementacją.

Interfejs - nie interfejs Java, ale „interfejs” w bardziej ogólnym ujęciu - z modułem kodu jest w zasadzie umową zawartą z kodem klienta korzystającym z interfejsu.

Implementacja modułu kodu to wewnętrzny kod, który powoduje, że moduł działa. Często możesz wdrożyć określony interfejs na więcej niż jeden inny sposób, a nawet zmienić implementację bez kodu klienta, nawet nie wiedząc o zmianie.

Interfejs Java powinien być używany wyłącznie jako interfejs w powyższym sensie ogólnym, aby zdefiniować zachowanie klasy na korzyść kodu klienta korzystającego z klasy, bez określania jakiejkolwiek implementacji. Interfejs zawiera zatem sygnatury metod - nazwy, typy zwracane i listy argumentów - dla metod, które mają być wywoływane przez kod klienta, i zasadniczo powinien mieć mnóstwo Javadoc dla każdej metody opisującej działanie tej metody. Najbardziej przekonującym powodem korzystania z interfejsu jest planowanie wielu różnych implementacji interfejsu, być może wybór implementacji zależy od konfiguracji wdrożenia.

Natomiast klasa abstrakcyjna Java zapewnia częściową implementację klasy, a nie jej głównym celem jest określenie interfejsu. Powinno być używane, gdy wiele klas współdzieli kod, ale gdy oczekuje się, że podklasy zapewnią także część implementacji. Dzięki temu wspólny kod może pojawiać się tylko w jednym miejscu - klasie abstrakcyjnej - jednocześnie wyjaśniając, że części implementacji nie występują w klasie abstrakcyjnej i oczekuje się, że zostaną dostarczone przez podklasy.


0

twoja odpowiedź jest prawidłowa, ale osoba przeprowadzająca wywiad wymaga rozróżnienia według perspektywy inżynierii oprogramowania, a nie szczegółów Java.

Proste słowa:

Interfejs jest jak interfejs sklepu, wszystko, co jest na nim pokazane, powinno znajdować się w sklepie, więc każda metoda w interfejsie musi być tam zaimplementowana w konkretnej klasie. A co, jeśli niektóre klasy mają pewne dokładne metody i różnią się w innych. Załóżmy, że interfejs dotyczy sklepu, który zawiera dwie rzeczy i załóżmy, że oba sklepy zawierają sprzęt sportowy, ale jeden ma dodatkowe ubrania, a drugi dodatkowe buty. Więc tworzysz abstrakcyjną klasę dla sportu, która implementuje metodę Sports i pozostawia inną metodę niezaimplementowaną. Klasa abstrakcyjna tutaj oznacza, że ​​ten sklep sam nie istnieje, ale jest bazą dla innych klas / sklepów. W ten sposób organizujesz kod, unikając błędów przy replikacji kodu, ujednolicając kod i zapewniając możliwość ponownego użycia przez inną 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.