Schnittstellen in Java

Java-Schnittstellen unterscheiden sich von Klassen, und es ist wichtig zu wissen, wie ihre speziellen Eigenschaften in Ihren Java-Programmen verwendet werden. In diesem Lernprogramm wird der Unterschied zwischen Klassen und Schnittstellen vorgestellt. Anschließend werden Sie durch Beispiele geführt, die zeigen, wie Java-Schnittstellen deklariert, implementiert und erweitert werden.

Sie erfahren auch, wie sich die Benutzeroberfläche in Java 8 mit zusätzlichen und statischen Methoden und in Java 9 mit den neuen privaten Methoden entwickelt hat. Diese Ergänzungen machen Schnittstellen für erfahrene Entwickler nützlicher. Leider verwischen sie auch die Grenzen zwischen Klassen und Schnittstellen, was die Schnittstellenprogrammierung für Java-Anfänger noch verwirrender macht.

download Code abrufen Laden Sie den Quellcode herunter, zum Beispiel Anwendungen in diesem Tutorial. Erstellt von Jeff Friesen für JavaWorld.

Was ist eine Java-Schnittstelle?

Eine Schnittstelle ist ein Punkt, an dem sich zwei Systeme treffen und interagieren. Sie können beispielsweise eine Verkaufsautomatenschnittstelle verwenden, um einen Artikel auszuwählen, dafür zu bezahlen und einen Lebensmittel- oder Getränkeartikel zu erhalten. Aus programmtechnischer Sicht befindet sich eine Schnittstelle zwischen Softwarekomponenten. Beachten Sie, dass sich zwischen dem externen Code, der die Methode aufruft, und dem Code innerhalb der Methode, die als Ergebnis des Aufrufs ausgeführt wird, eine Schnittstelle für den Methodenheader (Methodenname, Parameterliste usw.) befindet. Hier ist ein Beispiel:

System.out.println(average(10, 15)); double average(double x, double y) // interface between average(10, 15) call and return (x + y) / 2; { return (x + y) / 2; }

Was für Java-Anfänger oft verwirrend ist, ist, dass Klassen auch Schnittstellen haben. Wie ich in Java 101 erklärt habe: Klassen und Objekte in Java, ist die Schnittstelle der Teil der Klasse, auf den Code außerhalb zugreifen kann. Die Schnittstelle einer Klasse besteht aus einer Kombination von Methoden, Feldern, Konstruktoren und anderen Entitäten. Betrachten Sie Listing 1.

Listing 1. Die Account-Klasse und ihre Schnittstelle

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

Der Account(String name, long amount)Konstruktor und die void deposit(long amount), String getName(), long getAmount()und void setAmount(long amount)Methoden bilden die AccountKlasse Schnittstelle: sie zu externem Code zugänglich sind. Die Felder private String name;und private long amount;sind nicht zugänglich.

Weitere Informationen zu Java-Schnittstellen

Was können Sie mit Schnittstellen in Ihren Java-Programmen tun? Verschaffen Sie sich einen Überblick über Jeffs sechs Rollen der Java-Oberfläche.

Der Code einer Methode, der die Schnittstelle der Methode unterstützt, und der Teil einer Klasse, der die Schnittstelle der Klasse unterstützt (z. B. private Felder), werden als Implementierung der Methode oder Klasse bezeichnet . Eine Implementierung sollte vor externem Code verborgen sein, damit sie an sich ändernde Anforderungen angepasst werden kann.

Wenn Implementierungen verfügbar gemacht werden, können Abhängigkeiten zwischen Softwarekomponenten auftreten. Beispielsweise kann Methodencode auf externen Variablen beruhen und die Benutzer einer Klasse können von Feldern abhängig werden, die ausgeblendet werden sollten. Diese Kopplung kann zu Problemen führen, wenn sich Implementierungen entwickeln müssen (möglicherweise müssen exponierte Felder entfernt werden).

Java-Entwickler verwenden die Interface-Sprachfunktion, um Klassenschnittstellen zu abstrahieren und so Klassen von ihren Benutzern zu entkoppeln . Indem Sie sich auf Java-Schnittstellen anstelle von Klassen konzentrieren, können Sie die Anzahl der Verweise auf Klassennamen in Ihrem Quellcode minimieren. Dies erleichtert den Wechsel von einer Klasse zur anderen (möglicherweise zur Verbesserung der Leistung), wenn Ihre Software ausgereift ist. Hier ist ein Beispiel:

List names = new ArrayList() void print(List names) { // ... }

In diesem Beispiel wird ein namesFeld deklariert und initialisiert , in dem eine Liste von Zeichenfolgennamen gespeichert ist. Das Beispiel deklariert auch eine print()Methode zum Ausdrucken des Inhalts einer Liste von Zeichenfolgen, möglicherweise eine Zeichenfolge pro Zeile. Der Kürze halber habe ich die Implementierung der Methode weggelassen.

Listist eine Java-Schnittstelle, die eine sequentielle Sammlung von Objekten beschreibt. ArrayListist eine Klasse, die eine Array-basierte Implementierung der ListJava-Schnittstelle beschreibt. Eine neue Instanz der ArrayListKlasse wird abgerufen und der ListVariablen zugewiesen names. ( Listund ArrayListwerden im java.utilPaket der Standardklassenbibliothek gespeichert .)

Spitze Klammern und Generika

Die spitzen Klammern ( <und >) sind Teil des generischen Funktionsumfangs von Java. Sie geben an, dass nameseine Liste von Zeichenfolgen beschrieben wird (in der Liste können nur Zeichenfolgen gespeichert werden). Ich werde Generika in einem zukünftigen Java 101-Artikel vorstellen.

Wenn Clientcode mit interagiert names, werden die Methoden aufgerufen, die von deklariert und von Listimplementiert werden ArrayList. Der Client-Code interagiert nicht direkt mit ArrayList. Infolgedessen wird der Clientcode nicht unterbrochen, wenn eine andere Implementierungsklasse LinkedListerforderlich ist , z.

List names = new LinkedList() // ... void print(List names) { // ... }

Da der print()Methodenparametertyp lautet List, muss sich die Implementierung dieser Methode nicht ändern. Wenn der Typ jedoch gewesen wäre ArrayList, müsste der Typ in geändert werden LinkedList. Wenn beide Klassen ihre eigenen eindeutigen Methoden deklarieren, müssen Sie möglicherweise die print()Implementierung erheblich ändern .

Entkoppeln Listvon ArrayListund LinkedListermöglicht das Schreiben von Code, der gegen Änderungen bei der Klassenimplementierung immun ist. Durch die Verwendung von Java-Schnittstellen können Sie Probleme vermeiden, die durch die Verwendung von Implementierungsklassen entstehen können. Diese Entkopplung ist der Hauptgrund für die Verwendung von Java-Schnittstellen.

Java-Schnittstellen deklarieren

Sie deklarieren eine Schnittstelle, indem Sie sich an eine klassenähnliche Syntax halten, die aus einem Header gefolgt von einem Body besteht. Der Header besteht mindestens aus einem Schlüsselwort, interfacegefolgt von einem Namen, der die Schnittstelle identifiziert. Der Körper beginnt mit einer offenen Klammer und endet mit einer engen Klammer. Zwischen diesen Trennzeichen befinden sich Konstanten- und Methodenheaderdeklarationen:

interface identifier { // interface body }

Konventionell wird der erste Buchstabe des Namens einer Schnittstelle in Großbuchstaben und nachfolgende Buchstaben in Kleinbuchstaben geschrieben (z. B. Drawable). Wenn ein Name aus mehreren Wörtern besteht, wird der erste Buchstabe jedes Wortes in Großbuchstaben geschrieben (z. B. DrawableAndFillable). Diese Namenskonvention wird als CamelCasing bezeichnet.

Listing 2 deklariert eine Schnittstelle mit dem Namen Drawable.

Listing 2. Ein Beispiel für eine Java-Schnittstelle

interface Drawable { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; int WHITE = 5; void draw(int color); }

Schnittstellen in der Standardklassenbibliothek von Java

As a naming convention, many interfaces in Java's standard class library end with the able suffix. Examples include Callable, Cloneable, Comparable, Formattable, Iterable, Runnable, Serializable, and Transferable. The suffix isn't mandatory, however; the standard class library includes the interfaces CharSequence, ClipboardOwner, Collection, Executor, Future, Iterator, List, Map and many others.

Drawable declares five fields that identify color constants. This interface also declares the header for a draw() method that must be called with one of these constants to specify the color used to draw an outline. (Using integer constants isn't a good idea because any integer value could be passed to draw(). However, they suffice in a simple example.)

Field and method header defaults

Fields that are declared in an interface are implicitly public final static. An interface's method headers are implicitly public abstract.

Drawable identifies a reference type that specifies what to do (draw something) but not how to do it. Implementation details are consigned to classes that implement this interface. Instances of such classes are known as drawables because they know how to draw themselves.

Marker and tagging interfaces

An interface with an empty body is known as a marker interface or a tagging interface. The interface exists only to associate metadata with a class. For example, Cloneable (see Inheritance in Java, Part 2) implies that instances of its implementing class can be shallowly cloned. When Object's clone() method detects (via runtime type identification) that the calling instance's class implements Cloneable, it shallowly clones the object.

Implementing Java interfaces

A class implements an interface by appending Java's implements keyword followed by a comma-separated list of interface names to the class header, and by coding each interface method in the class. Listing 3 presents a class that implements Listing 2's Drawable interface.

Listing 3. Circle implementing the Drawable interface

class Circle implements Drawable { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public void draw(int color) { System.out.println("Circle drawn at (" + x + ", " + y + "), with radius " + radius + ", and color " + color); } double getRadius() { return radius; } double getX() { return x; } double getY() { return y; } }

Listing 3's Circle class describes a circle as a center point and a radius. As well as providing a constructor and suitable getter methods, Circle implements the Drawable interface by appending implements Drawable to the Circle header, and by overriding (as indicated by the @Override annotation) Drawable's draw() method header.

Listing 4 presents a second example: a Rectangle class that also implements Drawable.

Listing 4. Implementing the Drawable interface in a Rectangle context

class Rectangle implements Drawable { private double x1, y1, x2, y2; Rectangle(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } @Override public void draw(int color) { System.out.println("Rectangle drawn with upper-left corner at (" + x1 + ", " + y1 + ") and lower-right corner at (" + x2 + ", " + y2 + "), and color " + color); } double getX1() { return x1; } double getX2() { return x2; } double getY1() { return y1; } double getY2() { return y2; } }

Listing 4's Rectangle class describes a rectangle as a pair of points denoting the upper-left and lower-right corners of this shape. As with Circle, Rectangle provides a constructor and suitable getter methods, and also implements the Drawable interface.

Overriding interface method headers

The compiler reports an error when you attempt to compile a non-abstract class that includes an implements interface clause but doesn't override all of the interface's method headers.

An interface type's data values are the objects whose classes implement the interface and whose behaviors are those specified by the interface's method headers. This fact implies that you can assign an object's reference to a variable of the interface type, provided that the object's class implements the interface. Listing 5 demonstrates.

Listing 5. Aliasing Circle and Rectangle objects as Drawables

class Draw { public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Circle(10, 20, 15), new Circle(30, 20, 10), new Rectangle(5, 8, 8, 9) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); } }

Because Circle and Rectangle implement Drawable, Circle and Rectangle objects have Drawable type in addition to their class types. Therefore, it's legal to store each object's reference in an array of Drawables. A loop iterates over this array, invoking each Drawable object's draw() method to draw a circle or a rectangle.

Assuming that Listing 2 is stored in a Drawable.java source file, which is in the same directory as the Circle.java, Rectangle.java, and Draw.java source files (which respectively store Listing 3, Listing 4, and Listing 5), compile these source files via either of the following command lines:

javac Draw.java javac *.java

Run the Draw application as follows:

java Draw

You should observe the following output:

Circle drawn at (10.0, 20.0), with radius 15.0, and color 1 Circle drawn at (30.0, 20.0), with radius 10.0, and color 1 Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1

Note that you could also generate the same output by specifying the following main() method:

public static void main(String[] args) { Circle c = new Circle(10, 20, 15); c.draw(Drawable.RED); c = new Circle(30, 20, 10); c.draw(Drawable.RED); Rectangle r = new Rectangle(5, 8, 8, 9); r.draw(Drawable.RED); }

Wie Sie sehen, ist es mühsam, die draw()Methode jedes Objekts wiederholt aufzurufen . Außerdem wird dadurch der DrawKlassendatei zusätzlicher Bytecode hinzugefügt. Wenn Sie an Circleund Rectangleals Drawables denken , können Sie ein Array und eine einfache Schleife nutzen, um den Code zu vereinfachen. Dies ist ein zusätzlicher Vorteil des Entwurfs von Code, um Schnittstellen gegenüber Klassen zu bevorzugen.

Vorsicht!