Zoeken…


Invoering

Een interface is een referentietype, vergelijkbaar met een klasse, dat kan worden gedeclareerd met behulp van een interface trefwoord. Interfaces kunnen alleen constanten, methode-handtekeningen, standaardmethoden, statische methoden en geneste typen bevatten. Methodelichamen bestaan alleen voor standaardmethoden en statische methoden. Net als abstracte klassen kunnen interfaces niet worden geïnstantieerd - ze kunnen alleen worden geïmplementeerd door klassen of worden uitgebreid met andere interfaces. Interface is een veelgebruikte manier om volledige abstractie in Java te bereiken.

Syntaxis

  • openbare interface Foo {void foo (); / * andere methoden * /}
  • openbare interface Foo1 breidt Foo uit {void bar (); / * andere methoden * /}
  • public class Foo2 implementeert Foo, Foo1 {/ * implementatie van Foo en Foo1 * /}

Een interface declareren en implementeren

Verklaring van een interface met behulp van het trefwoord interface :

public interface Animal {
    String getSound(); // Interface methods are public by default
}

Annotatie negeren

@Override
public String getSound() {
    // Code goes here...
}

Dit dwingt de compiler om te controleren of we overschrijven en voorkomt dat het programma een nieuwe methode definieert of de methodehandtekening verprutst.

Interfaces worden geïmplementeerd met behulp van het sleutelwoord implements .

public class Cat implements Animal {

    @Override 
    public String getSound() {
        return "meow";
    }
}

public class Dog implements Animal {

    @Override
    public String getSound() {
        return "woof";
    }
}

In het voorbeeld klassen Cat en Dog moeten definiëren getSound() methode De volgende interface inherent abstract (met uitzondering van standaard werkwijzen).

Gebruik van de interfaces

Animal cat = new Cat();
Animal dog = new Dog();

System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"

Implementeren van meerdere interfaces

Een Java-klasse kan meerdere interfaces implementeren.

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

Merk op hoe de Cat klasse de overgeërfde abstract methoden in beide interfaces moet implementeren. Merk bovendien op hoe een klasse praktisch zoveel interfaces kan implementeren als nodig (er is een limiet van 65.535 vanwege JVM-beperking ).

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

Notitie:

  1. Alle variabelen die in een interface worden gedeclareerd, zijn public static final
  2. Alle methoden die in een interfacemethode worden aangegeven, zijn public abstract (deze verklaring is alleen geldig via Java 7. Vanaf Java 8 mag u methoden in een interface hebben die niet abstract hoeven te zijn; dergelijke methoden staan bekend als standaardmethoden )
  3. Interfaces kunnen niet als final worden verklaard
  4. Als meer dan één interface een methode met identieke handtekening aangeeft, wordt deze in feite behandeld als slechts één methode en kunt u niet onderscheiden van welke interfacemethode is geïmplementeerd
  5. Een bijbehorend bestand InterfaceName.class zou voor elke interface worden gegenereerd, na compilatie

Een interface uitbreiden

Een interface kan een stompsok via extends zoekwoord.

public interface BasicResourceService {
    Resource getResource();
}

public interface ExtendedResourceService extends BasicResourceService {
    void updateResource(Resource resource);
}

Nu een klasse implementeren ExtendedResourceService zal moeten implementeren zowel getResource() en updateResource() .

Meerdere interfaces uitbreiden

In tegenstelling tot klassen, kan het extends sleutelwoord worden gebruikt om meerdere interfaces uit te breiden (gescheiden door komma's) waardoor combinaties van interfaces mogelijk zijn in een nieuwe interface

public interface BasicResourceService {
    Resource getResource();
}

public interface AlternateResourceService {
    Resource getAlternateResource();
}

public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
    Resource updateResource(Resource resource);
}

In dit geval een klasse implementeren ExtendedResourceService zal moeten implementeren getResource() , getAlternateResource() , en updateResource() .

Interfaces gebruiken met generieken

