Интерфейсы в Java

Интерфейсы Java отличаются от классов, и важно знать, как использовать их особые свойства в ваших программах Java. В этом руководстве представлены различия между классами и интерфейсами, а затем представлены примеры, демонстрирующие, как объявлять, реализовывать и расширять интерфейсы Java.

Вы также узнаете, как интерфейс эволюционировал в Java 8 с добавлением стандартных и статических методов и в Java 9 с новыми частными методами. Эти дополнения делают интерфейсы более полезными для опытных разработчиков. К сожалению, они также стирают границы между классами и интерфейсами, делая программирование интерфейсов еще более запутанным для новичков в Java.

загрузить Получить код Загрузите исходный код для примеров приложений из этого руководства. Создано Джеффом Фризеном для JavaWorld.

Что такое интерфейс Java?

Интерфейс является точкой , где две системы встречаются и взаимодействуют. Например, вы можете использовать интерфейс торгового автомата, чтобы выбрать товар, оплатить его и получить еду или напиток. С точки зрения программирования, интерфейс находится между программными компонентами. Учтите, что интерфейс заголовка метода (имя метода, список параметров и т. Д.) Находится между внешним кодом, вызывающим метод, и кодом внутри метода, который будет выполнен в результате вызова. Вот пример:

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

Новичков часто сбивает с толку то, что классы также имеют интерфейсы. Как я объяснил в Java 101: Классы и объекты в Java, интерфейс - это часть класса, доступная для кода, находящегося вне его. Интерфейс класса состоит из некоторой комбинации методов, полей, конструкторов и других сущностей. Рассмотрим листинг 1.

Листинг 1. Класс Account и его интерфейс.

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)Конструктор и void deposit(long amount), String getName(), long getAmount(), и void setAmount(long amount)методы формирования в Accountинтерфейс класса,: они доступны для внешнего кода. private String name;И private long amount;поля недоступны.

Подробнее об интерфейсах Java

Что вы можете делать с интерфейсами в своих программах на Java? Ознакомьтесь с шестью ролями Джеффа в интерфейсе Java.

Код метода A, которая поддерживает интерфейс метода, и та часть класса , который поддерживает интерфейс класса, (например, частных полей) известен как метода или класса реализации . Реализация должна быть скрыта от внешнего кода, чтобы ее можно было изменить в соответствии с меняющимися требованиями.

Когда реализации раскрываются, могут возникнуть взаимозависимости между программными компонентами. Например, код метода может полагаться на внешние переменные, а пользователи класса могут стать зависимыми от полей, которые должны были быть скрыты. Эта связь может привести к проблемам, когда реализации должны развиваться (возможно, открытые поля необходимо удалить).

Разработчики Java используют функцию языка интерфейса для абстрагирования интерфейсов классов, тем самым отделяя классы от своих пользователей. Сосредоточившись на интерфейсах Java вместо классов, вы можете минимизировать количество ссылок на имена классов в исходном коде. Это облегчает переход от одного класса к другому (возможно, для повышения производительности) по мере развития вашего программного обеспечения. Вот пример:

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

В этом примере объявляется и инициализируется namesполе, в котором хранится список имен строк. В этом примере также объявляется print()метод печати содержимого списка строк, возможно, по одной строке на строку. Для краткости я опускал реализацию метода.

List- это интерфейс Java, описывающий последовательный набор объектов. ArrayList- это класс, описывающий реализацию Listинтерфейса Java на основе массивов . Получен новый экземпляр ArrayListкласса и присваивается Listпеременной names. ( Listи ArrayListхранятся в java.utilпакете стандартной библиотеки классов .)

Угловые скобки и универсальные модели

Угловые скобки ( <и >) являются частью набора универсальных функций Java. Они указывают, что namesописывает список строк (в списке могут храниться только строки). Я представлю дженерики в будущей статье о Java 101.

Когда клиентский код взаимодействует с names, он будет вызывать те методы, которые объявлены Listи реализованы ArrayList. Клиентский код не будет напрямую взаимодействовать с ArrayList. В результате клиентский код не сломается, когда потребуется другой класс реализации, например LinkedList:

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

Поскольку print()тип параметра метода - Listэто, реализация этого метода не должна изменяться. Однако, если бы тип был прежним ArrayList, его пришлось бы изменить на LinkedList. Если бы оба класса объявляли свои собственные уникальные методы, вам, возможно, потребовалось бы значительно изменить print()реализацию.

Развязка Listс ArrayListи LinkedListпозволяет писать код , который невосприимчив к изменениям класса реализации. Используя интерфейсы Java, вы можете избежать проблем, которые могут возникнуть из-за использования классов реализации. Эта развязка является основной причиной использования интерфейсов Java.

Объявление интерфейсов Java

Вы объявляете интерфейс, придерживаясь синтаксиса, подобного классу, который состоит из заголовка, за которым следует тело. Как минимум, заголовок состоит из ключевого слова, interfaceза которым следует имя, которое идентифицирует интерфейс. Тело начинается с символа открытой скобки и заканчивается закрытой скобкой. Между этими разделителями находятся объявления заголовков констант и методов:

interface identifier { // interface body }

По соглашению первая буква имени интерфейса пишется в верхнем регистре, а последующие буквы в нижнем (например, Drawable). Если имя состоит из нескольких слов, первая буква каждого слова пишется в верхнем регистре (например, DrawableAndFillable). Это соглашение об именах известно как CamelCasing.

В листинге 2 объявлен интерфейс с именем Drawable.

Листинг 2. Пример интерфейса Java.

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

Интерфейсы в стандартной библиотеке классов Java

Согласно соглашению об именах, многие интерфейсы в стандартной библиотеке классов Java заканчиваются суффиксом « способный» . Примеры включают Callable, Cloneable, Comparable, Formattable, Iterable, Runnable, Serializable, и Transferable. Однако суффикс не является обязательным; стандартная библиотека классов включает в себя интерфейсы CharSequence, ClipboardOwner, Collection, Executor, Future, Iterator, List, Mapи многие другие.

Drawableобъявляет пять полей, определяющих цветовые константы. Этот интерфейс также объявляет заголовок для draw()метода, который должен вызываться с одной из этих констант, чтобы указать цвет, используемый для рисования контура. (Использование целочисленных констант - не лучшая идея, потому что можно передать любое целочисленное значение draw(). Однако в простом примере их достаточно.)

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

Как видите, многократно вызывать draw()метод каждого объекта утомительно . Более того, при этом в Drawфайл класса добавляется дополнительный байт-код . Думая о Circleи Rectangleкак о Drawables, вы можете использовать массив и простой цикл для упрощения кода. Это дополнительное преимущество от разработки кода, который предпочитает интерфейсы классам.

Осторожно!