Konstruktor w interfejsie?

148

Wiem, że nie można zdefiniować konstruktora w interfejsie. Ale zastanawiam się, dlaczego, bo myślę, że może być bardzo przydatne.

Więc możesz być pewien, że niektóre pola w klasie są zdefiniowane dla każdej implementacji tego interfejsu.

Weźmy na przykład pod uwagę następującą klasę wiadomości:

public class MyMessage {

   public MyMessage(String receiver) {
      this.receiver = receiver;
   }

   private String receiver;

   public void send() {
      //some implementation for sending the mssage to the receiver
   }
}

Jeśli zdefiniujesz interfejs dla tej klasy, abym mógł mieć więcej klas, które implementują interfejs wiadomości, mogę zdefiniować tylko metodę wysyłania, a nie konstruktora. Jak więc mogę zapewnić, że każda implementacja tej klasy naprawdę ma ustawiony odbiornik? Jeśli używam metody takiej setReceiver(String receiver), jak nie mam pewności, czy ta metoda jest naprawdę wywołana. W konstruktorze mogłem to zapewnić.

daniel kullmann
źródło
2
Mówisz "W konstruktorze mogłem zapewnić [każda implementacja tej klasy naprawdę ma ustawiony odbiornik]". Ale nie, nie możesz tego zrobić. Gdyby było możliwe zdefiniowanie takiego konstruktora, parametr byłby tylko mocną wskazówką dla twoich implementatorów - ale mogliby po prostu go zignorować, gdyby chcieli.
Julien Silland
3
@mattb Umm, to inny język.
takennes

Odpowiedzi:

129

Biorąc niektóre z rzeczy, które opisałeś:

„Więc możesz być pewien, że niektóre pola w klasie są zdefiniowane dla każdej implementacji tego interfejsu”.

„Jeśli zdefiniujesz interfejs dla tej klasy, aby mieć więcej klas implementujących interfejs wiadomości, mogę zdefiniować tylko metodę wysyłania, a nie konstruktora”

... te wymagania są dokładnie tym, do czego służą klasy abstrakcyjne .

matowe b
źródło
ale zauważ, że przypadek użycia @Sebi (wywoływanie przeciążonych metod z konstruktorów nadrzędnych) jest złym pomysłem, jak wyjaśniono w mojej odpowiedzi.
rsp
44
Matt, to oczywiście prawda, ale klasy abstrakcyjne cierpią z powodu ograniczenia dziedziczenia pojedynczego, co prowadzi ludzi do spojrzenia na inne sposoby określania hierarchii.
CPerkins
6
To prawda i może rozwiązać bezpośredni problem Sebi. Ale jednym z powodów używania interfejsów w Javie jest to, że nie można mieć wielokrotnego dziedziczenia. W przypadku, gdy nie mogę uczynić mojej „rzeczy” klasą abstrakcyjną, ponieważ muszę dziedziczyć po czymś innym, problem pozostaje. Nie twierdzę, że mam rozwiązanie.
Jay
7
@CPerkins Chociaż to prawda, nie sugeruję, że użycie klasy abstrakcyjnej rozwiąże przypadek użycia Sebi. Jeśli już, najlepiej jest zadeklarować Messageinterfejs, który definiuje send()metodę, a jeśli Sebi chce udostępnić klasę „bazową” dla implementacji Messageinterfejsu, podaj AbstractMessagerównież. Klasy abstrakcyjne nie powinny zastępować interfejsów, nigdy nie próbowałem tego sugerować.
mat b
2
Zrozumiano, Matt. Nie kłóciłem się z tobą, bardziej wskazując, że to nie jest całkowite zastąpienie tego, czego chce opera.
CPerkins
76

Problem, który pojawia się, gdy zezwalasz na konstruktory w interfejsach, wynika z możliwości zaimplementowania kilku interfejsów w tym samym czasie. Gdy klasa implementuje kilka interfejsów, które definiują różne konstruktory, klasa musiałaby zaimplementować kilka konstruktorów, z których każdy spełnia tylko jeden interfejs, ale nie obsługuje innych. Niemożliwe będzie utworzenie obiektu wywołującego każdy z tych konstruktorów.

Lub w kodzie:

interface Named { Named(String name); }
interface HasList { HasList(List list); }

class A implements Named, HasList {

  /** implements Named constructor.
   * This constructor should not be used from outside, 
   * because List parameter is missing
   */
  public A(String name)  { 
    ...
  }

  /** implements HasList constructor.
   * This constructor should not be used from outside, 
   * because String parameter is missing
   */
  public A(List list) {
    ...
  }

  /** This is the constructor that we would actually 
   * need to satisfy both interfaces at the same time
   */ 
  public A(String name, List list) {
    this(name);
    // the next line is illegal; you can only call one other super constructor
    this(list); 
  }
}
daniel kullmann
źródło
1
Czy język nie mógł tego zrobić, pozwalając na takie rzeczy, jakclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako
1
Najbardziej użytecznym znaczeniem dla „konstruktora w interfejsie”, jeśli jest to dozwolone, byłoby new Set<Fnord>()interpretowanie go jako „Daj mi coś, czego mogę użyć jako Set<Fnord>”; jeśli autor Set<T>zamierzał HashSet<T>być implementacją do rzeczy, które nie mają szczególnej potrzeby czegoś innego, interfejs mógłby wtedy zdefiniować new Set<Fnord>()jako synonim new HashSet<Fnord>(). Implementacja wielu interfejsów przez klasę nie stanowiłaby żadnego problemu, ponieważ new InterfaceName()po prostu skonstruowałaby klasę wyznaczoną przez interfejs .
supercat,
Kontrargument: Twój A(String,List)konstruktor może być wyznaczonym konstruktorem A(String)i A(List)może być drugorzędnym konstruktorem, który go wywołuje. Twój kod nie jest kontrprzykładem, tylko kiepskim.
Ben Leggiero,
Dlaczego miałbyś wywoływać wszystkie konstruktory w implementacji ?! Tak, jeśli implementuje więcej interfejsów z ctor, jeden ze stringiem i jeden z int, potrzebuje tych dwóch ctorów - ale można użyć jednego z nich lub. Jeśli to nie ma zastosowania, klasa po prostu nie implementuje obu interfejsów. Więc co!? (są jednak inne powody, dla których nie ma ctora w interfejsach).
kai
@kai Nie, podczas tworzenia instancji musi wywołać oba konstruktory interfejsu. Innymi słowy: w moim przykładzie instancja ma zarówno nazwę, jak i listę, więc każda instancja musi utworzyć instancję zarówno z nazwą, jak i z listą.
daniel kullmann
13

Interfejs definiuje kontrakt na API, czyli zestaw metod, na które zgadzają się zarówno implementujący jak i użytkownik API. Interfejs nie ma implementacji instancji, dlatego nie ma konstruktora.

Opisany przypadek użycia jest podobny do klasy abstrakcyjnej, w której konstruktor wywołuje metodę metody abstrakcyjnej zaimplementowaną w klasie potomnej.

Nieodłącznym problemem jest tutaj to, że podczas wykonywania konstruktora podstawowego obiekt potomny nie jest jeszcze skonstruowany, a zatem znajduje się w nieprzewidywalnym stanie.

Podsumowując: czy prosi się o kłopoty, gdy wywołujesz przeciążone metody z konstruktorów nadrzędnych, aby zacytować mindprod :

Ogólnie rzecz biorąc, należy unikać wywoływania w konstruktorze jakichkolwiek metod innych niż końcowe. Problem polega na tym, że inicjalizacja instancji / inicjalizacja zmiennej w klasie pochodnej jest wykonywana po konstruktorze klasy bazowej.

rsp
źródło
6

Obejściem, które możesz wypróbować, jest zdefiniowanie getInstance()metody w interfejsie, aby osoba wdrażająca była świadoma, jakie parametry muszą być obsługiwane. Nie jest tak solidna jak klasa abstrakcyjna, ale zapewnia większą elastyczność jako interfejs.

Jednak to obejście wymaga użycia programu getInstance()do utworzenia wystąpienia wszystkich obiektów tego interfejsu.

Na przykład

public interface Module {
    Module getInstance(Receiver receiver);
}
L Appro
źródło
5

W interfejsie są tylko pola statyczne, które nie wymagają inicjalizacji podczas tworzenia obiektu w podklasie, a metoda interfejsu musi zapewniać rzeczywistą implementację w podklasie, więc nie ma potrzeby konstruktora w interfejsie.

Drugi powód - podczas tworzenia obiektu podklasy wywoływany jest konstruktor nadrzędny, ale jeśli będzie zaimplementowanych więcej niż jeden interfejs, to podczas wywołania konstruktora interfejsu wystąpi konflikt dotyczący tego, który konstruktor interfejsu wywoła pierwszy

