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

Java интерфейсите се различават от класовете и е важно да знаете как да използвате техните специални свойства във вашите Java програми. Този урок представя разликата между класове и интерфейси, след което ви води през примери, демонстриращи как да декларирате, внедрявате и разширявате Java интерфейси.

Също така ще научите как се е развил интерфейсът в Java 8 с добавяне на стандартни и статични методи и в Java 9 с новите частни методи. Тези допълнения правят интерфейсите по-полезни за опитни разработчици. За съжаление те също размиват линиите между класове и интерфейси, което прави програмирането на интерфейси още по-объркващо за начинаещите в Java.

изтегляне Вземете кода Изтеглете изходния код, например приложения в този урок. Създадено от Jeff Friesen за 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, е, че класовете също имат интерфейси. Както обясних в 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 на Jeff.

Код за метод, който поддържа интерфейс на метода, както и че част от един клас, който поддържа интерфейс на класа (като например частни полета) е известен като метод или клас на изпълнение . Прилагането трябва да бъде скрито от външен код, така че да може да бъде променено, за да отговори на променящите се изисквания.

Когато са изложени приложения, могат да възникнат взаимозависимости между софтуерните компоненти. Например кодът на метода може да разчита на външни променливи и потребителите на даден клас могат да станат зависими от полета, които е трябвало да бъдат скрити. Това свързване може да доведе до проблеми, когато изпълненията трябва да се развият (може би откритите полета трябва да бъдат премахнати).

Разработчиците на Java използват функцията на езика на интерфейса, за да абстрахират интерфейсите на класа, като по този начин отделят класовете от своите потребители. Като се фокусирате върху Java интерфейси вместо класове, можете да сведете до минимум броя препратки към имена на класове във вашия изходен код. Това улеснява преминаването от един клас в друг (може би за подобряване на производителността), докато вашият софтуер узрее. Ето пример:

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

Този пример декларира и инициализира namesполе, което съхранява списък с имена на низове. Примерът също декларира print()метод за разпечатване на съдържанието на списък от низове, може би по един низ на ред. За краткост пропуснах изпълнението на метода.

Listе интерфейс на Java, който описва последователна колекция от обекти. ArrayListе клас, който описва базирана на масив реализация на ListJava интерфейса. Получава се нов екземпляр на 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 }

By convention, the first letter of an interface's name is uppercased and subsequent letters are lowercased (for example, Drawable). If a name consists of multiple words, the first letter of each word is uppercased (such as DrawableAndFillable). This naming convention is known as CamelCasing.

Listing 2 declares an interface named Drawable.

Listing 2. A Java interface example

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

Interfaces in Java's standard class library

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

Както можете да видите, досадно е многократното извикване на draw()метода на всеки обект . Освен това при това се добавя допълнителен байт код към Drawфайла на класа на. Като мислите за Circleи Rectangleкато Drawables, можете да използвате масив и обикновен цикъл, за да опростите кода. Това е допълнителна полза от проектирането на код, за да се предпочитат интерфейси пред класове.

Внимание!