1. Förmågor

För att bättre förstå fördelarna med gränssnitt och var de ska användas måste vi prata om några mer abstrakta saker.

En klass modellerar vanligtvis ett visst objekt. Ett gränssnitt motsvarar mindre objekt och mer deras förmågor eller roller.

Kärnan i gränssnitt

Till exempel är saker som bilar, cyklar, motorcyklar och hjul bäst representerade som klasser och föremål. Men deras förmågor - som "jag kan ridas", "jag kan transportera människor", "jag kan stå" - presenteras bättre som gränssnitt. Här är några exempel:

Koda Beskrivning
interface CanMove
{
   void move(String newLocation);
}
Motsvarar förmågan att röra sig
interface Rideable
{
   void ride(Passenger passenger);
}
Motsvarar förmågan att bli riden
interface CanTransport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
Motsvarar förmågan att transportera grejer
class Wheel implements CanMove
{
   ...
}
Klassen Wheelkan röra sig
class Car implements CanMove, Rideable, CanTransport
{
   ...
}
Klassen Carkan röra sig, ridas och transportera grejer
class Skateboard implements CanMove, Rideable
{
   ...
}
Klassen Skateboardkan röra sig och ridas


2. Roller

Gränssnitt förenklar avsevärt en programmerares liv. Mycket ofta har ett program tusentals objekt, hundratals klasser, men bara ett par dussin gränssnitt , dvs roller . Det finns få roller, men det finns många sätt att kombinera dem (klasser).

Hela poängen är att du inte behöver skriva kod i varje klass för att interagera med varannan klass. Du behöver bara interagera med deras roller (gränssnitt).

Föreställ dig att du är en husdjurstränare. Vart och ett av husdjuren du arbetar med kan ha flera olika förmågor. Du hamnar i en vänskaplig diskussion med din granne om vems husdjur som kan göra mest ljud. För att lösa saken radar du bara upp alla husdjur som kan "tala", och du ger dem kommandot: Tala!

Du bryr dig inte om vad det är för djur eller vilka andra förmågor de har. Även om de kan göra en trippel backvolta. I just detta ögonblick är du bara intresserad av deras förmåga att tala högt. Så här skulle det se ut i koden:

Koda Beskrivning
interface CanSpeak
{
   void speak();
}
Förmågan CanSpeak. Detta gränssnitt förstår kommandot till speak, vilket betyder att det har en motsvarande metod.
class Cat implements CanSpeak
{
   void speak()
   {
      println("MEOW");
   }
}

class Dog implements CanSpeak
{
   void speak()
   {
      println("WOOF");
   }
}

class Fish
{
   ...
}
Djur som har denna funktion.

För att underlätta förståelsen gav vi klassernas namn på engelska. Detta är tillåtet i Java, men det är högst oönskat.













Vår Fishhar inte förmågan att tala (implementerar inte gränssnittet) CanSpeak.

public static void main(String[] args)
{
   // Add all the animals to the list
   ArrayList pets = new ArrayList();
   pets.add(new Cat());
   pets.add(new Dog());
   pets.add(new Fish());

   // If the ability exists, then make a sound
   for(Object pet: pets)
   {
      if (pet instanceof CanSpeak)
      {
         CanSpeak loudmouth = (CanSpeak) pet;
         loudmouth.speak();
      }
   }
}
Och hur ger vi dem kommandot?

När antalet klasser i dina program når tusentals kommer du inte att kunna leva utan gränssnitt. Istället för att beskriva interaktionen mellan tusentals klasser räcker det med att beskriva interaktionen mellan några dussin gränssnitt — detta förenklar livet avsevärt.

Och i kombination med polymorfism är detta tillvägagångssätt i allmänhet en fantastisk framgång.



3. defaultImplementering av gränssnittsmetoder

Abstrakta klasser kan ha variabler och implementeringar av metoder, men de kan inte ha flera arv. Gränssnitt kan inte ha variabler eller implementeringar av metoder, men som kan ha flera arv.

Situationen uttrycks i följande tabell:

Förmåga/egenskap Abstrakta klasser Gränssnitt
Variabler
Metodimplementering
Multipelt arv

Så, vissa programmerare ville verkligen att gränssnitt skulle ha förmågan att ha metodimplementationer. Men att ha möjlighet att lägga till en metodimplementering betyder inte att en alltid kommer att läggas till. Lägg till det om du vill. Eller om du inte gör det, så gör det inte.

Dessutom beror problem med multipelt arv i första hand på variabler. Det var i alla fall vad de bestämde och gjorde. Från och med JDK 8 introducerade Java möjligheten att lägga till metodimplementationer till gränssnitt.

Här är en uppdaterad tabell (för JDK 8 och högre):

Förmåga/egenskap Abstrakta klasser Gränssnitt
Variabler
Metodimplementering
Multipelt arv

Nu för abstrakta klasser såväl som gränssnitt kan du deklarera metoder med eller utan implementering. Och detta är utmärkta nyheter!

I abstrakta klasser måste metoder utan implementering föregås av abstractnyckelordet. Du behöver inte lägga till något innan metoder med en implementering. I gränssnitt är det tvärtom. Om en metod inte har en implementering ska inget läggas till. Men om det finns en implementering defaultmåste nyckelordet läggas till.

För enkelhetens skull presenterar vi denna information i följande lilla tabell:

Förmåga/egenskap Abstrakta klasser Gränssnitt
Metoder utan implementering abstract
Metoder med en implementering default

Problem

Att använda gränssnitt som har metoder kan avsevärt förenkla stora klasshierarkier. Till exempel kan abstraktet InputStreamoch OutputStreamklasserna deklareras som gränssnitt! Detta gör att vi kan använda dem mycket oftare och mycket mer bekvämt.

Men det finns redan tiotals miljoner (miljarder?) Java-klasser i världen. Och om du börjar byta standardbibliotek, då kanske du går sönder något. Gillar allt! 😛

För att inte av misstag slå sönder befintliga program och bibliotek, bestämdes det att metodimplementationer i gränssnitt skulle ha lägst arvsföreträde .

Till exempel, om ett gränssnitt ärver ett annat gränssnitt som har en metod, och det första gränssnittet deklarerar samma metod men utan en implementering, kommer metodimplementeringen från det ärvda gränssnittet inte att nå det ärvda gränssnittet. Exempel:

interface Pet
{
   default void meow()
   {
      System.out.println("Meow");
   }
}

interface Cat extends Pet
{
   void meow(); // Here we override the default implementation by omitting an implementation
}

class Tom implements Cat
{
}

Koden kompileras inte eftersom Tomklassen inte implementerar meow()metoden.