Interfejsy w Javie

Interfejsy Java różnią się od klas i ważne jest, aby wiedzieć, jak używać ich specjalnych właściwości w programach Java. W tym samouczku przedstawiono różnice między klasami i interfejsami, a następnie przedstawiono przykłady demonstrujące, jak deklarować, implementować i rozszerzać interfejsy Java.

Dowiesz się również, jak interfejs ewoluował w Javie 8, po dodaniu domyślnych i statycznych metod oraz w Javie 9 z nowymi prywatnymi metodami. Te dodatki sprawiają, że interfejsy są bardziej przydatne dla doświadczonych programistów. Niestety, zacierają również granice między klasami i interfejsami, co sprawia, że ​​programowanie interfejsów jest jeszcze bardziej zagmatwane dla początkujących w Javie.

pobierz Pobierz kod Pobierz kod źródłowy na przykład aplikacje w tym samouczku. Stworzone przez Jeffa Friesena dla JavaWorld.

Co to jest interfejs Java?

Interfejs jest punkt, w którym dwa systemy spełniają i interakcji. Na przykład, możesz użyć interfejsu automatu sprzedającego, aby wybrać przedmiot, zapłacić za niego i otrzymać jedzenie lub napój. Z punktu widzenia programowania interfejs znajduje się między komponentami oprogramowania. Weź pod uwagę, że interfejs nagłówka metody (nazwa metody, lista parametrów itd.) Znajduje się między kodem zewnętrznym, który wywołuje metodę, a kodem w metodzie, która zostanie wykonana w wyniku wywołania. Oto przykład:

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

To, co często jest mylące dla początkujących w Javie, to fakt, że klasy mają również interfejsy. Jak wyjaśniłem w Java 101: Classes and objects in Java, interfejs jest częścią klasy, która jest dostępna dla kodu znajdującego się poza nią. Interfejs klasy składa się z kombinacji metod, pól, konstruktorów i innych jednostek. Rozważ listę 1.

Listing 1. Klasa konta i jej interfejs

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

Account(String name, long amount)Konstruktor i void deposit(long amount), String getName(), long getAmount(), i void setAmount(long amount)metody utworzenia przez Accountinterfejs klasa za: są one dostępne dla kodu zewnętrznego. private String name;I private long amount;pola są niedostępne.

Więcej o interfejsach Java

Co możesz zrobić z interfejsami w programach Java? Zapoznaj się z omówieniem sześciu ról Jeffa interfejsu Java.

Kod metody, która obsługuje interfejs metody, oraz ta część klasy, która obsługuje interfejs klasy (na przykład pola prywatne), jest nazywana implementacją metody lub klasy . Implementację należy ukryć przed zewnętrznym kodem, aby można było ją zmienić w celu spełnienia zmieniających się wymagań.

Kiedy implementacje są ujawniane, mogą pojawić się współzależności między komponentami oprogramowania. Na przykład kod metody może polegać na zmiennych zewnętrznych, a użytkownicy klasy mogą uzależnić się od pól, które powinny zostać ukryte. To sprzężenie może prowadzić do problemów, gdy implementacje muszą ewoluować (być może odsłonięte pola muszą zostać usunięte).

Deweloperzy Java używają funkcji języka interfejsu do abstrakcyjnych interfejsów klas, oddzielając w ten sposób klasy od ich użytkowników. Skupiając się na interfejsach Java zamiast na klasach, możesz zminimalizować liczbę odwołań do nazw klas w kodzie źródłowym. Ułatwia to przechodzenie z jednej klasy do drugiej (być może w celu poprawy wydajności) w miarę dojrzewania oprogramowania. Oto przykład:

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

Ten przykład deklaruje i inicjuje namespole, które przechowuje listę nazw ciągów. W przykładzie zadeklarowano również print()metodę drukowania zawartości listy ciągów, być może jednego ciągu w wierszu. Dla zwięzłości pominąłem implementację metody.

Listto interfejs Java opisujący sekwencyjną kolekcję obiektów. ArrayListto klasa opisująca opartą na Listtablicach implementację interfejsu Java. Zostaje ArrayListpobrana nowa instancja klasy i przypisana do Listzmiennej names. ( Listi ArrayListsą przechowywane w java.utilpakiecie standardowej biblioteki klas ).

Nawiasy kątowe i typy ogólne

Nawiasy ostre ( <i >) są częścią zestawu funkcji ogólnych języka Java. Wskazują, że namesopisuje listę ciągów (na liście mogą być przechowywane tylko łańcuchy). W przyszłym artykule o języku Java 101 przedstawię typy generyczne.

Gdy kod klienta współdziała z names, wywoła metody, które są zadeklarowane przez Listi które są implementowane przez ArrayList. Kod klienta nie będzie wchodził w bezpośrednią interakcję z ArrayList. W rezultacie kod klienta nie zepsuje się, gdy LinkedListwymagana jest inna klasa implementacji, na przykład :

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

Ponieważ print()typ parametru metody to List, implementacja tej metody nie musi się zmieniać. Jeśli jednak ArrayListbyłby typ, należałoby zmienić typ na LinkedList. Jeśli obie klasy miałyby zadeklarować swoje własne unikalne metody, może być konieczna znacząca zmiana print()implementacji.

Oddzielenie Listod ArrayListi LinkedListumożliwia pisanie kodu, który jest odporny na zmiany implementacji klas. Korzystając z interfejsów Java, można uniknąć problemów, które mogą wyniknąć z polegania na klasach implementacji. To rozdzielenie jest głównym powodem używania interfejsów Java.

Deklarowanie interfejsów Java

Deklarujesz interfejs, stosując klasową składnię, która składa się z nagłówka, po którym następuje treść. Jako minimum, nagłówek składa się ze słowa kluczowego, interfacepo którym następuje nazwa identyfikująca interfejs. Ciało zaczyna się od znaku otwartego nawiasu klamrowego i kończy się nawiasem klamrowym. Pomiędzy tymi ogranicznikami znajdują się deklaracje nagłówka stałej i metody:

interface identifier { // interface body }

Zgodnie z konwencją pierwsza litera nazwy interfejsu jest wielka, a kolejne małe (na przykład Drawable). Jeśli nazwa składa się z wielu słów, pierwsza litera każdego słowa jest wielka (na przykład DrawableAndFillable). Ta konwencja nazewnictwa jest znana jako CamelCasing.

Listing 2 deklaruje interfejs o nazwie Drawable.

Listing 2. Przykład interfejsu Java

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

Interfejsy w standardowej bibliotece klas Java

Zgodnie z konwencją nazewnictwa, wiele interfejsów w standardowej bibliotece klas Javy kończy się sufiksem zdolnym . Przykłady obejmują Callable, Cloneable, Comparable, Formattable, Iterable, Runnable, Serializable, i Transferable. Sufiks nie jest jednak obowiązkowy; biblioteka standardowa klasa obejmuje interfejsów CharSequence, ClipboardOwner, Collection, Executor, Future, Iterator, List, Mapi wiele innych.

Drawabledeklaruje pięć pól, które identyfikują stałe koloru. Ten interfejs deklaruje również nagłówek draw()metody, którą należy wywołać z jedną z tych stałych, aby określić kolor używany do rysowania konturu. (Używanie stałych całkowitych nie jest dobrym pomysłem, ponieważ można przekazać dowolną wartość całkowitą draw(). Jednak w prostym przykładzie wystarczą one).

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

Jak widać, wielokrotne wywoływanie draw()metody każdego obiektu jest żmudne . Ponadto spowoduje to dodanie dodatkowego kodu bajtowego do Drawpliku klas. Myśląc o Circlei Rectanglejako Drawables, możesz wykorzystać tablicę i prostą pętlę, aby uprościć kod. Jest to dodatkowa korzyść wynikająca z projektowania kodu, który preferuje interfejsy nad klasami.

Uwaga!