Stel dat u een interface wilt definiëren waarmee u gegevens van en naar verschillende soorten kanalen kunt publiceren (bijvoorbeeld AMQP, JMS, enz.), Maar u wilt de implementatiedetails kunnen uitschakelen ...

Laten we een basis-IO-interface definiëren die kan worden hergebruikt voor meerdere implementaties:

public interface IO<IncomingType, OutgoingType> {

    void publish(OutgoingType data);
    IncomingType consume();
    IncomingType RPCSubmit(OutgoingType data);

}

Nu kan ik die interface instantiëren, maar omdat we geen standaardimplementaties voor die methoden hebben, heeft het een implementatie nodig wanneer we het instantiëren:

    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

We kunnen ook iets nuttiger doen met die interface, laten we zeggen dat we het willen gebruiken om enkele basis RabbitMQ-functies in te pakken:

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

}

Laten we zeggen dat ik deze IO-interface nu wil gebruiken als een manier om het aantal bezoeken aan mijn website te tellen sinds mijn laatste herstart van het systeem en dan het totale aantal bezoeken te kunnen weergeven - u kunt zoiets doen:

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

Laten we nu de VisitCounter gebruiken:

    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

Wanneer u meerdere interfaces implementeert, kunt u dezelfde interface niet twee keer implementeren. Dat geldt ook voor generieke interfaces. De volgende code is dus ongeldig en resulteert in een compilatiefout:

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

Nut van interfaces

In veel gevallen kunnen interfaces zeer nuttig zijn. Stel bijvoorbeeld dat u een lijst met dieren had en dat u de lijst wilde doorlopen, waarbij ze elk het geluid afdrukken dat ze maken.

{cat, dog, bird}

Een manier om dit te doen zou zijn om interfaces te gebruiken. Hierdoor zou dezelfde methode in alle klassen kunnen worden aangeroepen

public interface Animal {
    public String getSound();
}

Elke klasse die implements Animal , moet ook een getSound() -methode bevatten, maar ze kunnen allemaal verschillende implementaties hebben

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

We hebben nu drie verschillende klassen, die elk een methode getSound() . Omdat al deze klassen de Animal interface implement , die de methode getSound() declareert, kan op elk exemplaar van een Animal getSound() aangeroepen

Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();

dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"

Omdat elk van deze Animal een Animal , kunnen we de dieren zelfs in een lijst plaatsen, ze doorlopen en hun geluiden afdrukken

Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
    System.out.println(animal.getSound());
}

Omdat de volgorde van de reeks Dog , Cat en vervolgens Bird , wordt "Woof Meow Chirp" afgedrukt naar de console.

Interfaces kunnen ook worden gebruikt als de retourwaarde voor functies. Bijvoorbeeld, het retourneren van een Dog als de invoer "hond" is , Cat als de invoer "kat" is en Bird als het "vogel" is , en vervolgens het geluid van dat dier afdrukken met behulp van

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

Interfaces zijn ook handig voor uitbreidbaarheid, omdat als u een nieuw type Animal wilt toevoegen, u niets hoeft te veranderen met de bewerkingen die u erop uitvoert.

Interfaces implementeren in een abstracte klasse

Een in een interface gedefinieerde methode is standaard public abstract . Wanneer een abstract class een interface implementeert, hoeven alle methoden die in de interface zijn gedefinieerd niet door de abstract class te worden geïmplementeerd. Dit komt omdat een class die abstract is verklaard, abstracte methode-verklaringen kan bevatten. Het is daarom de verantwoordelijkheid van de eerste concrete subklasse om abstract methoden te implementeren die zijn geërfd van alle interfaces en / of de 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.");
    }
}

Vanaf Java 8 is het mogelijk voor een interface om default van methoden te declareren, wat betekent dat de methode niet abstract , daarom zullen geen concrete subklassen niet worden gedwongen om de methode te implementeren, maar de default erven, tenzij overschreven.

Standaard methoden

Geïntroduceerd in Java 8, standaardmethoden zijn een manier om een implementatie in een interface te specificeren. Dit kan worden gebruikt om de typische "Base" - of "Abstract" -klasse te voorkomen door een gedeeltelijke implementatie van een interface te bieden en de subklassenhiërarchie te beperken.

