1. Introduktion av gränssnitt
Idag är din dag för kunskap. Ett annat nytt och intressant ämne är gränssnitt.
Konceptet med ett gränssnitt är barnet till principerna om abstraktion och polymorfism. Ett gränssnitt är mycket likt en abstrakt klass, där alla metoder är abstrakta. Det deklareras på samma sätt som en klass, men vi använder nyckelordet interface
.
interface Feline
{
void purr();
void meow();
void growl();
}
Här är några användbara fakta om gränssnitt:
1. Deklarera ett gränssnitt
interface Drawable
{
void draw();
}
interface HasValue
{
int getValue();
}
- Istället för
class
nyckelordet skriver viinterface
. - Den innehåller bara abstrakta metoder (skriv inte nyckelordet
abstract
) - Faktum är att gränssnitt har alla
public
metoder
Ett gränssnitt kan bara ärva gränssnitt. Men ett gränssnitt kan ha många föräldrar. Ett annat sätt att säga detta är att säga att Java har flera arv av gränssnitt. Exempel:
interface Piece extends Drawable, HasValue
{
int getX();
int getY();
}
3. Ärva klasser från gränssnitt
En klass kan ärva flera gränssnitt (endast från en klass). Detta görs med hjälp av implements
nyckelordet. Exempel:
abstract class ChessItem implements Drawable, HasValue
{
private int x, y, value;
public int getValue()
{
return value;
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
}
Klassen ChessItem förklaras abstrakt: den implementerar alla ärvda metoder utom draw
. Klassen innehåller med andra ord ChessItem
en abstrakt metod — draw()
.
Den tekniska innebörden av nyckelorden extends
och implements
är densamma: båda är arv. Distinktionen gjordes för att förbättra läsbarheten av koden. Vi säger också att klasser ärvs (via extends
) och gränssnitt implementeras (via implements
)
4. Variabler
Här är det viktigaste: vanliga variabler kan inte deklareras i gränssnitt (även om statiska kan).
Men varför behöver vi gränssnitt? När används de? Gränssnitt har två starka fördelar jämfört med klasser:
2. Separera "beskrivning av metoder" från deras genomförande.
Tidigare sa vi att om du vill tillåta att metoderna i din klass ska anropas från andra klasser, måste dina metoder markeras med nyckelordet public
. Om du vill att några av dessa metoder endast ska anropas från din klass, måste du markera dem med nyckelordet private
. Med andra ord delar vi in klassens metoder i två kategorier: "för alla att använda" och "bara för vårt eget bruk".
Gränssnitt bidrar till att stärka denna division ytterligare. Vi kommer att göra en speciell "klass för alla att använda" samt en andra klass "bara för eget bruk", som kommer att ärva den första klassen. Så här skulle det se ut ungefär:
Innan | Efter |
---|---|
|
|
|
|
Vi delar upp vår klass i två: ett gränssnitt och en klass som ärver gränssnittet . Och vad är fördelen här?
Många olika klasser kan implementera (ärva) samma gränssnitt. Och var och en kan ha sitt eget beteende. Det finns till exempel ArrayList
LinkedList
två olika implementeringar av List
gränssnittet.
Således döljer vi inte bara de olika implementeringarna, utan även själva implementeringsklassen (eftersom vi bara behöver gränssnittet i koden). Detta låter oss vara mycket flexibla: precis när programmet körs kan vi ersätta ett objekt med ett annat, ändra ett objekts beteende utan att påverka alla klasser som använder det.
Detta är en mycket kraftfull teknik i kombination med polymorfism. Just nu är det långt ifrån självklart varför du ska göra detta. Du måste först stöta på program med dussintals eller hundratals klasser för att förstå att gränssnitt kan göra ditt liv så mycket enklare än utan dem.
3. Multipelt arv
I Java kan alla klasser bara ha en överordnad klass. I andra programmeringsspråk kan klasser ofta ha flera överordnade klasser. Detta är mycket bekvämt, men ger också många problem.
Javas skapare kom fram till en kompromiss: de förbjöd multipelt arv av klasser, men tillät multipelt arv av gränssnitt. Ett gränssnitt kan ha flera överordnade gränssnitt. En klass kan ha flera överordnade gränssnitt men bara en överordnad klass.
Varför förbjöd de multipelt arv av klasser men tillät multipelt arv av gränssnitt? På grund av så kallat diamantarvsproblem:
När B-klassen ärver A-klassen vet den ingenting om C- och D-klasserna. Så den använder variablerna i klassen A som den tycker är lämplig. C-klassen gör samma sak: den använder variablerna i klassen A, men på ett annat sätt. Och allt detta resulterar i en konflikt i D-klassen.
Låt oss titta på följande enkla exempel. Låt oss säga att vi har 3 klasser:
class Data
{
protected int value;
}
class XCoordinate extends Data
{
public void setX (int x) { value = x;}
public int getX () { return value;}
}
class YCoordinate extends Data
{
public void setY (int y) { value = y;}
public int getY () { return value; }
}
Dataklassen lagrar value
variabeln. Dess underklass XCoordinate använder den variabeln för att lagra x
värdet, och YCoordinate
klassen understigande använder den för att lagra y
värdet.
Och det fungerar. Separat. Men om vi vill att klassen XYCoordinates ska ärva både klasserna XCoordinate
och YCoordinate
klasserna får vi trasig kod. Den här klassen kommer att ha metoderna för sina förfaderklasser, men de kommer inte att fungera korrekt, eftersom de har samma value variable
.
Men eftersom gränssnitt inte kan ha variabler kan de inte ha den här typen av konflikter. Följaktligen är flera arv av gränssnitt tillåtet.
GO TO FULL VERSION