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.
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 |
---|---|
|
Motsvarar förmågan att röra sig |
|
Motsvarar förmågan att bli riden |
|
Motsvarar förmågan att transportera grejer |
|
Klassen Wheel kan röra sig |
|
Klassen Car kan röra sig, ridas och transportera grejer |
|
Klassen Skateboard kan 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 |
---|---|
|
Förmågan CanSpeak . Detta gränssnitt förstår kommandot till speak , vilket betyder att det har en motsvarande metod. |
|
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.
|
|
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. default
Implementering 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 abstract
nyckelordet. 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 default
må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 InputStream
och OutputStream
klasserna 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 Tom
klassen inte implementerar meow()
metoden.
GO TO FULL VERSION