Java Language
gränssnitt
Sök…
Introduktion
interface
. Gränssnitt kan endast innehålla konstanter, metodsignaturer, standardmetoder, statiska metoder och kapslade typer. Metodkroppar finns endast för standardmetoder och statiska metoder. Som abstrakta klasser kan gränssnitt inte instanseras - de kan bara implementeras av klasser eller utökas med andra gränssnitt. Gränssnitt är ett vanligt sätt att uppnå full abstraktion i Java.
Syntax
- offentligt gränssnitt Foo {void foo (); / * alla andra metoder * /}
- offentligt gränssnitt Foo1 utökar Foo {void bar (); / * alla andra metoder * /}
- public class Foo2 implementerar Foo, Foo1 {/ * implementering av Foo och Foo1 * /}
Förklara och implementera ett gränssnitt
Förklaring av ett gränssnitt med interface
nyckelord:
public interface Animal {
String getSound(); // Interface methods are public by default
}
Åsidosätt kommentar
@Override
public String getSound() {
// Code goes here...
}
Detta tvingar kompilatorn att kontrollera att vi åsidosätter och förhindrar att programmet definierar en ny metod eller krossar metodsignaturen.
Gränssnitt genomförs med hjälp av implements
sökord.
public class Cat implements Animal {
@Override
public String getSound() {
return "meow";
}
}
public class Dog implements Animal {
@Override
public String getSound() {
return "woof";
}
}
I exemplet måste klasserna Cat
and Dog
definiera getSound()
som metoder för ett gränssnitt är i sig abstrakt (med undantag för standardmetoder).
Använda gränssnitten
Animal cat = new Cat();
Animal dog = new Dog();
System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"
Implementera flera gränssnitt
En Java-klass kan implementera flera gränssnitt.
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");
}
}
Lägg märke till hur Cat
klassen måste implementera de ärvda abstract
metoderna i båda gränssnitten. Lägg också märke till hur en klass praktiskt kan implementera så många gränssnitt som behövs (det finns en gräns på 65 535 på grund av JVM-begränsning ).
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
Notera:
- Alla variabler som deklareras i ett gränssnitt är
public static final
- Alla metoder som deklareras i ett gränssnittsmetoder är
public abstract
(detta uttalande gäller endast via Java 7. Från Java 8 får du ha metoder i ett gränssnitt, som inte behöver vara abstrakta; sådana metoder kallas standardmetoder ) - Gränssnitt kan inte deklareras som
final
- Om mer än ett gränssnitt förklarar en metod som har identisk signatur, behandlas det effektivt som bara en metod och du kan inte skilja från vilken gränssnittsmetod som implementeras
- En motsvarande fil InterfaceName.class skulle genereras för varje gränssnitt vid sammanställning
Utöka ett gränssnitt
Ett gränssnitt kan förlänga ett annat gränssnitt via extends
sökord.
public interface BasicResourceService {
Resource getResource();
}
public interface ExtendedResourceService extends BasicResourceService {
void updateResource(Resource resource);
}
Nu måste en klass som implementerar ExtendedResourceService
implementera både getResource()
och updateResource()
.
Utöka flera gränssnitt
Skillnad klasser, den extends
nyckelord kan användas för att utöka flera gränssnitt (Åtskilda av komman) möjliggör kombinationer av gränssnitt till ett nytt gränssnitt
public interface BasicResourceService {
Resource getResource();
}
public interface AlternateResourceService {
Resource getAlternateResource();
}
public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
Resource updateResource(Resource resource);
}
I det här fallet måste en klass som implementerar ExtendedResourceService
implementera getResource()
, getAlternateResource()
och updateResource()
.
Använda gränssnitt med generik
Låt oss säga att du vill definiera ett gränssnitt som tillåter publicering / konsumtion av data till och från olika typer av kanaler (t.ex. AMQP, JMS, etc.), men du vill kunna stänga av implementeringsdetaljer ...
Låt oss definiera ett grundläggande IO-gränssnitt som kan användas på nytt i flera implementationer:
public interface IO<IncomingType, OutgoingType> {
void publish(OutgoingType data);
IncomingType consume();
IncomingType RPCSubmit(OutgoingType data);
}
Nu kan jag instansera gränssnittet, men eftersom vi inte har standardimplementeringar för dessa metoder, kommer det att behöva en implementering när vi instanserar det:
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
Vi kan också göra något mer användbart med det gränssnittet, låt oss säga att vi vill använda det för att slå in några grundläggande RabbitMQ-funktioner:
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);
}
}
Låt oss säga att jag vill använda detta IO-gränssnitt nu som ett sätt att räkna besök på min webbplats sedan min senaste systemstart och sedan kunna visa det totala antalet besök - du kan göra något så här:
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);
}
}
Låt oss nu använda 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
När du implementerar flera gränssnitt kan du inte implementera samma gränssnitt två gånger. Det gäller också generiska gränssnitt. Följande kod är således ogiltig och kommer att resultera i ett kompileringsfel:
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); }
}
Användbarhet av gränssnitt
Gränssnitt kan vara mycket användbara i många fall. Till exempel, säg att du hade en lista med djur och att du vill gå igenom listan, var och en skriver ut det ljud de gör.
{cat, dog, bird}
Ett sätt att göra detta skulle vara att använda gränssnitt. Detta skulle göra det möjligt att kalla fram samma metod för alla klasser
public interface Animal {
public String getSound();
}
Varje klass som implements Animal
måste också ha en getSound()
metod i sig, men alla kan ha olika implementationer
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";
}
}
Vi har nu tre olika klasser, var och en har en getSound()
-metod. Eftersom alla dessa klasser implement
den Animal
gränssnittet, som förklarar getSound()
metoden, varje instans av ett Animal
kan ha getSound()
uppmanade den
Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();
dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"
Eftersom var och en av dessa är ett Animal
, kan vi till och med sätta djuren i en lista, slinga igenom dem och skriva ut sina ljud
Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
System.out.println(animal.getSound());
}
Eftersom ordningen för matrisen är Dog
, Cat
och sedan Bird
kommer "Woof Meow Chirp" att skrivas ut på konsolen.
Gränssnitt kan också användas som returvärde för funktioner. Till exempel returnerar en Dog
om ingången är "hund" , Cat
om ingången är "katt" och Bird
om den är "fågel" , och sedan skriva ut ljudet från det djuret kan göras med
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
Gränssnitt är också användbara för utdragbarhet, eftersom om du vill lägga till en ny typ av Animal
, behöver du inte ändra någonting med de operationer du utför på dem.
Implementera gränssnitt i en abstrakt klass
En metod som definieras i ett interface
är som standard public abstract
. När en abstract class
implementerar ett interface
behöver inte metoder som definieras i interface
implementeras av den abstract class
. Detta beror på att en class
som förklaras abstract
kan innehålla abstrakta metoddeklarationer. Det är därför den första konkreta underklassens ansvar att implementera alla abstract
metoder som ärvts från alla gränssnitt och / eller den 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.");
}
}
Från Java 8 och framåt är det möjligt för ett interface
att förklara default
av metoder vilket innebär att metoden inte kommer att vara abstract
, därför kommer inte några konkreta underklasser att tvingas implementera metoden utan kommer att ärva default
inte åsidosättas.
Standardmetoder
Standardmetoder som introduceras i Java 8 är ett sätt att specificera en implementering i ett gränssnitt. Detta kan användas för att undvika den typiska klassen "Base" eller "Abstract" genom att tillhandahålla en delvis implementering av ett gränssnitt och begränsa underklasshierarkin.
Observeringsmönsterimplementering
Till exempel är det möjligt att implementera Observer-Listener-mönstret direkt i gränssnittet, vilket ger mer flexibilitet för implementeringsklasserna.
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 valfri klass göras "observerbar" bara genom att implementera det observerbara gränssnittet, samtidigt som det är fritt att ingå i en annan klasshierarki.
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();
}
}
Diamantproblem
Kompilatorn i Java 8 är medveten om diamantproblemet som orsakas när en klass implementerar gränssnitt som innehåller en metod med samma signatur.
För att lösa det måste en implementeringsklass åsidosätta den delade metoden och tillhandahålla en egen implementering.
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"
}
}
Det finns fortfarande frågan om att ha metoder med samma namn och parametrar med olika returtyper, som inte kommer att sammanställas.
Använd standardmetoder för att lösa kompatibilitetsproblem
Standardmetodimplementeringarna är mycket praktiska om en metod läggs till ett gränssnitt i ett befintligt system där gränssnitten används av flera klasser.
För att undvika att hela systemet bryts ned kan du tillhandahålla en standardmetodimplementering när du lägger till en metod i ett gränssnitt. På detta sätt kommer systemet fortfarande att kompilera och de faktiska implementeringarna kan göras steg för steg.
Mer information finns i ämnet Standardmetoder .
Modifierare i gränssnitt
Oracle Java Style Guide anger:
Ändringar bör inte skrivas ut när de är implicita.
(Se Modifierare i Oracle Official Code Standard för sammanhanget och en länk till det faktiska Oracle-dokumentet.)
Denna stilvägledning gäller särskilt gränssnitt. Låt oss överväga följande kodavsnitt:
interface I {
public static final int VARIABLE = 0;
public abstract void method();
public static void staticMethod() { ... }
public default void defaultMethod() { ... }
}
variabler
Alla gränssnittsvariabler är implicit konstanter med implicita public
(tillgängliga för alla), static
(är tillgängliga med gränssnittsnamn) och final
(måste initieras under deklarering) modifierare:
public static final int VARIABLE = 0;
metoder
- Alla metoder som inte ger implementering är implicit
public
ochabstract
.
public abstract void method();
- Alla metoder med
static
ellerdefault
måste tillhandahålla implementering och är implicitpublic
.
public static void staticMethod() { ... }
När alla ovanstående ändringar har tillämpats får vi följande:
interface I {
int VARIABLE = 0;
void method();
static void staticMethod() { ... }
default void defaultMethod() { ... }
}
Stärka parametrarna för begränsad typ
Parametrar med begränsad typ låter dig ställa in begränsningar för generiska typargument:
class SomeClass {
}
class Demo<T extends SomeClass> {
}
Men en typparameter kan bara binda till en enda klasstyp.
En gränssnitttyp kan vara bunden till en typ som redan hade en bindning. Detta uppnås med &
symbolen:
interface SomeInterface {
}
class GenericClass<T extends SomeClass & SomeInterface> {
}
Detta stärker bindningen, potentiellt kräver typargument att härledas från flera typer.
Flera gränssnitstyper kan vara bundna till en typparameter:
class Demo<T extends SomeClass & FirstInterface & SecondInterface> {
}
Men bör användas med försiktighet. Flera gränssnittsbindningar är vanligtvis ett tecken på en kodlukt , vilket antyder att en ny typ ska skapas som fungerar som en adapter för de andra typerna:
interface NewInterface extends FirstInterface, SecondInterface {
}
class Demo<T extends SomeClass & NewInterface> {
}