Java Language
interfaces
Zoeken…
Invoering
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:
- Alle variabelen die in een interface worden gedeclareerd, zijn
public static final
- 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 ) - Interfaces kunnen niet als
final
worden verklaard - 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
- 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
- Alle methoden die geen implementatie bieden, zijn impliciet
public
enabstract
.
public abstract void method();
- Alle methoden met
static
ofdefault
moeten implementatie bieden en zijn implicietpublic
.
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> {
}