Java Language
Интерфейсы
Поиск…
Вступление
interface
. Интерфейсы могут содержать только константы, сигнатуры методов, методы по умолчанию, статические методы и вложенные типы. Органы метода существуют только для методов по умолчанию и статических методов. Подобно абстрактным классам, интерфейсы не могут быть созданы - они могут быть реализованы только классами или расширены другими интерфейсами. Интерфейс является распространенным способом достижения полной абстракции в Java.
Синтаксис
- открытый интерфейс Foo {void foo (); / * любые другие методы * /}
- открытый интерфейс Foo1 расширяет Foo {void bar (); / * любые другие методы * /}
- public class Foo2 реализует Foo, Foo1 {/ * реализацию Foo и Foo1 * /}
Объявление и реализация интерфейса
Объявление интерфейса с использованием ключевого слова interface
:
public interface Animal {
String getSound(); // Interface methods are public by default
}
Переопределить аннотацию
@Override
public String getSound() {
// Code goes here...
}
Это заставляет компилятор проверить, что мы переопределяем и не позволяет программе определять новый метод или испортить подпись метода.
Интерфейсы реализуются с помощью implements
ключевого слова.
public class Cat implements Animal {
@Override
public String getSound() {
return "meow";
}
}
public class Dog implements Animal {
@Override
public String getSound() {
return "woof";
}
}
В этом примере классы Cat
и Dog
должны определять метод getSound()
поскольку методы интерфейса являются абстрактно абстрактными (за исключением методов по умолчанию).
Использование интерфейсов
Animal cat = new Cat();
Animal dog = new Dog();
System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"
Реализация нескольких интерфейсов
Класс Java может реализовывать несколько интерфейсов.
public interface NoiseMaker {
String noise = "Making Noise"; // interface variables are public static final by default
String makeNoise(); //interface methods are public abstract by default
}
public interface FoodEater {
void eat(Food food);
}
public class Cat implements NoiseMaker, FoodEater {
@Override
public String makeNoise() {
return "meow";
}
@Override
public void eat(Food food) {
System.out.println("meows appreciatively");
}
}
Обратите внимание, как класс Cat
должен реализовывать наследованные abstract
методы в обоих интерфейсах. Кроме того, обратите внимание, как класс может практически реализовать столько интерфейсов, сколько необходимо (предел 65535 из-за ограничения JVM ).
NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid
Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid
Замечания:
- Все переменные, объявленные в интерфейсе, являются
public static final
- Все методы, объявленные в методах интерфейса, являются
public abstract
(этот оператор действителен только через Java 7. Из Java 8 вам разрешено иметь методы в интерфейсе, которые не обязательно должны быть абстрактными, такие методы известны как методы по умолчанию ) - Интерфейсы не могут быть объявлены
final
- Если несколько интерфейсов объявляют метод, имеющий идентичную подпись, то он эффективно обрабатывается только как один метод, и вы не можете отличить, от какого метода интерфейса
- Соответствующий файл InterfaceName.class будет создан для каждого интерфейса, после компиляции
Расширение интерфейса
Интерфейс может расширять другой интерфейс с помощью ключевого слова extends
.
public interface BasicResourceService {
Resource getResource();
}
public interface ExtendedResourceService extends BasicResourceService {
void updateResource(Resource resource);
}
Теперь класс, реализующий ExtendedResourceService
, должен будет реализовать как getResource()
и updateResource()
.
Расширение нескольких интерфейсов
В отличие от классов ключевое слово extends
может использоваться для расширения нескольких интерфейсов (разделенных запятыми), что позволяет сочетать интерфейсы с новым интерфейсом
public interface BasicResourceService {
Resource getResource();
}
public interface AlternateResourceService {
Resource getAlternateResource();
}
public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
Resource updateResource(Resource resource);
}
В этом случае для класса, реализующего ExtendedResourceService
, потребуется реализовать getResource()
, getAlternateResource()
и updateResource()
.
Использование интерфейсов с универсалами
Предположим, вы хотите определить интерфейс, который позволяет публиковать / потреблять данные для разных типов каналов (например, AMQP, JMS и т. Д.), Но вы хотите, чтобы их можно было отключить ...
Давайте определим базовый интерфейс ввода-вывода, который можно повторно использовать в нескольких реализациях:
public interface IO<IncomingType, OutgoingType> {
void publish(OutgoingType data);
IncomingType consume();
IncomingType RPCSubmit(OutgoingType data);
}
Теперь я могу создать экземпляр этого интерфейса, но поскольку у нас нет стандартных реализаций для этих методов, ему понадобится реализация, когда мы его создадим:
IO<String, String> mockIO = new IO<String, String>() {
private String channel = "somechannel";
@Override
public void publish(String data) {
System.out.println("Publishing " + data + " to " + channel);
}
@Override
public String consume() {
System.out.println("Consuming from " + channel);
return "some useful data";
}
@Override
public String RPCSubmit(String data) {
return "received " + data + " just now ";
}
};
mockIO.consume(); // prints: Consuming from somechannel
mockIO.publish("TestData"); // Publishing TestData to somechannel
System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now
Мы также можем сделать что-то более полезное с этим интерфейсом, допустим, мы хотим использовать его для переноса некоторых основных функций RabbitMQ:
public class RabbitMQ implements IO<String, String> {
private String exchange;
private String queue;
public RabbitMQ(String exchange, String queue){
this.exchange = exchange;
this.queue = queue;
}
@Override
public void publish(String data) {
rabbit.basicPublish(exchange, queue, data.getBytes());
}
@Override
public String consume() {
return rabbit.basicConsume(exchange, queue);
}
@Override
public String RPCSubmit(String data) {
return rabbit.rpcPublish(exchange, queue, data);
}
}
Предположим, я хочу использовать этот интерфейс ввода-вывода в качестве способа подсчета посещений моего веб-сайта с момента последнего перезапуска системы, а затем с возможностью отображать общее количество посещений - вы можете сделать что-то вроде этого:
import java.util.concurrent.atomic.AtomicLong;
public class VisitCounter implements IO<Long, Integer> {
private static AtomicLong websiteCounter = new AtomicLong(0);
@Override
public void publish(Integer count) {
websiteCounter.addAndGet(count);
}
@Override
public Long consume() {
return websiteCounter.get();
}
@Override
public Long RPCSubmit(Integer count) {
return websiteCounter.addAndGet(count);
}
}
Теперь давайте использовать VisitCounter:
VisitCounter counter = new VisitCounter();
// just had 4 visits, yay
counter.publish(4);
// just had another visit, yay
counter.publish(1);
// get data for stats counter
System.out.println(counter.consume()); // prints 5
// show data for stats counter page, but include that as a page view
System.out.println(counter.RPCSubmit(1)); // prints 6
При реализации нескольких интерфейсов вы не можете реализовать один и тот же интерфейс дважды. Это также относится к общим интерфейсам. Таким образом, следующий код недействителен и приведет к ошибке компиляции:
interface Printer<T> {
void print(T value);
}
// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
@Override public void print(Double d){ System.out.println("Decimal: " + d); }
@Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}
Полезность интерфейсов
Во многих случаях интерфейсы могут быть чрезвычайно полезными. Например, скажем, что у вас есть список животных, и вы хотели пройти через список, каждый из которых печатает звук, который они создают.
{cat, dog, bird}
Один из способов сделать это - использовать интерфейсы. Это позволило бы вызвать тот же метод для всех классов
public interface Animal {
public String getSound();
}
Любой класс, который implements Animal
также должен иметь метод getSound()
, но все они могут иметь разные реализации
public class Dog implements Animal {
public String getSound() {
return "Woof";
}
}
public class Cat implements Animal {
public String getSound() {
return "Meow";
}
}
public class Bird implements Animal{
public String getSound() {
return "Chirp";
}
}
Теперь у нас есть три разных класса, каждый из которых имеет метод getSound()
. Поскольку все эти классы implement
интерфейс Animal
, который объявляет метод getSound()
, любой экземпляр Animal
может иметь на нем getSound()
Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();
dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"
Поскольку каждый из них является Animal
, мы могли бы даже поместить животных в список, пропустить их и распечатать их звуки
Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
System.out.println(animal.getSound());
}
Поскольку порядок массива - Dog
, Cat
, а затем Bird
, «Woof Meow Chirp» будет напечатан на консоли.
Интерфейсы также могут использоваться как возвращаемое значение для функций. Например, возвращение Dog
если вход «собака» , Cat
если вход «cat» , и « Bird
если это «птица» , а затем печать звука этого животного может быть выполнена с использованием
public Animal getAnimalByName(String name) {
switch(name.toLowerCase()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
case "bird":
return new Bird();
default:
return null;
}
}
public String getAnimalSoundByName(String name){
Animal animal = getAnimalByName(name);
if (animal == null) {
return null;
} else {
return animal.getSound();
}
}
String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null
Интерфейсы также полезны для расширяемости, поскольку, если вы хотите добавить новый тип Animal
, вам не нужно ничего менять с помощью операций, которые вы выполняете на них.
Внедрение интерфейсов в абстрактном классе
Метод, определенный в interface
, по умолчанию является public abstract
. Когда abstract class
реализует interface
, любые методы, которые определены в interface
, не должны быть реализованы abstract class
. Это связано с тем, что class
, объявленный abstract
может содержать объявления абстрактных методов. Поэтому первым конкретным подклассом является реализация любых abstract
методов, унаследованных от любых интерфейсов и / или abstract class
.
public interface NoiseMaker {
void makeNoise();
}
public abstract class Animal implements NoiseMaker {
//Does not need to declare or implement makeNoise()
public abstract void eat();
}
//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
@Override
public void makeNoise() {
System.out.println("Borf borf");
}
@Override
public void eat() {
System.out.println("Dog eats some kibble.");
}
}
Начиная с Java 8, interface
может объявлять по default
реализации методов, что означает, что метод не будет abstract
, поэтому любые конкретные подклассы не будут вынуждены реализовать этот метод, но наследуют реализацию по default
если не переопределены.
Методы по умолчанию
Представленные в Java 8 методы по умолчанию - это способ указания реализации внутри интерфейса. Это можно использовать, чтобы избежать типичного класса «Base» или «Abstract», предоставляя частичную реализацию интерфейса и ограничивая иерархию подклассов.
Реализация шаблона наблюдателя
Например, можно реализовать шаблон Observer-Listener непосредственно в интерфейсе, предоставляя большую гибкость реализующим классам.
interface Observer {
void onAction(String a);
}
interface Observable{
public abstract List<Observer> getObservers();
public default void addObserver(Observer o){
getObservers().add(o);
}
public default void notify(String something ){
for( Observer l : getObservers() ){
l.onAction(something);
}
}
}
Теперь любой класс может быть выполнен «Наблюдаемым», просто реализуя интерфейс Observable, будучи свободным, чтобы быть частью другой иерархии классов.
abstract class Worker{
public abstract void work();
}
public class MyWorker extends Worker implements Observable {
private List<Observer> myObservers = new ArrayList<Observer>();
@Override
public List<Observer> getObservers() {
return myObservers;
}
@Override
public void work(){
notify("Started work");
// Code goes here...
notify("Completed work");
}
public static void main(String[] args) {
MyWorker w = new MyWorker();
w.addListener(new Observer() {
@Override
public void onAction(String a) {
System.out.println(a + " (" + new Date() + ")");
}
});
w.work();
}
}
Проблема с алмазами
Компилятор в Java 8 знает о проблеме с алмазом, которая возникает, когда класс реализует интерфейсы, содержащие метод с той же сигнатурой.
Для его решения класс реализации должен переопределить совместно используемый метод и обеспечить его собственную реализацию.
interface InterfaceA {
public default String getName(){
return "a";
}
}
interface InterfaceB {
public default String getName(){
return "b";
}
}
public class ImpClass implements InterfaceA, InterfaceB {
@Override
public String getName() {
//Must provide its own implementation
return InterfaceA.super.getName() + InterfaceB.super.getName();
}
public static void main(String[] args) {
ImpClass c = new ImpClass();
System.out.println( c.getName() ); // Prints "ab"
System.out.println( ((InterfaceA)c).getName() ); // Prints "ab"
System.out.println( ((InterfaceB)c).getName() ); // Prints "ab"
}
}
По-прежнему существует проблема с методами с тем же именем и параметрами с разными типами возвращаемых данных, которые не будут компилироваться.
Использовать методы по умолчанию для решения проблем совместимости
Реализация метода по умолчанию очень удобна, если метод добавлен к интерфейсу в существующей системе, где интерфейсы используются несколькими классами.
Чтобы не разбивать всю систему, вы можете предоставить реализацию метода по умолчанию при добавлении метода к интерфейсу. Таким образом, система все еще будет компилироваться, и фактические реализации могут быть сделаны шаг за шагом.
Дополнительные сведения см. В разделе « Способы по умолчанию» .
Модификаторы в интерфейсах
Руководство по стилю Java Java:
Модификаторы не должны выписываться, если они неявные.
(См. Модификаторы в стандартном стандартном коде Oracle для контекста и ссылку на фактический документ Oracle.)
Это руководство по стилю применяется, в частности, к интерфейсам. Рассмотрим следующий фрагмент кода:
interface I {
public static final int VARIABLE = 0;
public abstract void method();
public static void staticMethod() { ... }
public default void defaultMethod() { ... }
}
переменные
Все переменные интерфейса являются неявно константами с неявным public
(доступным для всех), static
(доступны по имени интерфейса) и final
(должны быть инициализированы во время объявления) модификаторами:
public static final int VARIABLE = 0;
методы
- Все методы, которые не обеспечивают реализацию, являются неявно
public
иabstract
.
public abstract void method();
- Все методы со
static
или модификатором поdefault
должны обеспечивать реализацию и неявноpublic
.
public static void staticMethod() { ... }
После того, как все вышеуказанные изменения будут применены, мы получим следующее:
interface I {
int VARIABLE = 0;
void method();
static void staticMethod() { ... }
default void defaultMethod() { ... }
}
Усиление параметров ограниченного типа
Параметры ограниченного типа позволяют вам устанавливать ограничения на аргументы общего типа:
class SomeClass {
}
class Demo<T extends SomeClass> {
}
Но параметр типа может привязываться только к одному типу класса.
Тип интерфейса может быть привязан к типу, который уже имел привязку. Это достигается с помощью символа &
:
interface SomeInterface {
}
class GenericClass<T extends SomeClass & SomeInterface> {
}
Это усиливает привязку, что потенциально требует аргументов типа для получения нескольких типов.
Несколько типов интерфейсов могут быть привязаны к параметру типа:
class Demo<T extends SomeClass & FirstInterface & SecondInterface> {
}
Но следует использовать с осторожностью. Связывание нескольких интерфейсов обычно является признаком запаха кода , предполагая, что должен быть создан новый тип, который действует как адаптер для других типов:
interface NewInterface extends FirstInterface, SecondInterface {
}
class Demo<T extends SomeClass & NewInterface> {
}