Поиск…


Вступление

Интерфейс является ссылочным типом, похожим на класс, который может быть объявлен с помощью ключевого слова 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

Замечания:

  1. Все переменные, объявленные в интерфейсе, являются public static final
  2. Все методы, объявленные в методах интерфейса, являются public abstract (этот оператор действителен только через Java 7. Из Java 8 вам разрешено иметь методы в интерфейсе, которые не обязательно должны быть абстрактными, такие методы известны как методы по умолчанию )
  3. Интерфейсы не могут быть объявлены final
  4. Если несколько интерфейсов объявляют метод, имеющий идентичную подпись, то он эффективно обрабатывается только как один метод, и вы не можете отличить, от какого метода интерфейса
  5. Соответствующий файл 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;

методы

  1. Все методы, которые не обеспечивают реализацию, являются неявно public и abstract .
public abstract void method();
Java SE 8
  1. Все методы со 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> {

}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow