1. Evner

For bedre at forstå fordelene ved grænseflader, og hvor de skal bruges, er vi nødt til at tale om nogle mere abstrakte ting.

En klasse modellerer normalt et bestemt objekt. En grænseflade svarer mindre til objekter og mere til deres evner eller roller.

Essensen af ​​grænseflader

For eksempel er ting som biler, cykler, motorcykler og hjul bedst repræsenteret som klasser og objekter. Men deres evner - såsom "Jeg kan rides", "Jeg kan transportere mennesker", "Jeg kan stå" - præsenteres bedre som grænseflader. Her er nogle eksempler:

Kode Beskrivelse
interface CanMove
{
   void move(String newLocation);
}
Svarer til evnen til at bevæge sig
interface Rideable
{
   void ride(Passenger passenger);
}
Svarer til evnen til at blive redet
interface CanTransport
{
   void addStuff(Object stuff);
   Object removeStuff();
}
Svarer til evnen til at transportere ting
class Wheel implements CanMove
{
   ...
}
Klassen Wheelkan bevæge sig
class Car implements CanMove, Rideable, CanTransport
{
   ...
}
Klassen Carkan bevæge sig, blive redet og transportere ting
class Skateboard implements CanMove, Rideable
{
   ...
}
Klassen Skateboardkan bevæge sig og rides


2. Roller

Grænseflader forenkler i høj grad en programmørs liv. Meget ofte har et program tusindvis af objekter, hundredvis af klasser, men kun et par dusin grænseflader , dvs. roller . Der er få roller, men der er mange måder at kombinere dem på (klasser).

Hele pointen er, at du ikke behøver at skrive kode for i hver klasse for at interagere med hver anden klasse. Du skal bare interagere med deres roller (grænseflader).

Forestil dig, at du er en kæledyrstræner. Hvert af de kæledyr, du arbejder med, kan have flere forskellige evner. Du kommer i et venligt skænderi med din nabo om, hvis kæledyr kan larme mest. For at afgøre sagen, opstiller du bare alle de kæledyr, der kan "tale", og du giver dem kommandoen: Tal!

Du er ligeglad med, hvad det er for et dyr, eller hvilke andre evner de har. Også selvom de kan lave en tredobbelt saltomortale. I dette øjeblik er du kun interesseret i deres evne til at tale højt. Sådan ser det ud i kode:

Kode Beskrivelse
interface CanSpeak
{
   void speak();
}
Evnen CanSpeak. Denne grænseflade forstår kommandoen til speak, hvilket betyder, at den har en tilsvarende metode.
class Cat implements CanSpeak
{
   void speak()
   {
      println("MEOW");
   }
}

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

class Fish
{
   ...
}
Dyr, der har denne funktion.

For at lette forståelsen gav vi klassernes navne på engelsk. Dette er tilladt i Java, men det er meget uønsket.













Vores Fishhar ikke evnen til at tale (implementerer ikke grænsefladen 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();
      }
   }
}
Og hvordan giver vi dem kommandoen?

Når antallet af klasser i dine programmer når op på tusindvis, vil du ikke kunne leve uden grænseflader. I stedet for at beskrive interaktionen mellem tusindvis af klasser, er det nok at beskrive interaktionen mellem et par dusin grænseflader - dette forenkler livet i høj grad.

Og når det kombineres med polymorfi, er denne tilgang generelt en kæmpe succes.



3. defaultImplementering af grænseflademetoder

Abstrakte klasser kan have variabler og implementeringer af metoder, men de kan ikke have multiple arv. Grænseflader kan ikke have variabler eller implementeringer af metoder, men det kan have flere arv.

Situationen er udtrykt i følgende tabel:

Evne/egenskab Abstrakte klasser Grænseflader
Variabler
Metode implementering
Multipel arv

Så nogle programmører ønskede virkelig, at grænseflader kunne have metodeimplementeringer. Men at have mulighed for at tilføje en metodeimplementering betyder ikke, at en altid vil blive tilføjet. Tilføj det, hvis du vil. Eller hvis du ikke gør det, så lad være.

Derudover skyldes problemer med multipel nedarvning primært variabler. Det var i hvert fald, hvad de besluttede og gjorde. Startende med JDK 8 introducerede Java muligheden for at tilføje metodeimplementeringer til grænseflader.

Her er en opdateret tabel (for JDK 8 og nyere):

Evne/egenskab Abstrakte klasser Grænseflader
Variabler
Metode implementering
Multipel arv

Nu for abstrakte klasser såvel som grænseflader, kan du erklære metoder med eller uden implementering. Og det er fremragende nyheder!

I abstrakte klasser skal metoder uden implementering indledes med nøgleordet abstract. Du behøver ikke tilføje noget før metoder med en implementering. I grænseflader er det modsatte tilfældet. Hvis en metode ikke har en implementering, skal der ikke tilføjes noget. Men hvis der er en implementering, så defaultskal nøgleordet tilføjes.

For nemheds skyld præsenterer vi disse oplysninger i følgende lille tabel:

Evne/egenskab Abstrakte klasser Grænseflader
Metoder uden implementering abstract
Metoder med en implementering default

Problem

Brug af grænseflader, der har metoder, kan i høj grad forenkle store klassehierarkier. For eksempel kan abstraktet InputStreamog OutputStreamklasserne erklæres som grænseflader! Dette lader os bruge dem meget oftere og meget mere bekvemt.

Men der er allerede titusinder af millioner (milliarder?) Java-klasser i verden. Og hvis du begynder at ændre standardbiblioteker, så kan du måske ødelægge noget. Som alt! 😛

For ikke ved et uheld at bryde eksisterende programmer og biblioteker blev det besluttet, at metodeimplementeringer i grænseflader ville have den laveste arveprioritet .

For eksempel, hvis en grænseflade arver en anden grænseflade, der har en metode, og den første grænseflade erklærer den samme metode, men uden en implementering, så vil metodeimplementeringen fra den nedarvede grænseflade ikke nå den nedarvede grænseflade. Eksempel:

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 vil ikke kompilere, fordi Tomklassen ikke implementerer meow()metoden.