Что такое интерфейс в java
Перейти к содержимому

Что такое интерфейс в java

  • автор:

What Is an Interface?

As you've already learned, objects define their interaction with the outside world through the methods that they expose. Methods form the object's interface with the outside world; the buttons on the front of your television set, for example, are the interface between you and the electrical wiring on the other side of its plastic casing. You press the "power" button to turn the television on and off.

In its most common form, an interface is a group of related methods with empty bodies. A bicycle's behavior, if specified as an interface, might appear as follows:

To implement this interface, the name of your class would change (to a particular brand of bicycle, for example, such as ACMEBicycle ), and you'd use the implements keyword in the class declaration:

Implementing an interface allows a class to become more formal about the behavior it promises to provide. Interfaces form a contract between the class and the outside world, and this contract is enforced at build time by the compiler. If your class claims to implement an interface, all methods defined by that interface must appear in its source code before the class will successfully compile.

Interfaces

There are a number of situations in software engineering when it is important for disparate groups of programmers to agree to a "contract" that spells out how their software interacts. Each group should be able to write their code without any knowledge of how the other group's code is written. Generally speaking, interfaces are such contracts.

For example, imagine a futuristic society where computer-controlled robotic cars transport passengers through city streets without a human operator. Automobile manufacturers write software (Java, of course) that operates the automobile—stop, start, accelerate, turn left, and so forth. Another industrial group, electronic guidance instrument manufacturers, make computer systems that receive GPS (Global Positioning System) position data and wireless transmission of traffic conditions and use that information to drive the car.

The auto manufacturers must publish an industry-standard interface that spells out in detail what methods can be invoked to make the car move (any car, from any manufacturer). The guidance manufacturers can then write software that invokes the methods described in the interface to command the car. Neither industrial group needs to know how the other group's software is implemented. In fact, each group considers its software highly proprietary and reserves the right to modify it at any time, as long as it continues to adhere to the published interface.

Interfaces in Java

In the Java programming language, an interface is a reference type, similar to a class, that can contain only constants, method signatures, default methods, static methods, and nested types. Method bodies exist only for default methods and static methods. Interfaces cannot be instantiated—they can only be implemented by classes or extended by other interfaces. Extension is discussed later in this lesson.

Defining an interface is similar to creating a new class:

Note that the method signatures have no braces and are terminated with a semicolon.

To use an interface, you write a class that implements the interface. When an instantiable class implements an interface, it provides a method body for each of the methods declared in the interface. For example,

In the robotic car example above, it is the automobile manufacturers who will implement the interface. Chevrolet's implementation will be substantially different from that of Toyota, of course, but both manufacturers will adhere to the same interface. The guidance manufacturers, who are the clients of the interface, will build systems that use GPS data on a car's location, digital street maps, and traffic data to drive the car. In so doing, the guidance systems will invoke the interface methods: turn, change lanes, brake, accelerate, and so forth.

Interfaces as APIs

The robotic car example shows an interface being used as an industry standard Application Programming Interface (API). APIs are also common in commercial software products. Typically, a company sells a software package that contains complex methods that another company wants to use in its own software product. An example would be a package of digital image processing methods that are sold to companies making end-user graphics programs. The image processing company writes its classes to implement an interface, which it makes public to its customers. The graphics company then invokes the image processing methods using the signatures and return types defined in the interface. While the image processing company's API is made public (to its customers), its implementation of the API is kept as a closely guarded secret—in fact, it may revise the implementation at a later date as long as it continues to implement the original interface that its customers have relied on.

Абстрактные классы и интерфейсы в Java

Абстрактные классы и интерфейсы встречаются повсюду как в Java-приложениях, так и в самом Java Development Kit (JDK). Каждый из них служит своей цели:

Интерфейс — это контракт, который должен быть реализован конкретным классом.

Абстрактный класс похож на обычный, но отличается тем, что может содержать абстрактные методы — методы без реализации, и нельзя создать экземпляр абстрактного класса.

Многие разработчики не видят разницы между интерфейсами и абстрактными классами, но на самом деле между ними есть весьма существенное различие.

Интерфейсы

Интерфейс — это контракт, который реализуется в некотором классе. У интерфейса не может быть состояния, поэтому в нем нельзя использовать изменяемые поля экземпляра. В интерфейсе могут быть только неизменяемые final-поля.

Когда использовать интерфейсы

Интерфейсы очень полезны для уменьшения связанности (coupling) кода и реализации полиморфизма. Для примера давайте взглянем на интерфейс List из JDK:

Как вы, вероятно, заметили, код весьма краток и лаконичен. Здесь мы видим сигнатуры методов, которые будут реализованы в конкретном классе, реализующем этот интерфейс.

Контракт интерфейса List реализуется классами ArrayList , Vector , LinkedList и другими.

При использовании полиморфизма тип переменной объявляем как List , и присваиваем ей любую из доступных реализаций. Например:

В этом случае в каждом классе присутствует своя реализация методов. И это отличный пример использования интерфейсов. Если вы заметили, что ряд ваших классов содержит одинаковые методы, но с разными реализациями, то стоит использовать интерфейс.

Переопределение метода интерфейса

Помните, что интерфейс — это контракт, который должен быть реализован конкретным классом. Методы интерфейса неявно абстрактны и обязаны быть реализованы в классе, реализующем этот интерфейс.

Рассмотрим следующий пример:

Результат будет следующий:

Обратите внимание еще раз, что методы интерфейса неявно абстрактны и их не нужно явно объявлять как abstract.

Неизменяемые переменные

Еще одно правило, которое следует помнить, заключается в том, что интерфейс может содержать только неизменяемые переменные. Следующий код вполне рабочий:

Обратите внимание, что обе переменные неявно final и static . Это означает, что они являются константами, не зависят от экземпляра и не могут быть изменены.

При попытке изменить поля в интерфейсе Challenger , например, следующим образом:

будет ошибка компиляции:

Default-методы

После появления в Java 8 методов по умолчанию, некоторые разработчики решили, что интерфейсы стали абстрактными классами. Однако это не так, поскольку у интерфейсов не может быть состояния.

У методов по умолчанию может быть реализация, а у абстрактных методов — нет. Методы по умолчанию — результат появления лямбда-выражений и Stream API, но использовать их нужно с осторожностью.

В качестве примера default-метода из JDK можно привести метод forEach() из интерфейса Iterable . Вместо копирования кода этого метода во все реализации Iterable , мы можем переиспользовать метод forEach :

Любая реализация Iterable может использовать метод forEach() без необходимости реализации этого нового метода.

Давайте рассмотрим пример с методом по умолчанию:

Важно отметить, что у default-метода должна быть реализация и default-метод не может быть статическим.

Абстрактные классы

У абстрактных классов может быть состояние в виде изменяемых полей экземпляра. Например:

Абстрактные методы в абстрактных классах

Аналогично интерфейсам в абстрактных классах могут быть абстрактные методы. Абстрактный метод — это метод без тела (без реализации). Но в отличие от интерфейсов, абстрактные методы в абстрактных классах должны быть явно объявлены как абстрактные.

Попытка объявить метод без реализации и без ключевого слова abstract , например, следующим образом:

приведет к ошибке компиляции:

Когда использовать абстрактные классы

Рекомендуется использовать абстрактный класс, когда вам нужно изменяемое состояние. В качестве примера можно привести класс AbstractList из Java Collections Framework, который использует состояние.

Если хранить состояние класса не нужно, обычно лучше использовать интерфейс.

Хороший пример использования абстрактных классов — паттерн «шаблонный метод» (template method). Шаблонный метод манипулирует переменными экземпляра (полями) внутри конкретных методов.

Различия между абстрактными классами и интерфейсами

С точки зрения объектно-ориентированного программирования основное различие между интерфейсом и абстрактным классом заключается в том, что интерфейс не может иметь состояния, тогда как абстрактный класс может (в виде полей экземпляра).

Другое ключевое различие заключается в том, что классы могут реализовывать более одного интерфейса, но расширять только один абстрактный класс. Множественное наследование может привести к тупиковым ситуациям в коде, поэтому авторы Java решили этого избежать, отказавшись от него.

Еще одно различие состоит в том, что интерфейс может быть реализован классом или расширен другим интерфейсом, а класс может быть только расширен.

Также важно отметить, что лямбда-выражения могут использоваться только с функциональными интерфейсами (интерфейс только с одним методом), но не с абстрактными классами с одним абстрактным методом.

В таблице 1 обобщены различия между абстрактными классами и интерфейсами.

Таблица 1. Сравнение интерфейсов и абстрактных классов

Интерфейсы

Абстрактные классы

Могут содержать только final static поля. Интерфейс никогда не может изменять свое состояние.

Могут быть любые поля, в том числе статические, изменяемые и неизменяемые.

