Hoofdstuk 4: Overerving en Polymorfisme
Inleiding
Objectgeorienteerd programmeren laat je toe om nieuwe klassen af te leiden van bestaande klassen. Dit wordt overerving genoemd
- Het proceduraal denken focust je op het ontwerpen van methode en het object-geörienteerd denken verbindt gegevens en methodes samen in objecten. Software ontwerp maakt gebruik van object-geörienteerd denken laat toe om objecten zelf te definiëren via klassen.
- Overerving is een belangrijk en krachtig tool in het hergebruik van software. Veronderstel dat je voor een CAD programma een aantal klassen moet definiëren die cirkels, rechthoeken en driehoeken modelleren. Deze klassen hebben veel gemeen.
Superklassen en Subklassen
Overerving laat toe om je een algemene klasse de definiëren, de zogenaamde superklasse en later kan je die klasse uitbreiden naar meer gespecializeerdere klassen, de zogenaamde subklassen.
- Een klasse gebruik je om objecten te modelleren van eenzelfde type. Verschillende klassen hebben misschien enkele eigenschappen of methodes gemeenschappelijk, die kunnen dan opgenomen worden in een algemene klasse en die kunnen dan gedeeld worden door andere klassen. Je kan een gespecializeerde klasse definiëren als uitbreiding op een algemene klasse.
- Beschouw een verzameling van klassen die geometrische objecten wil voorstellen. Deze klassen worden gebruikt om cirkels en rechthoeken te definiëren. Geometrische objecten hebben veel eigenschappen (attributen) en methodes gemeenschappelijk. Elke geometrisch object kan getekend worden in een bepaald kleur of kan opgevuld zijn of niet. De klasse kan dan de gegevensvelden
kleur
enopgevuld
bevatten, met elk een setter en getter methode. Deze kunnen in een algemene klasseGeometrischObject
vervat zitten. Veronderstel dat deze klasse ook een eigenschap
datumCreatie
heeft en de methodesgetDatumCreatie()
entoString()
. DetoString()
methode retourneert een string voorstelling van het object. Omdat cirkel een speciaal soort geometrisch object is, heeft het ook eigenschappen en methodes gemeen met andere objecten. Het is dus logisch om een klasseCirkel
te definiëren dat een uitbreiding is op de klasseGeometrischObject
.In een UML diagram wordt overerving aangeduid door een volle puil. Deze pijl vertrekt van de subklasse en wijst naar de superklasse.

- In de terminologie van Java: een klasse
C1
dat een uitbreiding is van een andere klasseC2
, wordt ook een subklasse genoemd. en de klasseC2
wordt dan de superklasse genoemd. Een superklasse wordt ook soms wel eens de ouderklasse of basisklasse genoemd. Een subklasse wordt ook wel de kindklasse, uitgebreide klasse of afgeleide klasse genoemd. - Een klasse
Cirkel
erft dus alle toegankelijke gegevensvariabelen over van de superklasseGeometrischObject
. In die zin is de klasseCirkel
een uitbreiding van de klasseGeometrischObject
. We duiden dit aan in de declaratie van de klasse door het sleutelwoordextends
. Een klasse beschrijving van een subklasse begint dan als volgt
public class Cirkel extends GeometrischObject
- Via het sleutelwoord
extends
weet Java dat de klasseCirkel
alle methodes overerft van de klasseGeometrischObject
. - Je kan overerving zien als een 'is-een'-relatie. Een cirkel en een rechthoek is een geometrisch object. Een subklasse is niet een deelverzameling van een superklasse. Integendeel, een subklasse bevat meer eigenschappen en methodes dan zijn superklasse. Een subklasse is specifieker dan een superklasse.
- Gegevensvariabelen die
private
gedeclareerd zijn, zijn niet toegankelijk van buiten de klasse. - Niet alle 'is-een'-relatie moet via overerving gaan. Een vierkant is een rechthoek, maar het is niet helemaal juist om vierkant als uitbreiding te beschouwen van rechthoek. Dit omdat de gegevensvariabelen
breedte
enhoogte
niet gepast zijn voor een vierkant. Het zou beter zijn om een klasseVierkant
te laten overerven van de klasseGeometrischObject
. - Sommige programmeertalen maken het de programmeur mogelijk om een subklassen te maken die afgeleid zijn van meerdere superklassen. Dit is gekend als meervoudige overerving. Java dit niet toe: een subklasse kan enkel maar overerven van één superklasse. Dit is enkelvoudige overerving.
Het sleutelwoord super
Het sleutelwoord
super
verwijst naar de superklasse en kan gebruikt worden om methodes en constructoren van de superklasse aan te roepen.
- Het sleutelwoord
this
werd gebruik als verwijzing naar het oproepende object. Het sleutelwoordsuper
verwijst naar de superklasse van de klasse waarsuper
voorkomt. - Het kan gebruikt worden om:
- een constructor van de superklasse aan te roepen
- een methode van de superklasse aan te roepen
Aanroepen van constructoren van superklassen
- De syntax om een constructor van een superklasse aan te roepen is:
super(), of super(parameters);
- De uitdrukking
super()
roept de no-argument constructor aan ensuper(parameters)
roept de constructor aan dat past met de argumenten. - Een voorbeeld:
GeometrischObject.java
Cirkel.java
- Een constructor van een afgeleide klasse kan een overladen constructor of een constructor van zijn superklasse aanroepen. Als geen van beide expliciet worden aangeroepen, dan zal de compiler automatisch een
super()
aanroep doen als eerste uitdrukking in de constructor. - In andere woorden, wanneer een object van een subklasse wordt aangemaakt, zal de constructor van de subklasse eerst de constructor van de superklasse aanroepen, vóór de uitdrukkingen die in de constructor van de subklasse staan.
- Op deze manier zal een afgeleide klasse altijd eerst de constructor van de superklasse aanroepen (ookal wordt hij niet expliciet aangeroepen in de subklasse). Dit noemt men ook wel het aaneenschakelen van constructoren.
Aanroepen van methodes van constructoren
- Het sleutelwoord
super
kan ook gebruikt worden om een methode van de superklasse aan te roepen:
super.methode(parameters);
- Zo kan je methodes aanroepen die enkel in de superklasse zijn gedeclareerd en geïmplementeerd.
GeometrischObject.java
Cirkel.java
- In het voorbeeld heeft de klasse
Cirkel
geen methode getKleur, die moet aangeroepen worden via de superklasseGeometrischObject
.
Overschrijven van methodes
Om een methode te overschrijven, moet de methode gedefinieerd zijn inde subklasse met dezelfde signatuur en hetzelfde retourtype als de definitie in de superklasse
- Een subklasse erft alle methodes van de superklasse, maar soms is het nodig dat de subklasse een eigen implementatie heeft voor een methode. Dit wordt aangeduid als overschrijven van methodes.
GeometrischObject.java
Cirkel.java
- In het bovenstaand voorbeeld is de methode
toString()
gedefinieerd in de klasseGeometrischObject
en wordt aangevuld in de klasseCirkel
. Beide methodes kunnen in de klasse Cirkel gebruikt worden. - Enkele aandachtspunten
- Een methode kan enkel overschreven worden als hij in de superklasse als
public
gedeclareerd is. Dus een private methode kan niet worden overschreven, omdat deze methode niet toegankelijke is buiten de klasse. - Een statische methode (met het sleutelwoord
static
) kan overgeërfd worden, maar kan niet overschreven worden. Een statische methode gedeclareerd in de superklasse kan gebruikt worden in de subklasse, maar kan niet overschreven worden.
- Een methode kan enkel overschreven worden als hij in de superklasse als
Overschrijven vr Overladen
Overladen is het definiëren van meerdere methodes met dezelfde naam, maar een andere signatuur. Overschrijven is het maken van een nieuwe implementatie voor een methode in de subklasse.
- Overladen van methodes is het maken van meerdere methodes met dezelfde naam, maar met een ander signatuur. De signatuur was de verzameling van alle paramters die de methode verwacht. Om een methode te overschrijven moet dezelfde methode gedefinieerd worden in subklasse met dezelfde signatuur en hetzelfde retourtype.
Test_a.java
Test_b.java
- In de code
Test_a.java
overschrijft de methodep(double i)
in de klasse A de methodep(double i)
in de klasse B. In de codeTest_b.java
biedt klasse B de methodep(double i)
aan en biedt klasse A de methodep(int i)
aan. De klasse A is een uitbreiding van klasse B. Bijgevolg is de methodep(double i)
ook beschikbaar in de klasse A. dus de methodep(int i)
overlaadt de methodep(double i)
. Merk op:
- Overschreven methodes bevinden zich in verschillende klassen. Deze klassen zijn gerelateerd via overerving. Overladen methodes bevinden zich in dezelfde klasse of in verschillende klassen. Waarbij deze klassen ook gerelateerd zijn via overerving.
- Overschreven methodes hebben dezelfde signatuur en retourtype; overladen methodes hebben dezelfde naam, maar een verschillende parameterlijst.
Om fouten te vermijden, kan je gebruik maken van een speciale Java syntax: override annaotatie. Hiervoor dien je
@Override
te zetten voor een methode in de subklasse. bijvoorbeeld:
- Deze annotatie duidt aan de geannoteerde methode verplicht is om een methode in de superklasse te overschrijven. Als deze methode in de superklasse niet bestaat, dan genereert de methode aan fout. Hierdoor, kan je fouten verhinderen waardoor je zeker bent dat je een methode aan het overschrijven bent en geen methode aan het overladen.
De klasse Object
en de toString()
methode
elke klasse is een afgeleide klasse van de klasse
java.lang.Object
Zelfs al heb je in een klasse geen overerving gespecifieerd, dan nog is een klasse die je maakt afgeleid van de klasse
Object
. Dit is het principe van objectgeörienteerd programmeren: alles is een object en elk object heeft zijn eigenschappen en methodes, die beschreven zijn in een klasse.De volgende twee klassen zijn equivalent:
public class Klasse{ ... }
en
- De klassen
String
,GeometrischObject
erven impliciet over van de klasseObject
. Het is belangrijk om te weten dat er in de klasseObject
ook enkele methode gedefinieerd zijn zoals de methodetoString()
. - De signatuur van de methode
toString()
is
public String toString()
- Door de methode aan te roepen maak je een String aan die typisch het object beschrijft. Bij default, zal de
toString()
methode de naam van de klasse waartoe het object behoort, weergeven. Daarna komt er het at teken (@
) en de geheugenlokatie van het object. Bijvoorbeeld, het volgend stukje code zal resulteren in:
- De uitkomst van dit stukje code is:
Cirkel@15037e5
. Deze boodschap heeft geen relevante informatie. In de klasse Cirkel zullen we typisch de methodetoString()
overschrijven met een nieuwe implementatie. Bijvoorbeeld:
Polymorfisme
Polymorfise betekent dat een variabele gedeclareerd als een supertype kan verwijzen naar een object van een subtype
- De drie zuilen van objectgeörienteerd programmeren zijn encapsulatie, overerving en polymorfisme. De eerste twee hebben we al gezien. Hier gaan we dieper in op de laatste zuil, polymorfisme.
- Laat ons eerst twee termen introduceren: subtype en supertype. Een klasse definieert een type. A type gedefinieerd door een subklasse noemen we een subtype, en een type gedefinieerd door een superklasse noemen we een supertype. Hierdoor, kan je zeggen dat
Cirkel
een subtype is vanGeometrischObject
enGeometrischObject
is een supertype vanCirkel
. - Zoals we weten is een subklasse een specializatie van een superklasse; elke instantie (object) van een subklasse erft eigenschappen en methodes van de superklasse, maar niet omgekeerd. Elke cirkel is een geometrisch object, maar niet elk geometrisch object is een cirkel.
- Hierdoor kan je een instantie van een subklasse altijd doorgeven als actuele parameter van zijn superklasse.
- We illustreren dit met een voorbeeld:
- De methode
toonObject
heeft een parameter van het typeGeometrischObject
. Je kan die methode aanroepen door een instantie door te geven van het typeGeometrischObject
. ZowelCirkel
enRechthoek
zijn van het typeGeometrischObject
, waardoor instanties van het type Cirkel en Rechthoek ook kunnen doorgegeven worden aan de methode. - Een object van een subklasse kan dus gebruiktw worden waar een object van zijn superklasse wordt gebruikt. Dit is bekend als polymorfisme. Eenvoudigweg, betekent polymorfisme dat een variabele van een supertype kan verwijzen naar een object van een subtype.
Dynamische binding
Een methode kan geïmplementeerd zijn in verschillende klassen binnen een overervingsrelatie. De Java Virtuele Machine bepaalt welke methode er moet uitgevoerd worden at runtime (= tijdens het uitvoeren van het programma).
- Een methode kan gedefinieerd worden in een superklasse en overschreven worden in een subklasse. Bijvoorbeeld, de
toString()
methode is gedefinieerd in een klasseObject
en wordt overschreven in de klasseGeometrischObject
. Beschouw het volgend stukje code:
- Welke implementatie van de
toString()
methode wordt opgeroepen door het objecto
? - Om deze vraag te beantwoorden, introduceren we twee termen: gedeclareerd type en actueel type. In dit geval is
Object
het gedeclareerde type vano
. Het type dat in de declaratie van de variabele of verwijzing staat is het gedeclareerde type van de variabele. - Tijdens de uitvoering wordt het duidelijk dat
o
eigenlijk een GeometrischObject is. De instantie is gecreëerd door een constructor vanGeometrischObject
. Het actuele type vano
isGeometrischObject
. Het actuele type van een variabele is de actuele klasse voor het object waarnaar de variabele verwijst. - Welke
toString()
methode dat wordt aangeroepen dooro
wordt bepaald door het actuele type vano
. Dit noemen we dynamische binding (Engels: dynamic binding).
- Dynamische binding werkt als volgt: Stel dat een object
o
een instantie is van klassesC1
,C2
,C3
,... waarbijC1
een subklasse is vanC2
enC2
een subklasse vanC3
,... Wanneer het objecto
een methodep
uitvoert dan zal Java op zoek gaan naar een implementatie van die methodep
in de klassesC1
,C2
,C3
, ... in die volgorde totdat de methode gevonden is. Wanneer een implementatie gevonden is, stopt de zoektocht en wordt die implementatie uitgevoerd. Dus als de klasseC1
geen implementatie voor de methode heeft, enC2
enC3
wel, dan wordt de implementatie in klasseC2
uitgevoerd.
DynamischeBinding.java
MasterStudent.java
Student.java
Persoon.java
- Het uitvoeren van de main methode in de klasse
DynamischeBinding.java
resulteert in de volgende uitvoer:
Student
Student
Persoon
java.lang.Object@130c19b