"Amigo, hou je van walvissen?"

"Walvissen? Nee, nog nooit van gehoord."

"Het is net een koe, alleen groter en hij zwemt. Trouwens, walvissen zijn voortgekomen uit koeien. Uh, of ze hebben tenminste een gemeenschappelijke voorouder. Het maakt niet uit."

Polymorfisme en overheersing - 1

"Luister. Ik wil je vertellen over een andere zeer krachtige tool van OOP: polymorfisme . Het heeft vier kenmerken."

1) Overheersende methode.

Stel je voor dat je een "Koe"-klasse hebt geschreven voor een spel. Het heeft veel lidvariabelen en methoden. Objecten van deze klasse kunnen verschillende dingen doen: lopen, eten, slapen. Koeien laten ook een belletje rinkelen als ze lopen. Laten we zeggen dat je alles in de klas tot in het kleinste detail hebt geïmplementeerd.

Polymorfisme en overheersing - 2

Dan zegt de klant plotseling dat hij een nieuw niveau van het spel wil uitbrengen, waar alle acties in de zee plaatsvinden en de hoofdpersoon een walvis is.

Je begon de Whale-klasse te ontwerpen en besefte dat deze slechts een klein beetje verschilt van de Cow-klasse. Beide klassen gebruiken vergelijkbare logica en u besluit overerving te gebruiken.

De klasse Cow is bij uitstek geschikt om de ouderklasse te zijn: deze beschikt al over alle benodigde variabelen en methoden. Het enige dat u hoeft te doen, is het vermogen van de walvis om te zwemmen toe te voegen. Maar er is een probleem: je walvis heeft poten, hoorns en een bel. De klasse Cow implementeert deze functionaliteit immers. Wat kan je doen?

Polymorfisme en overheersing - 3

Methode negeren komt te hulp. Als we een methode erven die niet precies doet wat we nodig hebben in onze nieuwe klasse, kunnen we de methode vervangen door een andere.

Polymorfisme en overheersing - 4

Hoe wordt dit gedaan? In onze afstammelingenklasse declareren we de methode die we willen wijzigen (met dezelfde methodehandtekening als in de bovenliggende klasse) . Vervolgens schrijven we nieuwe code voor de methode. Dat is het. Het is alsof de oude methode van de bovenliggende klasse niet bestaat.

Dit is hoe het werkt:

Code Beschrijving
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");
}
}
Hier definiëren we twee klassen:  Cow en  WhaleWhaleerft  Cow.

De  Whale klasse overschrijft de  printName();methode.

public static void main(String[] args)
{
Cow cow = new Cow();
cow.printName();
}
Deze code toont « Ik ben een koe » op het scherm.
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Deze code toont « Ik ben een walvis » op het scherm

Nadat het erft Cowen overschrijft printName, Whaleheeft de klasse eigenlijk de volgende gegevens en methoden:

Code Beschrijving
class Whale
{
public void printColor()
{
System.out.println("I'm white");
}
public void printName()
{
System.out.println("I'm a whale");
}
}
We weten niets over een oude methode.

"Eerlijk gezegd, dat was wat ik verwachtte."

2) Maar dat is niet alles.

"Stel dat de  Cow klasse een  printAllmethode heeft die de twee andere methoden aanroept. Dan zou de code als volgt werken:"

Op het scherm verschijnt:
ik ben wit,
ik ben een walvis

Code Beschrijving
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();
}
Op het scherm verschijnt:
ik ben wit,
ik ben een walvis

Merk op dat wanneer de methode printAll() van de klasse Cow wordt aangeroepen voor een Whale-object, de methode printName() van de Whale wordt gebruikt, niet die van Cow.

Het belangrijkste is niet de klasse waarin de methode is geschreven, maar het type (klasse) van het object waarop de methode wordt aangeroepen.

"Ik zie."

"U kunt alleen niet-statische methoden overnemen en overschrijven. Statische methoden worden niet overgeërfd en kunnen daarom niet worden overschreven."

Zo ziet de klasse Whale eruit nadat we overerving hebben toegepast en de methoden hebben overschreven:

Code Beschrijving
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");
}
}
Zo ziet de klasse Whale eruit nadat we overerving hebben toegepast en de methode hebben overschreven. We weten niets over een oude printNamemethode.

3) Type gieten.