Класс может реализовывать несколько интерфейсов.

Класс может расширять только один абстрактный класс.

Может быть реализован с помощью ключевого слова implements.

Может расширять другой интерфейс с помощью extends.

Может быть только расширен с помощью extends.

Можно использовать только static final поля. Параметры и локальные переменные в методах.

Могут быть изменяемые поля экземпляра. Параметры и локальные переменные в методах.

В лямбда-выражениях могут использоваться только функциональные интерфейсы.

Абстрактные классы с одним абстрактным методом не могут использоваться в лямбда-выражениях.

Не может быть конструктора.

Может содержать конструктор.

Могут быть абстрактные методы.

Могут быть default и static методы (c Java 8).

Могут быть private методы с реализацией (с Java 9).

Могут быть любые методы.

Задачка

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

В приведенном ниже коде объявлены интерфейс, абстрактный класс и используются лямбда-выражения.

Как вы думаете, какой будет вывод, когда мы запустим этот код? Выберите один из следующих вариантов:

Вариант 1

Вариант 2

Вариант 3

Вариант 5

Разбор задачи

Эта задачка демонстрирует понятия об интерфейсах, абстрактных методах и о некоторых других вещах. Давайте разберем код строка за строкой.

В первой строке main() присутствует лямбда-выражение для интерфейса Zombie. Обратите внимание, что в этой лямбде мы инкрементируем статическое поле. Здесь также можно было использовать поле экземпляра, но не локальную переменную, объявленную вне лямбда-выражения. То есть код компилируется без ошибок. Также обратите внимание, что это лямбда-выражение еще не выполняется, оно только объявлено, и поле nemesisRaids не будет увеличено.

Далее мы выводим значение поля nemesisRaids , которое еще не увеличено. Следовательно, вывод будет:

Еще один интересный момент заключается в том, что мы используем анонимный внутренний класс. Мы создаем не экземпляр абстрактного класса Nemesis , но экземпляр анонимного класса, расширяющего Nemesis . Также обратите внимание, что первый конкретный класс в иерархии наследования всегда будет обязан реализовать абстрактные методы.

В интерфейсе Zombie есть поле с типом интерфейса Zombie , объявленное с помощью лямбда-выражения. Поэтому, когда мы вызываем метод Zombie.zombie.shoot() , получим следующий вывод:

В следующей строке вызывается лямбда-выражение, которое мы создали в начале. Следовательно, переменная nemesisRaids будет увеличена. Однако, поскольку мы используем оператор постинкремента, она будет увеличена только после этого выражения. Следующий вывод будет:

Далее вызовем метод shoot для nemesis , который изменяет поле экземпляра shoots на 23. Обратите внимание, что как раз здесь мы видим основную разницу между интерфейсом и абстрактным классом.

Наконец, мы выводим значение nemesis.shoots и nemesisRaids .

Правильный ответ — вариант 3:

Материал подготовлен в преддверии старта специализации Java-разработчик.

Недавно в рамках специализации прошел открытый урок, на котором мы обсудили алгоритм бинарного поиска, разобрались, почему он быстрее линейного. А также познакомились с понятием «О-большое». Делимся записью этого урока.

# Interfaces

