Warum sollte in Java eine statisch verschachtelte Schnittstelle verwendet werden?

235

Ich habe gerade eine statisch verschachtelte Schnittstelle in unserer Codebasis gefunden.

class Foo {
    public static interface Bar {
        /* snip */
    }
    /* snip */
}

Ich habe das noch nie gesehen. Der ursprüngliche Entwickler ist unerreichbar. Deshalb muss ich SO fragen:

Welche Semantik steckt hinter einer statischen Schnittstelle? Was würde sich ändern, wenn ich das entferne static? Warum sollte jemand das tun?

Mo.
quelle
2
Dies ist keine innere Schnittstelle, sondern eine verschachtelte Schnittstelle. Inner hat in Java eine bestimmte Bedeutung.
Marquis von Lorne

Antworten:

293

Das statische Schlüsselwort im obigen Beispiel ist redundant (eine verschachtelte Schnittstelle ist automatisch "statisch") und kann ohne Auswirkung auf die Semantik entfernt werden. Ich würde empfehlen, es zu entfernen. Gleiches gilt für "public" für Schnittstellenmethoden und "public final" für Schnittstellenfelder - die Modifikatoren sind redundant und fügen dem Quellcode nur Unordnung hinzu.

In beiden Fällen deklariert der Entwickler einfach eine Schnittstelle mit dem Namen Foo.Bar. Es gibt keine weitere Zuordnung zur einschließenden Klasse, außer dass der Code, der nicht auf Foo zugreifen kann, auch nicht auf Foo.Bar zugreifen kann. (Vom Quellcode - Bytecode oder Reflection können auf Foo.Bar zugreifen, auch wenn Foo paketprivat ist!)

Es ist akzeptabel, eine verschachtelte Schnittstelle auf diese Weise zu erstellen, wenn Sie erwarten, dass sie nur von der äußeren Klasse verwendet wird, damit Sie keinen neuen Namen der obersten Ebene erstellen. Beispielsweise:

public class Foo {
    public interface Bar {
        void callback();
    }
    public static void registerCallback(Bar bar) {...}
}
// ...elsewhere...
Foo.registerCallback(new Foo.Bar() {
    public void callback() {...}
});
Jesse Glick
quelle
1
In Jesse Glicks Antwort, was dies bedeutet: (Vom Quellcode - Bytecode oder Reflektion kann auf Foo.Bar zugegriffen werden, selbst wenn Foo paketprivat ist!).
Vasu
Auf private Kaillash-Methoden kann durch Relfektion (im Reflect-Paket) und durch direkten Zugriff auf den Bytecode der generierten .class-Dateien zugegriffen werden.
Gmoore
2
Mit "Bytecode ... kann auf Foo.Bar zugreifen" meine ich, dass eine kompilierte Klasse, die auf Foo.Bar verweist, geladen und ausgeführt werden kann, auch wenn sie nicht auf Foo verweisen kann. Dies kann vorkommen, wenn die Klasse zu einem früheren Zeitpunkt kompiliert wurde, als Foo öffentlich war, oder wenn die Klasse von Hand zusammengestellt oder aus einer Nicht-Java-Sprache usw. kompiliert wurde. Der Java-Compiler überprüft jedoch den Zugriffsmodifikator für die einschließende Klasse selbst wenn der resultierende Bytecode nicht auf diese einschließende Klasse verweisen würde.
Jesse Glick
@Jesse Kann auf eine private statische Klasse in einer privaten Klasse der obersten Ebene durch Reflektion zugegriffen werden?
Pacerier
1
@ Pacerier: nein. Genauer gesagt können Sie die Klasse laden und ihre Mitglieder überprüfen, sie jedoch nicht instanziieren oder Methoden aufrufen, ohne setAccessible (true) zu verwenden. Mit anderen Worten, es ist durch Reflexion sichtbar, aber nicht zugänglich. Wenn die verschachtelte statische Klasse jedoch öffentlich ist, kann standardmäßig über Reflektion auf sie zugegriffen werden, obwohl sie statisch nicht zugänglich ist (während der Kompilierung).
Jesse Glick
72

Die Frage wurde beantwortet, aber ein guter Grund für die Verwendung einer verschachtelten Schnittstelle ist, dass ihre Funktion in direktem Zusammenhang mit der Klasse steht, in der sie sich befindet. Ein gutes Beispiel hierfür ist a Listener. Wenn Sie eine Klasse hatten Foound wollten, dass andere Klassen auf Ereignisse warten können, könnten Sie eine Schnittstelle mit dem Namen deklarieren FooListener, was in Ordnung ist, aber es wäre wahrscheinlich klarer, eine verschachtelte Schnittstelle zu deklarieren und diese anderen Klassen implementieren zu lassen Foo.Listener( eine verschachtelte Klasse Foo.Eventist damit nicht schlecht).

ColinD
quelle
11
Ein typisches Beispiel ist java.util.Map.Entry(eine Schnittstelle, die in einer anderen Schnittstelle verschachtelt ist).
Paŭlo Ebermann
4
Ich weiß, dass dies ein altes Thema ist, aber ich würde es vorziehen, wenn sich die äußere Klasse in einem eigenen Paket befindet und alle zusätzlichen Schnittstellen (z. B. Map.Entry) oder Klassen ebenfalls in diesem Paket enthalten sind. Ich sage das, weil ich meine Klassen gerne kurz und auf den Punkt halten möchte. Ein Leser kann auch sehen, welche anderen Entitäten mit einer Klasse verbunden sind, indem er sich die Klassen im Paket ansieht. Ich hätte wahrscheinlich ein Paket java.collections.mapfür Karten. Hier geht es um OO und Modularität. java.utilhat zu viel drin. utilist wie common- ein Geruch IMO
David Kerr
1
@ Shaggy: hat java.utilsicher zu viel drin. Trotzdem denke ich nicht, dass es auch ideal ist, es in so feinkörnige Pakete zu zerlegen, wie Sie es vorschlagen.
ColinD
1
@DavidKerr Angenommen, Sie rufen diese Schnittstelle java.util.MapEntryaußerhalb ihres eigenen Pakets auf. Erster Reflex: Welche Schnittstelle bezieht sich auf diese Klasse? Ich schaue in die Klasse. Sofern javadoc nicht auf diese Schnittstelle verweist, habe ich keine Ahnung von ihrer Beziehung. Außerdem sehen sich die Leute das Importpaket nicht an. Jeder hat seine eigene Meinung. Niemand hat Recht, niemand liegt falsch
Raymond Chenon
@RaymondChenon Teil meiner Aussage war es, Map und die zugehörigen Klassen, z. B. Map.Entry, in ein separates Paket zu packen, z. B. java.collections.map. Auf diese Weise befindet sich der gesamte Code für die Karte an einem einzigen Ort (dem Paket) und die Kartenklasse der obersten Ebene wird nicht belastet. Ich würde vielleicht die zugehörigen Implementierungen (HashMap usw.) in dasselbe Paket packen.
David Kerr
14

Mitgliedsschnittstellen sind implizit statisch. Der statische Modifikator in Ihrem Beispiel kann entfernt werden, ohne die Semantik des Codes zu ändern. Siehe auch die Java-Sprachspezifikation 8.5.1. Statische Elementtypdeklarationen

Bas Leijdekkers
quelle
Dies ist keine 'innere Schnittstelle': Es ist eine verschachtelte Schnittstelle. Inner hat in Java eine bestimmte Bedeutung.
Marquis von Lorne
@EJP, ich habe verschiedene Blogs mit austauschbaren Begriffen gelesen. Existiert eine innere Schnittstelle? Wie unterscheiden sie sich von verschachtelten Schnittstellen?
Nummer 945
@BreakingBenjamin, verschachtelte Schnittstelle bedeutet statisch. Es gibt eine innere Klasse und eine verschachtelte Klasse, aber keine innere Schnittstelle. Selbst wenn ja, sollte es als verschachtelte Schnittstelle aufgerufen werden. Inner - nicht statisch und verschachtelt ist statisch.
Sundar Rajan
9

Eine innere Schnittstelle muss statisch sein, um darauf zugreifen zu können. Die Schnittstelle ist nicht mit Instanzen der Klasse verknüpft, sondern mit der Klasse selbst, sodass auf sie Foo.Barwie folgt zugegriffen werden kann:

public class Baz implements Foo.Bar {
   ...
}

In den meisten Fällen unterscheidet sich dies nicht von einer statischen inneren Klasse.

Clinton N. Dreisbach
quelle
35
Eine verschachtelte Schnittstelle ist automatisch statisch, unabhängig davon, ob das Schlüsselwort geschrieben wird oder nicht.
Paŭlo Ebermann
3
Wir brauchen wirklich eine Möglichkeit für die Community, um eine andere Antwort zu akzeptieren: stackoverflow.com/a/74400/632951
Pacerier
2
'Static Inner' ist ein Widerspruch .
Marquis von Lorne
@ ClintonN.Dreisbach Kannst du erklären, was mit weiter gemeint ist The interface isn't associated with instances of the class, but with the class itself, ich habe es nicht verstanden
Kasun Siyambalapitiya
6

Jesses Antwort ist nah, aber ich denke, dass es einen besseren Code gibt, um zu demonstrieren, warum eine innere Schnittstelle nützlich sein kann. Schauen Sie sich den folgenden Code an, bevor Sie weiterlesen. Können Sie herausfinden, warum die innere Schnittstelle nützlich ist? Die Antwort ist, dass die Klasse DoSomethingAlready mit instanziiert werden kann, jeder Klasse kann , die A und C implementiert. nicht nur die konkrete Klasse Zoo. Dies kann natürlich auch erreicht werden, wenn AC nicht innerlich ist, aber stellen Sie sich vor, Sie verketten längere Namen (nicht nur A und C) und tun dies für andere Kombinationen (z. B. A und B, C und B usw.) und Sie leicht sehen, wie die Dinge außer Kontrolle geraten. Ganz zu schweigen davon, dass Personen, die Ihren Quellbaum überprüfen, von Schnittstellen überwältigt werden, die nur in einer Klasse von Bedeutung sind.Eine innere Schnittstelle ermöglicht die Erstellung benutzerdefinierter Typen und verbessert deren Kapselung .

class ConcreteA implements A {
 :
}

class ConcreteB implements B {
 :
}

class ConcreteC implements C {
 :
}

class Zoo implements A, C {
 :
}

class DoSomethingAlready {
  interface AC extends A, C { }

  private final AC ac;

  DoSomethingAlready(AC ac) {
    this.ac = ac;
  }
}
user1982892
quelle
2
Das ist schwachsinn. Die Klasse Zooist nicht implementieren die Schnittstelle ACalso Instanzen Zookönnen nicht an den Konstruktor übergeben werden , von DoSomethingAlreadydenen erwartet AC. Die Tatsache, dass ACbeide Aund erweitert werden C, bedeutet nicht, dass Klassen implementieren Aund auf Cmagische Weise auch implementieren AC.
Holger
3

Um Ihre Frage sehr direkt zu beantworten, schauen Sie sich Map.Entry an.

Map.Entry

Auch dies kann nützlich sein

Blogeintrag für statisch verschachtelte Inerfaces

Henry B.
quelle
4
Dies ist ein Beispiel, aber keine wirkliche Antwort.
Paŭlo Ebermann
Ich benutze Map.Entry, um häufig "Pair" -Objekte zu erstellen. Es ist ausgesetzt. Die Implementierung von Pair hat zwei Denkrichtungen, aber das ist hier nicht der Punkt. Map.Entry mag inner sein, aber ich benutze es draußen.
Ravindranath Akila
0

Normalerweise sehe ich statische innere Klassen. Statische innere Klassen können nicht auf die enthaltenen Klassen verweisen, wohingegen nicht statische Klassen dies können. Es sei denn, Sie stoßen auf Paketkollisionen (es gibt bereits eine Schnittstelle namens Bar im selben Paket wie Foo). Ich denke, ich würde es zu einer eigenen Datei machen. Es könnte auch eine Entwurfsentscheidung sein, die logische Verbindung zwischen Foo und Bar durchzusetzen. Vielleicht wollte der Autor, dass Bar nur mit Foo verwendet wird (obwohl eine statische innere Schnittstelle dies nicht erzwingt, sondern nur eine logische Verbindung).

Basszero
quelle
'Static Inner' ist ein Widerspruch. Verschachtelte Klassen sind entweder statisch oder innerlich.
Marquis von Lorne
0

Wenn Sie die Klasse Foo in die Schnittstelle Foo ändern, ist das Schlüsselwort "public" im obigen Beispiel ebenfalls redundant, da

Die in einer anderen Schnittstelle definierte Schnittstelle ist implizit öffentlich statisch.

Danylo Volokh
quelle
Gab keine Antwort auf die Frage
Raining
0

1998 schlug Philip Wadler einen Unterschied zwischen statischen und nicht statischen Schnittstellen vor.

Soweit ich sehen kann, besteht der einzige Unterschied darin, eine Schnittstelle nicht statisch zu machen, darin, dass sie jetzt nicht statische innere Klassen enthalten kann. Die Änderung würde also keine vorhandenen Java-Programme ungültig machen.

Zum Beispiel schlug er eine Lösung für das Ausdrucksproblem vor , bei der es sich um die Nichtübereinstimmung zwischen dem Ausdruck "wie viel kann Ihre Sprache ausdrücken" einerseits und dem Ausdruck "die Begriffe, die Sie in Ihrer Sprache darstellen möchten" andererseits handelt .

Ein Beispiel für den Unterschied zwischen statischen und nicht statischen verschachtelten Schnittstellen ist in seinem Beispielcode zu sehen :

// This code does NOT compile
class LangF<This extends LangF<This>> {
    interface Visitor<R> {
        public R forNum(int n);
    }

    interface Exp {
        // since Exp is non-static, it can refer to the type bound to This
        public <R> R visit(This.Visitor<R> v);
    }
}

Sein Vorschlag hat es in Java 1.5.0 nie geschafft. Daher sind alle anderen Antworten richtig: Es gibt keinen Unterschied zu statischen und nicht statischen verschachtelten Schnittstellen.

Pindatjuh
quelle
Es sollte beachtet werden, dass sich all dies auf GJ bezieht, das ein sehr früher Prozessor für Generika in Java war.
Marquis von Lorne
-1

In Java ermöglicht die statische Schnittstelle / Klasse, dass die Schnittstelle / Klasse wie eine Klasse der obersten Ebene verwendet wird, dh von anderen Klassen deklariert werden kann. Sie können also Folgendes tun:

class Bob
{
  void FuncA ()
  {
    Foo.Bar foobar;
  }
}

Ohne die statische Aufladung würde das oben Gesagte nicht kompiliert werden können. Dies hat den Vorteil, dass Sie keine neue Quelldatei benötigen, um die Schnittstelle zu deklarieren. Es ordnet die Schnittstellenleiste auch visuell der Klasse Foo zu, da Sie Foo.Bar schreiben müssen, und impliziert, dass die Foo-Klasse etwas mit Instanzen von Foo.Bar tut.

Eine Beschreibung der Klassentypen in Java .

Skizz
quelle
Ohne die statische es tut Kompilierung. Die 'statische' ist redundant.
Marquis von Lorne
@EJP: Gemäß dem Artikel, auf den ich verlinkt habe, kann die innere Klasse ohne die Statik nur von der besitzenden Klasse und nicht von irgendetwas außerhalb der schuldenden Klasse verwendet werden. Die Statik macht es zu einer verschachtelten Klasse der obersten Ebene und kann von Objekten außerhalb des Bereichs der besitzenden Klasse verwendet werden (zumindest laut Artikel). Die Statik ist nicht vollständig redundant und wirkt sich auf den Umfang der inneren Klasse aus. Dies gilt für Klassen. Schnittstellen können sich immer wie Objekte der obersten Ebene verhalten (zur Überprüfung muss die Sprachspezifikation gelesen werden). Vielleicht hätte ich die Schnittstellen- und Klassenteile meiner Antwort (meine schlechte) trennen sollen.
Skizz
-6

Statisch bedeutet, dass jeder Klassenteil des Pakets (Projekts) ohne Verwendung eines Zeigers darauf zugreifen kann. Dies kann je nach Situation nützlich oder hinderlich sein.

Das perfekte Beispiel für die Nützlichkeit "statischer" Methoden ist die Math-Klasse. Alle Methoden in Mathe sind statisch. Dies bedeutet, dass Sie nicht aus dem Weg gehen, eine neue Instanz erstellen, Variablen deklarieren und in noch mehr Variablen speichern müssen. Sie können einfach Ihre Daten eingeben und ein Ergebnis erhalten.

Statik ist nicht immer so nützlich. Wenn Sie beispielsweise einen Fallvergleich durchführen, möchten Sie Daten möglicherweise auf verschiedene Arten speichern. Sie können nicht drei statische Methoden mit identischen Signaturen erstellen. Sie benötigen 3 verschiedene Instanzen, nicht statisch, und dann können Sie vergleichen, denn wenn es statisch ist, ändern sich die Daten nicht zusammen mit der Eingabe.

Statische Methoden eignen sich für einmalige Rückgaben und schnelle Berechnungen oder einfach zu ermittelnde Daten.

Vordreller
quelle
1
Ich weiß, was statisch bedeutet. Ich habe es noch nie zuvor auf einer Benutzeroberfläche gesehen. Daher ist Ihre Frage nicht zum Thema - sorry
Mo.
1
Ich denke, seine Antwort ist nicht zum Thema.
Koray Tugay