Hier is een nog interessanter punt. Omdat een klasse alle methoden en gegevens van zijn bovenliggende klasse overerft, kan naar een object van deze klasse worden verwezen door variabelen van de bovenliggende klasse (en de bovenliggende klasse van de bovenliggende klasse, enz., tot en met de klasse Object). Overweeg dit voorbeeld:

Code Beschrijving
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printColor();
}
Op het scherm verschijnt:
Ik ben blank.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printColor();
}
Op het scherm verschijnt:
Ik ben blank.
public static void main(String[] args)
{
Object o = new Whale();
System.out.println(o.toString());
}
Op het scherm verschijnt:
Whale@da435a.
De methode toString() wordt geërfd van de klasse Object.

"Goed spul. Maar waarom zou je dit nodig hebben?"

"Het is een waardevol kenmerk. Je zult later begrijpen dat het heel, heel waardevol is."

4) Late binding (dynamische verzending).

Hier is hoe het eruit ziet:

Code Beschrijving
public static void main(String[] args)
{
Whale whale = new Whale();
whale.printName();
}
Op het scherm verschijnt:
Ik ben een walvis.
public static void main(String[] args)
{
Cow cow = new Whale();
cow.printName();
}
Op het scherm verschijnt:
Ik ben een walvis.

Merk op dat niet het type variabele bepaalt welke specifieke methode printName we aanroepen (die van de klasse Cow of Whale), maar eerder het type object waarnaar door de variabele wordt verwezen.

De variabele Cow slaat een verwijzing naar een Whale- object op en de methode printName die in de Whale- klasse is gedefinieerd, wordt aangeroepen.

"Nou, dat hebben ze voor de duidelijkheid niet toegevoegd."

"Ja, het is niet zo voor de hand liggend. Onthoud deze belangrijke regel:"

De set methoden die u op een variabele kunt aanroepen, wordt bepaald door het type van de variabele. Maar welke specifieke methode/implementatie wordt aangeroepen, wordt bepaald door het type/de klasse van het object waarnaar wordt verwezen door de variabele.

"Ik zal het proberen."

"Je zult dit constant tegenkomen, dus je zult het snel begrijpen en nooit meer vergeten."

5) Type gieten.

Casten werkt anders voor referentietypen, dwz klassen, dan voor primitieve typen. Verbredings- en versmallingsconversies zijn echter ook van toepassing op referentietypen. Overweeg dit voorbeeld:

Verbreding conversie Beschrijving
Cow cow = new Whale();

Een klassieke verbredende conversie. Nu kunt u alleen methoden aanroepen die zijn gedefinieerd in de klasse Cow op het Whale-object.

De compiler laat u de variabele koe alleen gebruiken om de methoden aan te roepen die zijn gedefinieerd door het type Koe.

Vernauwende conversie Beschrijving
Cow cow = new Whale();
if (cow instanceof Whale)
{
Whale whale = (Whale) cow;
}
Een klassieke versmallende conversie met een typecontrole. De variabele cow van het type Cow slaat een verwijzing op naar een Whale-object.
We controleren of dit het geval is en voeren vervolgens de (verbredende) typeconversie uit. Dit wordt ook wel typecasting genoemd .
Cow cow = new Cow();
Whale whale = (Whale) cow; //exception
U kunt ook een vernauwende conversie van een referentietype uitvoeren zonder typecontrole van het object.
In dit geval, als de cow- variabele naar iets anders dan een Whale-object wijst, wordt er een uitzondering (InvalidClassCastException) gegenereerd.

6) En nu iets lekkers. De oorspronkelijke methode aanroepen.

Wanneer u een overgeërfde methode overschrijft, wilt u deze soms niet volledig vervangen. Soms wil je er gewoon een klein beetje aan toevoegen.

In dit geval wilt u echt dat de code van de nieuwe methode dezelfde methode aanroept, maar dan in de basisklasse. En Java laat je dit doen. Zo werkt het:  super.method().

Hier zijn enkele voorbeelden:

Code Beschrijving
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();
}
Op het scherm verschijnt:
Ik ben wit.
Dit is onjuist: ik ben een koe,
ik ben een walvis

"Hmm. Nou, dat was een lesje. Mijn robotoren smolten bijna."

"Ja, dit zijn geen simpele dingen. Het is een van de moeilijkste materialen die je zult tegenkomen. De professor beloofde links te geven naar materialen van andere auteurs, zodat je, als je nog steeds iets niet begrijpt, de hiaten."