Satyajit Gami
źródło
3

Jeśli chcesz mieć pewność, że każda implementacja interfejsu zawiera określone pole, wystarczy dodać do interfejsu funkcję pobierającą dla tego pola :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • nie zepsuje hermetyzacji
  • poinformuje wszystkich, którzy używają twojego interfejsu, że Receiverobiekt musi zostać w jakiś sposób przekazany do klasy (przez konstruktora lub ustawiającego)
Denys Vasylenko
źródło
2

Zależności, do których nie istnieją odniesienia w metodach interfejsów, należy traktować jako szczegóły implementacji, a nie coś, co wymusza interfejs. Oczywiście mogą istnieć wyjątki, ale z reguły należy zdefiniować interfejs zgodnie z oczekiwanym zachowaniem. Stan wewnętrzny danej implementacji nie powinien być problemem projektowym interfejsu.

Yishai
źródło
1

Zobacz to pytanie, aby dowiedzieć się, dlaczego (zaczerpnięte z komentarzy).

Jeśli naprawdę potrzebujesz zrobić coś takiego, możesz potrzebować abstrakcyjnej klasy bazowej zamiast interfejsu.

JSB ձոգչ
źródło
1

Dzieje się tak, ponieważ interfejsy nie pozwalają na zdefiniowanie w niej treści metody. Ale powinniśmy zdefiniować konstruktor w tej samej klasie, co interfejsy mają domyślnie abstrakcyjny modyfikator dla wszystkich metod do zdefiniowania. Dlatego nie możemy zdefiniować konstruktora w interfejsach.

Aasif Ali
źródło
0

Oto przykład użycia tego Technic. W tym konkretnym przykładzie kod wywołuje Firebase przy użyciu makiety MyCompletionListener, czyli interfejsu zamaskowanego jako klasa abstrakcyjna, interfejsu z konstruktorem

private interface Listener {
    void onComplete(databaseError, databaseReference);
}

public abstract class MyCompletionListener implements Listener{
    String id;
    String name;
    public MyCompletionListener(String id, String name) {
        this.id = id;
        this.name = name;
    }
}

private void removeUserPresenceOnCurrentItem() {
    mFirebase.removeValue(child("some_key"), new MyCompletionListener(UUID.randomUUID().toString(), "removeUserPresenceOnCurrentItem") {
        @Override
        public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {

        }
    });
    }
}

@Override
public void removeValue(DatabaseReference ref, final MyCompletionListener var1) {
    CompletionListener cListener = new CompletionListener() {
                @Override
                public void onComplete(DatabaseError databaseError, DatabaseReference databaseReference) {
                    if (var1 != null){
                        System.out.println("Im back and my id is: " var1.is + " and my name is: " var1.name);
                        var1.onComplete(databaseError, databaseReference);
                    }
                }
            };
    ref.removeValue(cListener);
}
ExocetKid
źródło
Jak używać privatemodyfikatora dostępu z interface?
Rudra
0

Generalnie konstruktory służą do inicjowania niestatycznych elementów członkowskich określonej klasy w odniesieniu do obiektu.

Nie ma tworzenia obiektów dla interfejsu, ponieważ istnieją tylko zadeklarowane metody, ale nie są zdefiniowane. Dlaczego nie możemy utworzyć obiektu zgodnie z zadeklarowanymi metodami, tworzenie obiektu jest niczym innym, jak przydzieleniem części pamięci (w pamięci sterty) dla niestatycznych elementów członkowskich.

JVM utworzy pamięć dla członków, które są w pełni rozwinięte i gotowe do użycia, na podstawie których JVM oblicza, ile pamięci jest dla nich wymagane i tworzy pamięć.

W przypadku zadeklarowanych metod JVM nie jest w stanie obliczyć ilości pamięci wymaganej dla tych zadeklarowanych metod, ponieważ implementacja będzie w przyszłości, co nie zostanie wykonane do tego czasu. więc tworzenie obiektów nie jest możliwe dla interfejsu.

wniosek:

bez tworzenia obiektu nie ma szans na zainicjowanie niestatycznych składowych za pomocą konstruktora, dlatego konstruktor nie jest dozwolony wewnątrz interfejsu (ponieważ nie ma użycia konstruktora wewnątrz interfejsu)

Sai Kumar
źródło