Dlaczego klasa abstrakcyjna implementująca interfejs może pomijać deklarację / implementację jednej z metod interfejsu?

123

Ciekawa rzecz dzieje się w Javie, gdy używasz klasy abstrakcyjnej do implementacji interfejsu: może brakować niektórych metod interfejsu (tj. Nie ma ani abstrakcyjnej deklaracji, ani rzeczywistej implementacji), ale kompilator nie narzeka.

Na przykład, biorąc pod uwagę interfejs:

public interface IAnything {
  void m1();
  void m2();
  void m3();
}

Następująca klasa abstrakcyjna zostaje radośnie skompilowana bez ostrzeżenia lub błędu:

public abstract class AbstractThing implements IAnything {
  public void m1() {}
  public void m3() {}
}

Czy możesz mi wytłumaczyć dlaczego?

Giulio Piancastelli
źródło
2
Nie można stworzyć obiektu klasy abstrakcyjnej. Tak więc dopóki nie zostanie zapewniona implementacja dla klasy abstrakcyjnej, nie można tworzyć obiektów dla IAnything. Więc to jest absolutnie w porządku dla kompilatora. Kompilator oczekuje, że każda nieabstrakcyjna klasa implementująca IAnything musi implementować wszystkie metody zadeklarowane z IAnything. A ponieważ trzeba rozszerzyć i zaimplementować AbstractThing, aby móc tworzyć obiekty, kompilator zgłosi błąd, jeśli ta implementacja nie implementuje metod IAnything pominiętych przez AbstractThing.
VanagaS
Miałem konkretną klasę, która rozszerzała swoje własne "AbstractThing" w identycznym scenariuszu jak ten i chociaż nie zaimplementowałem żadnej z metod w interfejsie, kompilowała się w niewytłumaczalny sposób. Teraz robi to, czego się spodziewam, ale wcześniej nie potrafię zrozumieć, co sprawiło, że odniosło sukces. Podejrzewam, że nie miałem żadnego :wz plików.
Braden Best
możesz zobaczyć odpowiedź na podobne pytanie stackoverflow.com/questions/8026580/…
Do Nhu Vy

Odpowiedzi:

155

Dzieje się tak, ponieważ jeśli klasa jest abstrakcyjna, to z definicji wymagane jest utworzenie jej podklas w celu utworzenia instancji. Podklasy będą wymagane (przez kompilator) do zaimplementowania wszelkich metod interfejsu, które pominęła klasa abstrakcyjna.

Postępując zgodnie z przykładowym kodem, spróbuj utworzyć podklasę AbstractThingbez implementowania m2metody i zobacz, jakie błędy daje kompilator. Zmusi Cię to do wdrożenia tej metody.

Bill the Lizard
źródło
1
Myślę, że kompilator powinien nadal generować ostrzeżenia dotyczące klas abstrakcyjnych, które implementują interfejsy niekompletnie, po prostu dlatego, że musisz przejrzeć 2 definicje klas, a nie 1, aby zobaczyć, czego potrzebujesz w podklasie. Jest to jednak ograniczenie języka / kompilatora.
workmad3
3
To nie byłby dobry pomysł, ponieważ zazwyczaj może być wiele klas abstrakcyjnych, a „fałszywe” ostrzeżenia wkrótce cię przytłoczy, powodując pominięcie „prawdziwych” ostrzeżeń. Jeśli się nad tym zastanowić, słowo kluczowe „abstract” ma specjalnie po to, by powiedzieć kompilatorowi, aby pominął ostrzeżenia dla tej klasy.
belugabob
4
@workmad - jeśli masz wspólne implementacje dla podzbioru metod interfejsu, lepiej jest uwzględnić je w osobnej klasie bazowej (DRY ma przewagę nad kodem z jednym miejscem)
Gishu
4
Wymaganie umieszczenia pustych implementacji metod w klasie abstrakcyjnej byłoby niebezpieczne . Jeśli to zrobiłeś, implementatorzy podklas odziedziczyliby to niedziałanie bez informowania ich przez kompilator o problemie.
Bill the Lizard
8
Myślę, że workmad może sugerować, że definiujesz metody w klasie abstrakcyjnej bez treści metody i oznaczasz je jako abstrakcyjne. Nie wydaje mi się to złym pomysłem.
Dónal
33

Całkiem w porządku.
Nie można utworzyć instancji klas abstrakcyjnych ... ale klasy abstrakcyjne mogą służyć do przechowywania typowych implementacji dla m1 () i m3 ().
Więc jeśli implementacja m2 () jest inna dla każdej implementacji, ale m1 i m3 nie. Możesz tworzyć różne konkretne implementacje IAnything tylko z inną implementacją m2 i czerpać z AbstractThing - z poszanowaniem zasady DRY. Sprawdzanie, czy interfejs jest całkowicie zaimplementowany dla klasy abstrakcyjnej, jest daremne.

Aktualizacja : Co ciekawe, uważam, że C # wymusza to jako błąd kompilacji. W tym scenariuszu musisz skopiować sygnatury metod i poprzedzić je „abstract public” w abstrakcyjnej klasie bazowej .. (codziennie coś nowego :)

Gishu
źródło
7

W porządku. Aby zrozumieć powyższe, musisz najpierw zrozumieć naturę klas abstrakcyjnych. Pod tym względem są podobne do interfejsów. Oto, co o tym tutaj mówi Oracle .

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

Musisz więc pomyśleć o tym, co się stanie, gdy interfejs rozszerzy inny interfejs. Na przykład ...

//Filename: Sports.java
public interface Sports
{
   public void setHomeTeam(String name);
   public void setVisitingTeam(String name);
}

//Filename: Football.java
public interface Football extends Sports
{
   public void homeTeamScored(int points);
   public void visitingTeamScored(int points);
   public void endOfQuarter(int quarter);
}

... jak widać, kompiluje się to również doskonale. Po prostu dlatego, że podobnie jak w przypadku klasy abstrakcyjnej NIE można utworzyć wystąpienia interfejsu. Dlatego nie jest wymagane jawne wymienianie metod z jego „rodzica”. Jednak WSZYSTKIE sygnatury metod nadrzędnych niejawnie stają się częścią rozszerzającego interfejsu lub implementującej klasy abstrakcyjnej. Tak więc, gdy odpowiednia klasa (taka, która może zostać utworzona) rozszerzy powyższe, będzie wymagane upewnienie się, że każda metoda abstrakcyjna zostanie zaimplementowana.

Mam nadzieję, że to pomoże ... i Allahu 'alam!

Wdzięczny
źródło
To ciekawy punkt widzenia. To sprawia, że ​​myślę, że „klasy abstrakcyjne” to tak naprawdę „konkretne interfejsy”, tj. Interfejsy z pewnymi konkretnymi metodami, a nie klasy z metodami abstrakcyjnymi.
Giulio Piancastelli
... trochę obu naprawdę. Ale jedno jest pewne, nie są one natychmiastowe.
Wdzięczny
4

Interfejs oznacza klasę, która nie ma implementacji swojej metody, ale ma tylko deklarację.
Z drugiej strony, klasa abstrakcyjna to klasa, która może mieć implementację pewnej metody razem z jakąś metodą z samą deklaracją, bez implementacji.
Kiedy implementujemy interfejs do klasy abstrakcyjnej, oznacza to, że klasa abstrakcyjna odziedziczyła wszystkie metody interfejsu. Ponieważ implementacja całej metody w klasie abstrakcyjnej nie jest ważna, jednak chodzi o klasę abstrakcyjną (również przez dziedziczenie), więc klasa abstrakcyjna może pozostawić część metody w interfejsie bez implementacji tutaj. Ale kiedy ta klasa abstrakcyjna zostanie odziedziczona przez jakąś konkretną klasę, muszą zaimplementować wszystkie te niezaimplementowane metody w klasie abstrakcyjnej.

Mustakimur Rahman
źródło
4

Biorąc pod uwagę interfejs:

public interface IAnything {
  int i;
  void m1();
  void m2();
  void m3();
}

Tak naprawdę widzi to Java:

public interface IAnything {
  public static final int i;
  public abstract void m1();
  public abstract void m2();
  public abstract void m3();
}

Możesz więc pozostawić niektóre (lub wszystkie) z tych abstractmetod niezaimplementowanych, tak jak zrobiłbyś to w przypadku abstractklas rozszerzających inną abstractklasę.

Kiedy , reguła, że wszystkie metody muszą być wdrożone w pochodny , odnosi się tylko do konkretnej realizacji (tj, która nie jest sama w sobie).implementinterfaceinterfaceclassclassabstract

Jeśli rzeczywiście planujesz stworzyć abstract classwyjście z tego, nie ma reguły, która mówi, że masz do implementwszystkich interfacemetod (pamiętaj, że w takim przypadku obowiązkowe jest zadeklarowanie pochodnej classjako abstract)

sharhp
źródło
Użycie javap IAnything.classdo wygenerowania drugiego fragmentu kodu.
sharhp
3

Gdy klasa abstrakcyjna implementuje interfejs

W sekcji poświęconej interfejsom zauważono, że klasa implementująca interfejs musi implementować wszystkie metody interfejsu. Możliwe jest jednak zdefiniowanie klasy, która nie implementuje wszystkich metod interfejsu, pod warunkiem, że klasa jest zadeklarowana jako abstrakcyjna. Na przykład,

abstract class X implements Y {   
    // implements all but one method of Y
}

class XX extends X {   
    // implements the remaining method in Y 
} 

W tym przypadku klasa X musi być abstrakcyjna, ponieważ nie w pełni implementuje Y, ale klasa XX faktycznie implementuje Y.

Źródła: http://docs.oracle.com/javase/tutorial/java/IandI/abstract.html

Do Nhu Vy
źródło
1

Do implementacji metod nie są wymagane klasy abstrakcyjne. Więc nawet jeśli implementuje interfejs, abstrakcyjne metody interfejsu mogą pozostać abstrakcyjne. Jeśli spróbujesz zaimplementować interfejs w konkretnej klasie (tj. Nie abstrakcyjnej) i nie zaimplementujesz metod abstrakcyjnych, kompilator powie ci: albo zaimplementuj metody abstrakcyjne, albo zadeklaruj klasę jako abstrakcyjną.

Vincent Ramdhanie
źródło