Notice how the Cat class must implement the inherited abstract methods in both the interfaces. Furthermore, notice how a class can practically implement as many interfaces as needed (there is a limit of 65,535 due to JVM Limitation

  1. All variables declared in an interface are public static final
  2. All methods declared in an interface methods are public abstract (This statement is valid only through Java 7. From Java 8, you are allowed to have methods in an interface, which need not be abstract; such methods are known as default methods

# Declaring and Implementing an Interface

Declaration of an interface using the interface keyword:

Override Annotation

This forces the compiler to check that we are overriding and prevents the program from defining a new method or messing up the method signature.

Interfaces are implemented using the implements keyword.

In the example, classes Cat and Dog must define the getSound() method as methods of an interface are inherently abstract (with the exception of default methods).

Using the interfaces

# Extending an interface

An interface can extend another interface via the extends keyword.

Now a class implementing ExtendedResourceService will need to implement both getResource() and updateResource() .

Extending multiple interfaces

Unlike classes, the extends keyword can be used to extend multiple interfaces (Separated by commas) allowing for combinations of interfaces into a new interface

In this case a class implementing ExtendedResourceService will need to implement getResource() , getAlternateResource() , and updateResource() .

# Usefulness of interfaces

Interfaces can be extremely helpful in many cases. For example, say you had a list of animals and you wanted to loop through the list, each printing the sound they make.

One way to do this would be to use interfaces. This would allow for the same method to be called on all of the classes

Any class that implements Animal also must have a getSound() method in them, yet they can all have different implementations

We now have three different classes, each of which has a getSound() method. Because all of these classes implement the Animal interface, which declares the getSound() method, any instance of an Animal can have getSound() called on it

Because each of these is an Animal , we could even put the animals in a list, loop through them, and print out their sounds

Because the order of the array is Dog , Cat , and then Bird , "Woof Meow Chirp" will be printed to the console.

Interfaces can also be used as the return value for functions. For example, returning a Dog if the input is "dog", Cat if the input is "cat", and Bird if it is "bird", and then printing the sound of that animal could be done using

Interfaces are also useful for extensibility, because if you want to add a new type of Animal , you wouldn’t need to change anything with the operations you perform on them.

# Default methods

Introduced in Java 8, default methods are a way of specifying an implementation inside an interface. This could be used to avoid the typical "Base" or "Abstract" class by providing a partial implementation of an interface, and restricting the subclasses hierarchy.

# Observer pattern implementation

For example, it’s possible to implement the Observer-Listener pattern directly into the interface, providing more flexibility to the implementing classes.

Now, any class can be made "Observable" just by implementing the Observable interface, while being free to be part of a different class hierarchy.

# Diamond problem

The compiler in Java 8 is aware of the diamond problem

(opens new window) which is caused when a class is implementing interfaces containing a method with the same signature.

In order to solve it, an implementing class must override the shared method and provide its own implementation.

There’s still the issue of having methods with the same name and parameters with different return types, which will not compile.

# Use default methods to resolve compatibility issues

The default method implementations come in very handy if a method is added to an interface in an existing system where the interfaces is used by several classes.

To avoid breaking up the entire system, you can provide a default method implementation when you add a method to an interface. This way, the system will still compile and the actual implementations can be done step by step.

For more information, see the Default Methods

# Modifiers in Interfaces

The Oracle Java Style Guide states:

Modifiers should not be written out when they are implicit.

(opens new window) for the context and a link to the actual Oracle document.)

This style guidance applies particularly to interfaces. Let’s consider the following code snippet:

# Variables

All interface variables are implicitly constants with implicit public (accessible for all), static (are accessible by interface name) and final (must be initialized during declaration) modifiers:

# Methods

  1. All methods which don’t provide implementation are implicitly public and abstract .
  1. All methods with static or default modifier must provide implementation and are implicitly public .

After all of the above changes have been applied, we will get the following:

# Using Interfaces with Generics

Let’s say you want to define an interface that allows publishing / consuming data to and from different types of channels (e.g. AMQP, JMS, etc), but you want to be able to switch out the implementation details .

Let’s define a basic IO interface that can be re-used across multiple implementations:

Now I can instantiate that interface, but since we don’t have default implementations for those methods, it’ll need an implementation when we instantiate it:

We can also do something more useful with that interface, let’s say we want to use it to wrap some basic RabbitMQ functions:

Let’s say I want to use this IO interface now as a way to count visits to my website since my last system restart and then be able to display the total number of visits — you can do something like this:

Now let’s use the VisitCounter:

When implementing multiple interfaces, you can’t implement the same interface twice. That also applies to generic interfaces. Thus, the following code is invalid, and will result in a compile error:

# Strengthen bounded type parameters

(opens new window) allow you to set restrictions on generic type arguments:

But a type parameter can only bind to a single class type.

An interface type can be bound to a type that already had a binding. This is achieved using the & symbol:

This strengthens the bind, potentially requiring type arguments to derive from multiple types.

Multiple interface types can be bound to a type parameter:

But should be used with caution. Multiple interface bindings is usually a sign of a code smell

(opens new window) , suggesting that a new type should be created which acts as an adapter for the other types:

# Implementing interfaces in an abstract class

A method defined in an interface is by default public abstract . When an abstract class implements an interface , any methods which are defined in the interface do not have to be implemented by the abstract class . This is because a class that is declared abstract can contain abstract method declarations. It is therefore the responsibility of the first concrete sub-class to implement any abstract methods inherited from any interfaces and/or the abstract class .

From Java 8 onward it is possible for an interface to declare default implementations of methods which means the method won’t be abstract , therefore any concrete sub-classes will not be forced to implement the method but will inherit the default implementation unless overridden.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *