Sök…


Introduktion

Ett gränssnitt är en referenstyp, liknande en klass, som kan deklareras med hjälp av 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:

  1. Alla variabler som deklareras i ett gränssnitt är public static final
  2. 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 )
  3. Gränssnitt kan inte deklareras som final
  4. 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
  5. 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

  1. Alla metoder som inte ger implementering är implicit public och abstract .
public abstract void method();
Java SE 8
  1. Alla metoder med static eller default måste tillhandahålla implementering och är implicit public .
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> {

}


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow