Konstruktor in einer Schnittstelle?

148

Ich weiß, dass es nicht möglich ist, einen Konstruktor in einer Schnittstelle zu definieren. Aber ich frage mich warum, weil ich denke, dass es sehr nützlich sein könnte.

Sie können also sicher sein, dass einige Felder in einer Klasse für jede Implementierung dieser Schnittstelle definiert sind.

Betrachten Sie beispielsweise die folgende Nachrichtenklasse:

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
   }
}

Wenn Sie eine Schnittstelle für diese Klasse definieren, damit ich mehr Klassen haben kann, die die Nachrichtenschnittstelle implementieren, kann ich nur die Sendemethode und nicht den Konstruktor definieren. Wie kann ich also sicherstellen, dass für jede Implementierung dieser Klasse wirklich ein Empfänger festgelegt ist? Wenn ich eine Methode wie diese verwende, setReceiver(String receiver)kann ich nicht sicher sein, ob diese Methode wirklich aufgerufen wird. Im Konstruktor konnte ich es sicherstellen.

Daniel Kullmann
quelle
2
Sie sagen: "Im Konstruktor könnte ich sicherstellen, dass [jede Implementierung dieser Klasse wirklich einen Empfängersatz hat]." Aber nein, das konnte man unmöglich machen. Vorausgesetzt, es wäre möglich, einen solchen Konstruktor zu definieren, wäre der Parameter nur ein starker Hinweis für Ihre Implementierer - aber sie könnten ihn einfach ignorieren, wenn sie wollten.
Julien Silland
3
@mattb Umm, das ist eine andere Sprache.
Yesennes

Antworten:

129

Nehmen Sie einige der Dinge, die Sie beschrieben haben:

"Sie können also sicher sein, dass einige Felder in einer Klasse für jede Implementierung dieser Schnittstelle definiert sind."

"Wenn Sie eine Schnittstelle für diese Klasse definieren, damit ich mehr Klassen haben kann, die die Nachrichtenschnittstelle implementieren, kann ich nur die Sendemethode und nicht den Konstruktor definieren."

... diese Anforderungen sind genau das, wofür abstrakte Klassen sind.

matt b
quelle
Beachten Sie jedoch, dass der von @Sebi beschriebene Anwendungsfall (Aufrufen überladener Methoden von übergeordneten Konstruktoren) eine schlechte Idee ist, wie in meiner Antwort erläutert.
rsp
44
Matt, das ist eindeutig richtig, aber abstrakte Klassen leiden unter der Einschränkung der Einzelvererbung, die dazu führt, dass die Leute andere Möglichkeiten zur Angabe von Hierarchien betrachten.
CPerkins
6
Dies ist wahr und kann Sebis unmittelbares Problem lösen. Ein Grund für die Verwendung von Schnittstellen in Java ist jedoch, dass Sie keine Mehrfachvererbung haben können. In einem Fall, in dem ich mein "Ding" nicht zu einer abstrakten Klasse machen kann, weil ich von etwas anderem erben muss, bleibt das Problem bestehen. Nicht dass ich behaupte, eine Lösung zu haben.
Jay
7
@CPerkins Während dies wahr ist, schlage ich nicht vor, dass die einfache Verwendung einer abstrakten Klasse Sebis Anwendungsfall lösen wird. Wenn überhaupt, ist es am besten, eine MessageSchnittstelle zu deklarieren , die die send()Methode definiert , und wenn Sebi eine "Basis" -Klasse für Implementierungen der MessageSchnittstelle bereitstellen möchte , dann geben Sie auch eine AbstractMessagean. Abstrakte Klassen sollten nicht an die Stelle von Schnittstellen treten, ich habe nie versucht, dies vorzuschlagen.
Matt B
2
Verstanden, Matt. Ich habe nicht mit Ihnen gestritten und eher darauf hingewiesen, dass dies kein vollständiger Ersatz für das ist, was die Operation will.
CPerkins
76

Ein Problem, das auftritt, wenn Sie Konstruktoren in Schnittstellen zulassen, besteht in der Möglichkeit, mehrere Schnittstellen gleichzeitig zu implementieren. Wenn eine Klasse mehrere Schnittstellen implementiert, die unterschiedliche Konstruktoren definieren, müsste die Klasse mehrere Konstruktoren implementieren, von denen jeder nur eine Schnittstelle erfüllt, die anderen jedoch nicht. Es wird unmöglich sein, ein Objekt zu konstruieren, das jeden dieser Konstruktoren aufruft.

Oder im Code:

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
quelle
1
Könnte die Sprache es nicht getan haben, indem sie Dinge wieclass A implements Named, HashList { A(){HashList(new list()); Named("name");} }
mako
1
Die nützlichste Bedeutung für einen "Konstruktor in einer Schnittstelle" wäre, wenn dies zulässig wäre, wenn new Set<Fnord>()dies so interpretiert werden könnte, dass es "Gib mir etwas, als das ich verwenden kann Set<Fnord>" bedeutet. Wenn der Autor Set<T>beabsichtigt HashSet<T>, die Anlaufstelle für Dinge zu sein, für die kein besonderer Bedarf an etwas anderem besteht, kann die Schnittstelle new Set<Fnord>()als Synonym für definiert werden new HashSet<Fnord>(). Die Implementierung mehrerer Schnittstellen durch eine Klasse wäre kein Problem, da new InterfaceName()einfach eine von der Schnittstelle festgelegte Klasse erstellt würde .
Supercat
Gegenargument: Ihr A(String,List)Konstruktor könnte der designierte Konstruktor sein, und A(String)und A(List)sekundären diejenigen sein könnten , die es nennen. Ihr Code ist kein Gegenbeispiel, nur ein schlechtes.
Ben Leggiero
Warum würden Sie alle Konstruktoren in einer Implementierung aufrufen ?! Ja, wenn es mehr Schnittstellen mit ctor implementiert, eine mit einem String und eine mit einem int, benötigt es diese zwei ctors - aber entweder oder kann verwendet werden. Wenn dies nicht zutrifft, implementiert die Klasse einfach nicht beide Schnittstellen. Na und!? (Es gibt aber auch andere Gründe, warum ctor nicht in Interfaces vorhanden ist).
Kai
@kai Nein, beim Erstellen einer Instanz müssen beide Schnittstellenkonstruktoren aufgerufen werden. Mit anderen Worten: In meinem Beispiel hat die Instanz sowohl einen Namen als auch eine Liste, sodass jede Instanz sowohl den Namen als auch die Liste instanziieren muss.
Daniel Kullmann
13

Eine Schnittstelle definiert einen Vertrag für eine API, dh eine Reihe von Methoden, auf die sich sowohl der Implementierer als auch der Benutzer der API einigen. Eine Schnittstelle hat keine instanziierte Implementierung, daher keinen Konstruktor.

Der von Ihnen beschriebene Anwendungsfall ähnelt einer abstrakten Klasse, in der der Konstruktor eine Methode einer abstrakten Methode aufruft, die in einer untergeordneten Klasse implementiert ist.

Das inhärente Problem hierbei ist, dass während der Ausführung des Basiskonstruktors das untergeordnete Objekt noch nicht erstellt wird und sich daher in einem unvorhersehbaren Zustand befindet.

Zusammenfassend: Ist es problematisch, wenn Sie überladene Methoden von übergeordneten Konstruktoren aufrufen , um mindprod zu zitieren :

Im Allgemeinen müssen Sie vermeiden, nicht endgültige Methoden in einem Konstruktor aufzurufen. Das Problem ist, dass Instanzinitialisierer / Variableninitialisierung in der abgeleiteten Klasse nach dem Konstruktor der Basisklasse ausgeführt werden.

rsp
quelle
6

Sie können versuchen, eine getInstance()Methode in Ihrer Schnittstelle zu definieren, damit der Implementierer weiß, welche Parameter behandelt werden müssen. Es ist nicht so solide wie eine abstrakte Klasse, bietet aber mehr Flexibilität als eine Schnittstelle.

Für diese Problemumgehung müssen Sie jedoch die verwenden getInstance(), um alle Objekte dieser Schnittstelle zu instanziieren.

Z.B

public interface Module {
    Module getInstance(Receiver receiver);
}
Lappro
quelle
5

Es gibt nur statische Felder in der Schnittstelle, die während der Objekterstellung in der Unterklasse nicht initialisiert werden müssen, und die Methode der Schnittstelle muss die tatsächliche Implementierung in der Unterklasse bereitstellen. Daher ist kein Konstruktor in der Schnittstelle erforderlich.

Zweiter Grund: Während der Objekterstellung der Unterklasse wird der übergeordnete Konstruktor aufgerufen. Wenn jedoch mehr als eine Schnittstelle implementiert wird, tritt beim Aufruf des Schnittstellenkonstruktors ein Konflikt darüber auf, welcher Konstruktor der Schnittstelle zuerst aufgerufen wird

Satyajit Gami
quelle
3

Wenn Sie sicherstellen möchten, dass jede Implementierung der Schnittstelle ein bestimmtes Feld enthält, müssen Sie lediglich den Getter für dieses Feld zu Ihrer Schnittstelle hinzufügen :

interface IMyMessage(){
    @NonNull String getReceiver();
}
  • Die Kapselung wird nicht unterbrochen
  • Jeder, der Ihre Schnittstelle verwendet, wird darüber informiert, dass das ReceiverObjekt auf irgendeine Weise an die Klasse übergeben werden muss (entweder vom Konstruktor oder vom Setter).
Denys Vasylenko
quelle
2

Abhängigkeiten, auf die in einer Schnittstellenmethode nicht verwiesen wird, sollten als Implementierungsdetails betrachtet werden, nicht als etwas, das die Schnittstelle erzwingt. Natürlich kann es Ausnahmen geben, aber in der Regel sollten Sie Ihre Schnittstelle so definieren, wie das Verhalten erwartet wird. Der interne Status einer bestimmten Implementierung sollte kein Entwurfsanliegen der Schnittstelle sein.

Yishai
quelle
1

In dieser Frage finden Sie das Warum (aus den Kommentaren entnommen).

Wenn Sie so etwas wirklich tun müssen, möchten Sie möglicherweise eine abstrakte Basisklasse anstelle einer Schnittstelle.

JSB ձոգչ
quelle
1

Dies liegt daran, dass Schnittstellen es nicht erlauben, den Methodenkörper darin zu definieren. Wir sollten jedoch den Konstruktor in derselben Klasse definieren müssen, wie Schnittstellen standardmäßig den abstrakten Modifikator für alle zu definierenden Methoden haben. Deshalb können wir keinen Konstruktor in den Schnittstellen definieren.

Aasif Ali
quelle
0

Hier ist ein Beispiel mit dieser Technik. In diesem Beispiel ruft der Code Firebase mit einem Mock auf MyCompletionListener, bei dem es sich um eine als abstrakte Klasse maskierte Schnittstelle handelt, eine Schnittstelle mit einem Konstruktor

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
quelle
Wie können Sie den privateZugriffsmodifikator mit verwenden interface?
Rudra
0

Im Allgemeinen dienen Konstruktoren zum Initialisieren nicht statischer Elemente einer bestimmten Klasse in Bezug auf das Objekt.

Es gibt keine Objekterstellung für die Schnittstelle, da nur deklarierte Methoden, aber keine definierten Methoden vorhanden sind. Warum wir für deklarierte Methoden kein Objekt erstellen können, ist nichts anderes als das Zuweisen von Speicher (im Heapspeicher) für nicht statische Mitglieder.

JVM erstellt Speicher für Mitglieder, die vollständig entwickelt und einsatzbereit sind. Basierend auf diesen Mitgliedern berechnet JVM, wie viel Speicher für sie erforderlich ist, und erstellt Speicher.

Bei deklarierten Methoden kann JVM nicht berechnen, wie viel Speicher für diese deklarierten Methoden benötigt wird, da die Implementierung in Zukunft erfolgen wird, was zu diesem Zeitpunkt noch nicht erfolgt. Daher ist die Objekterstellung für die Schnittstelle nicht möglich.

Fazit:

Ohne Objekterstellung gibt es keine Möglichkeit, nicht statische Elemente über einen Konstruktor zu initialisieren. Aus diesem Grund ist der Konstruktor in einer Schnittstelle nicht zulässig (da der Konstruktor in einer Schnittstelle nicht verwendet wird).

Sai Kumar
quelle