"Amigo, gillar du valar?"

"Valar? Nej, aldrig hört talas om dem."

"Den är som en ko, bara större och den simmar. För övrigt kom valar från kor. Eh, eller åtminstone delar de en gemensam förfader. Det spelar ingen roll."

Polymorfism och åsidosättande - 1

"Lyssna upp. Jag vill berätta om ett annat mycket kraftfullt verktyg för OOP: polymorfism . Det har fyra funktioner."

1) Åsidosättande av metod.

Föreställ dig att du har skrivit en "ko"-klass för ett spel. Den har många medlemsvariabler och metoder. Objekt i den här klassen kan göra olika saker: gå, äta, sova. Kor ringer också i en klocka när de går. Låt oss säga att du har implementerat allt i klassen in i minsta detalj.

Polymorfism och överordnad - 2

Så plötsligt säger kunden att han vill släppa en ny nivå av spelet, där alla handlingar sker i havet, och huvudpersonen är en val.

Du började designa Whale-klassen och inser att den bara skiljer sig något från Cow-klassen. Båda klasserna använder mycket liknande logik, och du bestämmer dig för att använda arv.

Cow-klassen är idealisk för att vara förälderklassen: den har redan alla nödvändiga variabler och metoder. Allt du behöver göra är att lägga till valens förmåga att simma. Men det finns ett problem: din val har ben, horn och en klocka. När allt kommer omkring implementerar Cow-klassen denna funktionalitet. Vad kan du göra?

Polymorfism och överordnad - 3

Metodöverskridande kommer till undsättning. Om vi ​​ärver en metod som inte gör exakt vad vi behöver i vår nya klass kan vi ersätta metoden med en annan.

Polymorfism och åsidosättande - 4

Hur görs detta? I vår descendant-klass deklarerar vi metoden som vi vill ändra (med samma metodsignatur som i den överordnade klassen) . Sedan skriver vi ny kod för metoden. Det är allt. Det är som om föräldraklassens gamla metod inte existerar.

Så här fungerar det:

Koda Beskrivning
class Cow
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
Här definierar vi två klasser:  Cow och  WhaleWhaleärver  Cow.

Klassen  Whale åsidosätter  printName();metoden.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Den här koden visar " Jag är en ko " på skärmen.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Den här koden visar " Jag är en val " på skärmen

Efter att den ärver Cowoch åsidosätter printName, har klassen Whalefaktiskt följande data och metoder:

Koda Beskrivning
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Vi vet ingenting om någon gammal metod.

"Ärligt talat, det var vad jag förväntade mig."

2) Men det är inte allt.

"Anta att  Cow klassen har en  printAllmetod , som anropar de två andra metoderna. Då skulle koden fungera så här:"

Skärmen kommer att visa:
Jag är vit
Jag är en val

Koda Beskrivning
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Skärmen kommer att visa:
Jag är vit
Jag är en val

Observera att när Cow-klassens metod printAll () anropas på ett Whale-objekt, kommer metoden Whale's printName() att användas, inte Cow's.

Det viktiga är inte klassen metoden är skriven i, utan snarare typen (klass) av objektet som metoden anropas på.

"Jag förstår."

"Du kan bara ärva och åsidosätta icke-statiska metoder. Statiska metoder ärvs inte och kan därför inte åsidosättas."

Så här ser Whale-klassen ut när vi tillämpar arv och åsidosätter metoderna:

Koda Beskrivning
class Whale
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
Så här ser Whale-klassen ut efter att vi tillämpat arv och åsidosatt metoden. Vi vet ingenting om någon gammal printNamemetod.

3) Typ gjutning.

Här är en ännu mer intressant punkt. Eftersom en klass ärver alla metoder och data från sin överordnade klass, kan ett objekt i den här klassen refereras av variabler från förälderklassen ( och föräldern till föräldern, etc., ända upp till klassen Object). Tänk på det här exemplet:

Koda Beskrivning
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
Skärmen kommer att visa:
Jag är vit.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
Skärmen kommer att visa:
Jag är vit.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
Skärmen kommer att visa:
Whale@da435a.
Metoden toString() ärvs från klassen Object.

"Bra grejer. Men varför skulle du behöva det här?"

"Det är en värdefull egenskap. Du kommer att förstå senare att den är väldigt, väldigt värdefull."

4) Sen bindning (dynamisk utskick).

Så här ser det ut:

Koda Beskrivning
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Skärmen kommer att visa:
Jag är en val.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
Skärmen kommer att visa:
Jag är en val.

Observera att det inte är typen av variabel som bestämmer vilken specifik printName- metod vi kallar (den för klassen Cow eller Whale), utan snarare typen av objekt som variabeln refererar till.

Cow - variabeln lagrar en referens till ett Whale- objekt, och metoden printName som definieras i Whale -klassen kommer att anropas.

"Nja, det har de inte lagt till för tydlighetens skull."

"Ja, det är inte så självklart. Kom ihåg denna viktiga regel:"

Uppsättningen metoder du kan anropa en variabel bestäms av variabelns typ. Men vilken specifik metod/implementering som anropas bestäms av typen/klassen för objektet som variabeln refererar till.

"Jag ska försöka."

"Du kommer att stöta på det här hela tiden, så du kommer snabbt att förstå det och aldrig glömma."

5) Typ gjutning.

Gjutning fungerar annorlunda för referenstyper, dvs klasser, än för primitiva typer. Breddande och avsmalnande omvandlingar gäller dock även referenstyper. Tänk på det här exemplet:

Breddande konvertering Beskrivning
Cow cow = new Whale();

En klassisk breddningsomvandling. Nu kan du bara anropa metoder definierade i Cow-klassen på Whale-objektet.

Kompilatorn låter dig endast använda cow-variabeln för att anropa de metoder som definieras av Cow-typen.

Begränsande konvertering Beskrivning
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
En klassisk avsmalningskonvertering med typkontroll. Kovariabeln av typen Cow lagrar en referens till ett Whale-objekt.
Vi kontrollerar att så är fallet och utför sedan (vidgnings-) typkonverteringen. Detta kallas även typgjutning .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
Du kan också utföra en avsmalnande konvertering av en referenstyp utan att typkontrollera objektet.
I det här fallet, om kovariabeln pekar på något annat än ett Whale-objekt, kommer ett undantag (InvalidClassCastException) att kastas.

6) Och nu till något gott. Kallar den ursprungliga metoden.

Ibland när du åsidosätter en ärvd metod vill du inte helt ersätta den. Ibland vill man bara lägga till lite till det.

I det här fallet vill du verkligen att den nya metodens kod ska anropa samma metod, men på basklassen. Och Java låter dig göra detta. Så här går det till:  super.method().

Här är några exempel:

Koda Beskrivning
class Cow
{
public void printAll()
{
printColor();
printName();
}
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a cow");
}
}

class Whale extends Cow
{
public void printName()
{
System.out.print("This is false: ");
super.printName();

System.out.println("I'm a whale");
}
}
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printAll();
}
Skärmen kommer att visa:
Jag är vit
Detta är falskt: Jag är en ko
Jag är en val

"Hmm. Tja, det var en lektion. Mina robotöron smälte nästan."

"Ja, det här är inte enkla saker. Det är något av det svåraste materialet du kommer att stöta på. Professorn lovade att ge länkar till material från andra författare, så att om du fortfarande inte förstår något kan du fylla i luckor."