Implementatie van het waarnemerspatroon

Het is bijvoorbeeld mogelijk om het patroon Observer-Listener rechtstreeks in de interface te implementeren, waardoor de implementatieklassen flexibeler worden.

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

Nu kan elke klasse "Observable" worden gemaakt door de Observable-interface te implementeren, terwijl het vrij is om deel uit te maken van een andere klassenhiërarchie.

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

Diamond probleem

De compiler in Java 8 is zich bewust van het diamantprobleem dat wordt veroorzaakt wanneer een klasse interfaces implementeert die een methode met dezelfde handtekening bevatten.

Om het op te lossen, moet een implementatieklasse de gedeelde methode overschrijven en zijn eigen implementatie bieden.

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

Er is nog steeds de kwestie van methoden met dezelfde naam en parameters met verschillende retourtypen, die niet kunnen worden gecompileerd.

Gebruik standaardmethoden om compatibiliteitsproblemen op te lossen

De standaardmethode-implementaties zijn erg handig als een methode wordt toegevoegd aan een interface in een bestaand systeem waar de interfaces door verschillende klassen worden gebruikt.

Om te voorkomen dat het hele systeem wordt opgedeeld, kunt u een standaardmethode-implementatie bieden wanneer u een methode aan een interface toevoegt. Op deze manier wordt het systeem nog steeds gecompileerd en kunnen de daadwerkelijke implementaties stap voor stap worden uitgevoerd.


Zie het onderwerp Standaardmethoden voor meer informatie.

Modificaties in interfaces

De Oracle Java Style Guide stelt:

Modifiers mogen niet worden weggeschreven als ze impliciet zijn.

(Zie Modifiers in Oracle Official Code Standard voor de context en een link naar het werkelijke Oracle-document.)

Deze stijlrichtlijn is met name van toepassing op interfaces. Laten we het volgende codefragment overwegen:

interface I {
    public static final int VARIABLE = 0;

    public abstract void method();

    public static void staticMethod() { ... }
    public default void defaultMethod() { ... }
}

Variabelen

Alle interfacevariabelen zijn impliciet constanten met impliciet public (toegankelijk voor iedereen), static (toegankelijk via interfacenaam) en final (moeten tijdens de aangifte worden geïnitialiseerd):

public static final int VARIABLE = 0;

methoden

  1. Alle methoden die geen implementatie bieden, zijn impliciet public en abstract .
public abstract void method();
Java SE 8
  1. Alle methoden met static of default moeten implementatie bieden en zijn impliciet public .
public static void staticMethod() { ... }

Nadat alle bovenstaande wijzigingen zijn toegepast, krijgen we het volgende:

interface I {
    int VARIABLE = 0;
    
    void method();

    static void staticMethod() { ... }
    default void defaultMethod() { ... }
}

Versterkte parameters van het begrensde type

Met begrensde typeparameters kunt u beperkingen instellen voor generieke typeargumenten:

class SomeClass {

}

class Demo<T extends SomeClass> {

}

Maar een parameter type kan alleen binden aan een enkel type klasse.

Een interfacetype kan worden gebonden aan een type dat al een binding had. Dit wordt bereikt met het & -symbool:

interface SomeInterface {

}

class GenericClass<T extends SomeClass & SomeInterface> {

}

Dit versterkt de binding, waarvoor mogelijk typeargumenten nodig zijn die afkomstig zijn van meerdere typen.

Meerdere interfacetypen kunnen aan een typeparameter worden gebonden:

class Demo<T extends SomeClass & FirstInterface & SecondInterface> {

}

Maar moet met voorzichtigheid worden gebruikt. Meerdere interface-bindingen zijn meestal een teken van een codegeur , wat suggereert dat er een nieuw type moet worden gemaakt dat als adapter voor de andere typen fungeert:

interface NewInterface extends FirstInterface, SecondInterface {

}

class Demo<T extends SomeClass & NewInterface> {

}


Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow