Overerving, interfaces en polymorfisme in Java

Leer hoe Java-klassen op elkaar voortbouwen. Breid een klasse uit met extends en super, overschrijf overgeërfde methoden en geef je klassen bruikbare toString-, equals- en hashCode-methoden uit Object. Definieer gemeenschappelijk gedrag met abstracte klassen en interfaces, voorspel met polymorfisme en dynamische binding welke methode wordt uitgevoerd en modelleer vaste keuzemogelijkheden en eenvoudige gegevens met enums en records. Uitgewerkte codevoorbeelden laten elke stap zien, samen met de fouten die beginners het vaakst maken.

BeginnerLes 8 van 11 in Cursus Java programmeren: klassen, overerving, collecties en streams

Wat je leert

  1. Bouw met extends een nieuwe klasse op een bestaande klasse, weet wat de nieuwe klasse overerft en zorg dat het overgeërfde deel van elk object correct wordt geïnitialiseerd met super.
    • Maak een subklasse met extends
      Declareer een subklasse, bepaal welke leden die overerft en kan gebruiken en beslis met de is-een-test of overerving past.
    • Initialiseer objecten met superklasseconstructoren
      Geef waarden door aan de superklasseconstructor met super(...), voorspel in welke volgorde constructoren worden uitgevoerd en vermijd de klassieke fouten met constructoren.
  2. Verander overgeërfd gedrag door methoden te overschrijven, bouw met super voort op de versie van de superklasse en geef je klassen betekenisvolle toString-, equals- en hashCode-methoden uit Object.
    • Overschrijf overgeërfde methoden
      Vervang of breid een overgeërfde methode uit met een correcte overschrijving, roep de versie van de superklasse aan met super en onderscheid overschrijven van overladen.
    • Gebruik en overschrijf de methoden van Object
      Leg uit waarom elk object toString, equals, hashCode en getClass heeft, lees hun standaardgedrag af en schrijf een bruikbare toString.
    • Overschrijf equals en hashCode correct
      Definieer met een correcte equals(Object) wanneer twee objecten gelijk zijn, houd hashCode daarmee consistent en behandel gelijkheid binnen een klassenhiërarchie.
  3. Gebruik abstracte klassen om code te delen tussen nauw verwante klassen, gebruik interfaces om gedrag vast te leggen voor niet-verwante klassen en kies het juiste middel voor een ontwerp.
    • Schrijf abstracte klassen en abstracte methoden
      Declareer abstracte klassen en methoden, vul die aan in concrete subklassen en gebruik een abstracte klasse om velden en code te delen.
    • Definieer en implementeer interfaces
      Schrijf een interface als contract, implementeer die in één of meer klassen en combineer interfaces met overerving.
    • Voeg default-methoden, statische methoden en privémethoden toe aan interfaces
      Geef interfaces gemeenschappelijk gedrag met default-methoden, voeg hulpmethoden toe met statische methoden en privémethoden en los conflicten tussen default-methoden op.
    • Kies tussen een abstracte klasse en een interface
      Vergelijk abstracte klassen en interfaces en kies voor een bepaald ontwerp de juiste optie of gebruik ze allebei.
  4. Verwijs naar objecten via algemene typen, voorspel welke methode tijdens de uitvoering wordt uitgevoerd, zet typen veilig om en schrijf code die blijft werken als nieuwe subklassen worden toegevoegd.
    • Verwijs naar objecten via superklassetypen en interfacetypen
      Onderscheid het gedeclareerde type van een variabele van het werkelijke type van het object en gebruik superklassetypen en interfacetypen voor variabelen, parameters en lijsten.
    • Voorspel welke methode wordt uitgevoerd bij dynamische binding
      Volg methodeaanroepen door een klassenhiërarchie en onderscheid wat de compiler bepaalt van wat tijdens de uitvoering wordt bepaald.
    • Gebruik casts en instanceof om met verschillende typen te werken
      Gebruik veilig downcasting met instanceof en patroonherkenning, herken ClassCastException en weet wanneer een typetest wijst op een ontbrekende overschreven methode.
    • Ontwerp flexibele code met polymorfisme
      Schrijf methoden en collecties voor algemene typen, zodat nieuwe klassen zonder wijzigingen kunnen worden gebruikt, en maak klassen sorteerbaar met Comparable.
  5. Geef een vaste verzameling keuzemogelijkheden weer met een enum en een eenvoudig gegeven met een record, en gebruik daarbij de leden die de compiler voor je genereert.
    • Geef vaste verzamelingen waarden weer met enums
      Declareer enums, gebruik hun ingebouwde methoden en switch en geef enumconstanten velden, constructoren en gedrag.
    • Modelleer gegevens met records
      Declareer records voor eenvoudige gegevens, gebruik de gegenereerde constructor, accessors, equals, hashCode en toString en valideer gegevens in een compacte constructor.

Overerving in Java: uitleg

5 hoofdstukken

1

Breid klassen uit met overerving

Een subklasse begint met de velden en het gedrag van een bestaande klasse en voegt daarna toe wat haar anders maakt. Die relatie beïnvloedt zowel hoe andere code het object kan gebruiken als hoe Java het opbouwt. Volg een persoon en een student door hun declaraties en constructoren: private gegevens blijven onder controle van de superklasse en constructoraanroepen leveren de waarden die de overgeërfde toestand nodig heeft.

1.1 Bouw een subklasse en bescherm haar gegevens

Overerving maakt een subklasse op basis van een bestaande superklasse. In class Student extends Person is elke student een persoon. Een studentobject bevat zijn persoonstoestand en zijn extra studenttoestand samen in één object.

Voeg de studentspecifieke onderdelen toe

Hier zie je een kleine superklasse en een subklasse:

class Person {
    private String name;
    public Person(String name) { this.name = name; }
    public String getName() { return name; }
}

class Student extends Person {
    private int credits;
    public Student(String name) { super(name); }
    public void study() { credits += 5; }
    public int getCredits() { return credits; }
}

Student overerft getName(). De klasse voegt een veld voor studiepunten en twee methoden toe. Na Student s = new Student("Ada Lovelace"); begint het veld voor studiepunten op 0. Twee aanroepen van s.study() verhogen het eerst tot 5 en dan tot 10. s.getName() geeft nog steeds “Ada Lovelace” terug: de overgeërfde methode leest het private veld dat in Person is gedeclareerd.

De instructie super(name) geeft de naam door aan de constructor van Person. Constructoren worden niet overgeërfd, dus Student declareert een eigen constructor. Een subklasse mag methoden toevoegen en overgeërfd gedrag overschrijven, maar kan een overgeërfde public methode niet uit haar publieke interface verwijderen.

Bepaal welke leden toegankelijk zijn
ToegangsmodifierWaar het lid toegankelijk is
publicElke klasse met toegang tot het type dat het lid declareert
protectedHetzelfde pakket, plus toegang voor subklassen in andere pakketten
Geen modifierHetzelfde pakket
privateDe klasse die het lid declareert

Private leden worden niet overgeërfd. Het private veld name bestaat nog steeds in elke student, maar Student kan het niet rechtstreeks lezen. Door het veld private te houden en een getter aan te bieden, kan Person zijn eigen gegevens beheren. Voor protected toegang tot instanties over pakketgrenzen heen geldt een extra beperking: code in een subklasse benadert het lid via zichzelf of een referentie van het eigen subklassetype, niet via een willekeurige superklassereferentie.

Test de relatie voordat je uitbreidt

Java staat één directe superklasse per klasse toe. Een keten zoals GraduateStudent extends Student en Student extends Person is geldig; extends Student, Teacher is dat niet. Een klasse zonder expliciete superklasse breidt Object uit. Object zelf heeft geen superklasse.

Gebruik de is-een-test: kan elk subklasseobject worden gebruikt waar de superklasse wordt verwacht? Een vrachtwagen is een voertuig. Een klant heeft een adres, maar een klant is geen adres. Gebruik voor die tweede relatie compositie:

class Customer {
    private Address address;
}

Hier is Address een andere klasse. Via het veld kan een klant een adres gebruiken zonder te beweren er zelf één te zijn. Als je een klasse alleen uitbreidt om een handige methode te gebruiken, erf je ook het hele publieke contract ervan. Tot slot kan een final-klasse niet worden uitgebreid. Omdat String final is, compileert class ShortString extends String niet.

Belangrijkste punten

  • Een subklasseobject bevat de toestand van de superklasse en alle toestand die de subklasse toevoegt.
  • Private superklassevelden worden benaderd via de methoden van de superklasse.
  • Een klasse breidt één directe superklasse uit; constructoren worden niet overgeërfd.
  • Gebruik overerving voor een is-een-relatie en compositie voor een heeft-een-relatie.
  • Een final-klasse kan niet worden uitgebreid.

1.2 Geef waarden door via een constructorketen

Een subklasseconstructor moet de constructie van het superklassedeel van hetzelfde object regelen. Hij kan een superklasseconstructor aanroepen met super(...), of de taak doorgeven aan een andere constructor in de eigen klasse met this(...).

Kies de superklasseconstructor
class Person {
    private final String name;
    Person(String name) { this.name = name; }
}
class Student extends Person {
    private final String studentId;
    Student(String name, String studentId) {
        super(name);
        this.studentId = studentId;
    }
    Student(String name) {
        this(name, "no id yet");
    }
}

De constructor met twee argumenten geeft de naam door aan Person en slaat zelf de identificatie op. De constructor met één argument geeft de taak aan die constructor door. Een constructor bevat hoogstens één expliciete constructoraanroep: this(...) of super(...). Als je de this(...)-keten volgt, kom je uiteindelijk bij een constructor die super(...) aanroept.

Als geen van beide is geschreven, voegt Java super() in. Dat mislukt hier omdat Person alleen een constructor heeft die een naam vereist. Zodra je een constructor declareert, levert Java geen standaardconstructor zonder parameters. Een klasse zonder gedeclareerde constructor krijgt een standaardconstructor die super() aanroept.

new Person(name) schrijven binnen Student doet iets anders: het construeert een afzonderlijk object. Het initialiseert niet de persoonstoestand van de huidige student en vervangt ook niet de vereiste aanroep van de superklasseconstructor.

Volg de initialisatie in volgorde
class Base {
    Base() { System.out.println("Base ready"); }
}
class Middle extends Base {
    Middle() { System.out.println("Middle ready"); }
}
class Top extends Middle {
    Top() { super(); System.out.println("Top ready"); }
}

new Top() doorloopt de constructorketen tot aan Object. Als de aanroepen terugkeren, is de uitvoer “Base ready”, “Middle ready” en dan “Top ready”. De veldinitialisatoren van elke klasse worden uitgevoerd nadat de superklasseconstructor is teruggekeerd en vóór de rest van de constructorbody van die klasse.

Een overschrijfbare methode die door een superklasseconstructor wordt aangeroepen, kan al een overschrijving in een subklasse uitvoeren. Als die overschrijvende methode een referentieveld van de subklasse leest waarvan de toewijzing nog niet is uitgevoerd, ziet ze null, zelfs als het veld een initialisator bij de declaratie heeft. Gebruik constructoren om de toestand te initialiseren; vermijd aanroepen van methoden die subklassen kunnen overschrijven.

Herken de wijziging in Java 25

In Java 25 kan een constructor parameters valideren vóór zijn expliciete constructoraanroep:

Student(String name, String studentId, int credits) {
    if (credits < 0) {
        throw new IllegalArgumentException("negative credits");
    }
    super(name);
    this.studentId = studentId;
}

Java 25 staat ook eenvoudige vroege toewijzingen toe aan velden die in de huidige klasse zonder initialisator bij de declaratie zijn gedeclareerd. Het blijft verboden om vóór de aanroep de velden van het huidige object te lezen, de instantiemethoden ervan aan te roepen of this door te geven. Houd bij standaardtaalversies van vóór Java 25 super(...) of this(...) als eerste. Java 22–24 bood eerdere vormen als voorlopige functies.

Belangrijkste punten

  • super(...) construeert het superklassedeel van het huidige object.
  • Een impliciete super() vereist een toegankelijke superklasseconstructor zonder parameters.
  • this(...) geeft de taak door binnen dezelfde klasse; de keten moet een superklasseconstructor bereiken.
  • Veldinitialisatoren worden uitgevoerd nadat de superklasseconstructor is teruggekeerd.
  • Java 25 staat beperkt werk toe vóór een expliciete constructoraanroep.
  • Een overschreven methode die tijdens de constructie wordt aangeroepen, kan subklassevelden lezen voordat daaraan waarden zijn toegewezen.
2

Overschrijf methoden en gebruik de klasse Object

Overgeërfde methoden geven verwante objecten een gemeenschappelijk uitgangspunt. Door methoden te overschrijven kan elke subklasse op haar eigen manier op dezelfde aanroep reageren, terwijl de methoden die van Object zijn overgeërfd bepalen hoe een object wordt afgedrukt en met andere objecten wordt vergeleken. Kleine verschillen in signaturen zijn hier van belang: een methode kan er voor een lezer goed uitzien, maar toch niet de methode vervangen die bibliotheekcode daadwerkelijk aanroept. Ook gelijkheid kent beloften waarop collecties op basis van hashcodes vertrouwen.

2.1 Vervang overgeërfd gedrag met een overschrijvende methode

Een overschrijvende methode geeft een subklasse een eigen implementatie van een overgeërfde instantiemethode. De naam en parametertypen komen overeen met die van de oorspronkelijke methode, zodat aanroepers dezelfde operatie kunnen blijven gebruiken.

Breid een werkende methode uit

Stel dat de jaarbeloning van een werknemer uit twaalf maandbetalingen bestaat. Een manager ontvangt daarnaast een jaarlijkse bonus:

class Employee {
    private final double monthlySalary;
    Employee(double monthlySalary) {
        this.monthlySalary = monthlySalary;
    }
    public double yearlyPay() { return 12 * monthlySalary; }
}
class Manager extends Employee {
    private final double bonus;
    Manager(double monthlySalary, double bonus) {
        super(monthlySalary);
        this.bonus = bonus;
    }
    @Override
    public double yearlyPay() {
        return super.yearlyPay() + bonus;
    }
}

Voor new Manager(3000.0, 5000.0) geeft de aanroep van de superklassemethode 36.000,0 terug. Als je daar 5.000,0 bij optelt, krijg je 41.000,0. super.yearlyPay() kiest bewust de implementatie van de superklasse. De superklassemethode blijft beschikbaar binnen de subklasse, ook al voeren gewone aanroepen op de manager de overschrijvende methode uit.

Als je return yearlyPay() + bonus; schrijft, roep je de eigen methode van de manager opnieuw aan. Geen enkele aanroep wordt voltooid, zodat de uitvoering uiteindelijk mislukt met StackOverflowError. De ontbrekende super. verandert de betekenis van de aanroep.

Laat de compiler de signatuur controleren

Zet @Override boven elke overschrijvende methode. Een spelfout zoals yearlypay() of een extra parameter veroorzaakt dan een compilatiefout. Zonder de annotatie kan Java een nieuwe methode accepteren die aanroepers nooit gebruiken.

Een geldige overschrijving volgt deze regels:

  • De parametertypen komen precies overeen.
  • Het retourtype is hetzelfde, of een subtype bij een objectretourtype. Die toegestane versmalling heet een covariant retourtype.
  • De toegang is minstens even open als die van de overgeërfde methode.
  • De methode voegt geen gecontroleerde excepties toe die de overgeërfde declaratie verbiedt.

Zo kan public String toString() niet worden vervangen door String toString() met pakkettoegang. Bestaande aanroepers moeten toegang houden.

Onderscheid overschrijven van overladen en verbergen
DeclaratieWat die doet
Dezelfde instantiemethodesignatuur in een subklasseOverschrijft overgeërfd gedrag
Dezelfde naam, andere parameterlijstVoegt een overladen methodevariant toe
Dezelfde statische methodesignatuur in een subklasseVerbergt een statische methode

role() en role(String language) zijn overladen methodevarianten; de tweede vervangt de eerste niet. De keuze tussen overladen methoden gebruikt tijdens de compilatie de gedeclareerde argumenttypen. Statische methoden worden ook gekozen op basis van het type dat tijdens de compilatie wordt gebruikt, dus worden ze niet dynamisch overschreven.

Een private methode wordt niet overgeërfd; een methode met dezelfde naam in de subklasse staat daar los van. Een final-methode kan niet worden overschreven en constructoren kunnen niet worden overschreven. Naast compileren moet de methode nog steeds haar oorspronkelijke doel vervullen. Een overschrijvende methode van een manager die de maandbeloning teruggeeft, zou code die jaarbeloningen optelt verstoren, ook als de signatuur correct was.

Belangrijkste punten

  • Een overschrijvende methode komt overeen met de naam en parametertypen van een overgeërfde instantiemethode.
  • @Override spoort onbedoeld overladen methoden en spelfouten op.
  • super.method() hergebruikt de implementatie van de superklasse.
  • Een overschrijvende methode mag de toegang niet beperken of de toegestane gecontroleerde excepties uitbreiden.
  • Bij overladen veranderen de parameters; statische methoden worden verborgen en final-methoden kunnen niet worden overschreven.

2.2 Geef objecten bruikbare tekst en bepaal hun klasse

Elke Java-klasse breidt Object direct of indirect uit. Arrays zijn ook objecten. Deze gemeenschappelijke basis biedt methoden zoals toString(), equals(Object), hashCode() en getClass(), ook als een klasse er zelf geen declareert.

Vervang de standaardbeschrijving bij het afdrukken

De overgeërfde Object.toString() levert een klassenaam, @ en de hashcode van het object in hexadecimale notatie. Tekst zoals “school.Person@1b6d3586” geeft de klasse aan, maar zegt niets over de gegevens van de persoon. Het getal kan per uitvoering veranderen en is geen geheugenadres dat door Java wordt gegarandeerd.

Een bruikbare overschrijvende methode toont de velden die lezers nodig hebben. Hier heeft een studentobject getName() overgeërfd en bevat het een veld credits:

@Override
public String toString() {
    return getName() + " (" + credits + " credits)";
}

Voor een student met de naam Ada Lovelace en 10 studiepunten drukt System.out.println(s) “Ada Lovelace (10 credits)” af. De afdrukaanroep haalt automatisch de tekst van het object op. Stringconcatenatie doet dit ook: "Student: " + s bevat dezelfde beschrijving.

Voor een null-referentie leveren afdrukken en concatenatie “null” op. Een expliciete aanroep s.toString() wanneer s null is, werpt NullPointerException op, omdat er geen object is dat die aanroep kan ontvangen.

Als Person.toString() al de naam van de persoon teruggeeft, kan een subklasse daarop voortbouwen:

@Override
public String toString() {
    return super.toString() + ", student id " + studentId;
}

Voor identificatie “S-17” levert dat “Ada Lovelace, student id S-17” op. Hoe bruikbaar super.toString() is, hangt af van wat de superklasse werkelijk teruggeeft. Als die nog de versie van Object gebruikt, draagt die de standaardtekst met klasse en hashcode bij.

Houd identiteit en klasse-informatie gescheiden

Object.equals(Object) vergelijkt identiteit: de methode geeft waar terug als beide referenties naar hetzelfde object verwijzen. Twee afzonderlijke new Person("Ada")-objecten zijn volgens die overgeërfde implementatie niet gelijk, ook al komen hun namen overeen. Klassen zoals String, Integer en ArrayList bieden overschrijvende methoden op basis van waarde.

getClass() geeft de werkelijke klasse tijdens de uitvoering:

Person p = new Student("Ada Lovelace");
System.out.println(p.getClass().getSimpleName());

Dit drukt “Student” af als Student de getoonde constructor heeft. Het gedeclareerde type van de variabele verandert de klasse van het object niet. getClass() is final en kan niet worden overschreven.

hashCode() ondersteunt collecties op basis van hashcodes. Andere methoden van Object zijn de protected methode clone() en de methoden wait(), notify() en notifyAll() voor coördinatie van threads.

Controleer de exacte spelling

Java maakt onderscheid tussen toString() en tostring(). Een methode toString(String prefix) is een overladen methodevariant, dus wordt die bij het afdrukken niet aangeroepen. Beide fouten worden door @Override opgevangen. Een tekstrepresentatie bepaalt ook niet de gelijkheid: twee objecten kunnen dezelfde tekst afdrukken terwijl ze bij vergelijking toch ongelijk zijn.

Belangrijkste punten

  • Object biedt gemeenschappelijke methoden voor alle Java-objecten, inclusief arrays.
  • Afdrukken en stringconcatenatie gebruiken toString() en geven een null-referentie weer als “null”.
  • Object.equals vergelijkt identiteit totdat een klasse de methode overschrijft.
  • getClass() geeft de werkelijke klasse terug en is final.

2.3 Houd gelijkheid en hashcodes consistent

Twee objecten kunnen dezelfde waarde vertegenwoordigen zonder hetzelfde object te zijn. Een boekencatalogus kan twee boekobjecten als gelijk behandelen als hun cataloguscode en editie overeenkomen. Definieer die regel door equals(Object) te overschrijven en houd de hashcode daarmee consistent.

Vergelijk de velden die de waarde bepalen
import java.util.Objects;

final class Book {
    private final String code;
    private final int edition;
    Book(String code, int edition) {
        this.code = code;
        this.edition = edition;
    }
    @Override
    public boolean equals(Object other) {
        if (this == other) return true;
        if (!(other instanceof Book b)) return false;
        return edition == b.edition && Objects.equals(code, b.code);
    }
    @Override
    public int hashCode() {
        return Objects.hash(code, edition);
    }
}

De identiteitscontrole accepteert hetzelfde object snel. De typecontrole wijst een niet-gerelateerd object of null af en introduceert daarna b als een Book. Voor primitieve editiewaarden wordt == gebruikt; Objects.equals vergelijkt codes veilig, zelfs als een code null is. Twee boeken met code “B-1042” en editie 2 zijn bij vergelijking gelijk. Editie 3 levert een andere waarde op.

De parameter moet Object zijn. Als je equals(Book other) schrijft, voeg je een overladen methodevariant toe; de overgeërfde equals(Object) blijft bestaan. Bibliotheekcode die die signatuur aanroept, zou nog steeds identiteit vergelijken. Door @Override mislukt de verkeerde declaratie tijdens de compilatie.

Volg de beloften van gelijkheid

Voor referenties die niet null zijn, moet gelijkheid zijn:

  • Reflexief: een object is gelijk aan zichzelf.
  • Symmetrisch: als je de twee objecten omwisselt, krijg je hetzelfde antwoord.
  • Transitief: als het ene gelijk is aan een tweede en het tweede aan een derde, is het eerste gelijk aan het derde.
  • Consistent: herhaalde vergelijkingen geven hetzelfde resultaat zolang de vergeleken gegevens onveranderd blijven.

Een object moet ook ongelijk zijn aan null. Gelijke objecten moeten gelijke hashcodes teruggeven. Ongelijke objecten mogen een hashcode delen: een hashbotsing bewijst geen gelijkheid. Leid de hashcode af uit dezelfde velden als de gelijkheidsbepaling, zoals Objects.hash(code, edition) hier doet.

Een hashcollectie gebruikt de hashcode om kandidaten te vinden voordat ze de gelijkheid controleert. Als je alleen equals overschrijft, kan een verzameling afzonderlijke gelijke boeken behouden of kan een map een gelijke sleutel mogelijk niet vinden. Als je een veld verandert dat voor de hashcode wordt gebruikt terwijl het object als sleutel is opgeslagen, kan dat object onder zijn oude hashcode opgeborgen blijven.

Bescherm symmetrie tussen subklassen

Stel dat een uitbreidbare Book elke instanceof Book accepteert, terwijl Ebook bestandsgrootte toevoegt aan de gelijkheidsbepaling en alleen e-books accepteert. Een boek kan een e-book met dezelfde code en editie accepteren, maar het e-book wijst het boek af. De vergelijking heeft haar symmetrie verloren.

Eén aanpak vereist overeenkomende werkelijke klassen, waarbij je vóór getClass() op null controleert. Een andere maakt de gelijkheidsbepaling van de superklasse final, zodat subklassen de gelijkheidsregel niet kunnen uitbreiden. Een final-waardeklasse, zoals hierboven, vermijdt gelijkheidsbepaling tussen subklassen volledig. Records genereren gelijkheidsbepaling en hashcodes uit hun componenten; enumconstanten hebben al een gelijkheidsbepaling op basis van identiteit die bij hun vaste waarden past.

Belangrijkste punten

  • Overschrijf equals(Object), niet een overladen methodevariant met de klasse als parameter.
  • Gelijkheid moet reflexief, symmetrisch, transitief en consistent zijn; vergelijking met null is onwaar.
  • Gelijke objecten hebben gelijke hashcodes nodig; een gedeelde hashcode bewijst geen gelijkheid.
  • Houd velden voor gelijkheidsbepaling stabiel terwijl een object een sleutel in een hashcollectie of een element van een verzameling is.
  • Een superklasse en subklasse mogen geen gelijkheidsregels gebruiken die bij omkering van de operanden tegenstrijdige uitkomsten geven.
  • Records en enums bieden geschikt ingebouwd gedrag voor gelijkheidsbepaling.
3

Definieer contracten met abstracte klassen en interfaces

Sommige verwante klassen delen velden en werkende methoden; andere klassen delen alleen een functionaliteit. Abstracte klassen en interfaces drukken die verschillende relaties uit. Een tekenprogramma kan gemeenschappelijke vormgegevens in een abstracte klasse bewaren, terwijl een oplaadprogramma via een interface met niet-verwante apparaten werkt. Met beide aanpakken kan de compiler vereist gedrag controleren voordat het programma wordt uitgevoerd, en Java biedt precieze regels wanneer overgeërfde implementaties met elkaar concurreren.

3.1 Deel toestand en vereis gedrag van subklassen

Een abstracte klasse is een basisklasse waarvan je niet rechtstreeks een instantie kunt maken. Ze kan gemeenschappelijke velden en werkende methoden bevatten en tegelijk geselecteerde operaties overlaten aan subklassen om aan te vullen.

Laat een vereiste operatie abstract
abstract class Shape {
    private final String name;
    Shape(String name) { this.name = name; }
    public abstract double area();
    public String describe() {
        return name + " with area " + area();
    }
}
class Rectangle extends Shape {
    private final double width;
    private final double height;
    Rectangle(double width, double height) {
        super("rectangle");
        this.width = width;
        this.height = height;
    }
    @Override
    public double area() { return width * height; }
}

area() heeft een signatuur en een puntkomma, zonder body. De methode legt vast wat elke concrete vorm moet aanbieden. Omdat Shape een abstracte methode bevat, moet de klasse ook abstract zijn. Rectangle levert de ontbrekende implementatie en je kunt er een instantie van maken.

Voor new Rectangle(3.0, 4.0) slaat de superklasseconstructor “rectangle” op. De subklasseconstructor slaat de afmetingen op. De oppervlakte is 12,0 vierkante meter als de afmetingen in meters zijn. Een aanroep van describe() geeft “rectangle with area 12.0” terug. De aanroep van area() in de overgeërfde methode voert de implementatie van de rechthoek uit.

Gebruik constructoren en concrete methoden op de gewone manier

Een abstracte klasse kan velden, constructoren en methoden met een body hebben, met de gebruikelijke toegangsniveaus. Haar constructor wordt uitgevoerd als onderdeel van het maken van een subklasseobject. new Shape() is verboden, ook als er een passende constructor bestaat; new Rectangle(3.0, 4.0) is geldig.

Een tweede subklasse kan dezelfde overgeërfde beschrijving gebruiken terwijl ze de oppervlakte anders berekent. Een cirkelimplementatie geeft Math.PI * radius * radius terug. Voor straal 1,5 m is de berekening π maal 2,25, ongeveer 7,07 vierkante meter. De gedeelde methode describe() heeft geen cirkelspecifieke tak nodig.

Een variabele kan nog steeds het abstracte type hebben: Shape s = new Rectangle(3.0, 4.0);. Java verbiedt het construeren van de abstracte klasse, niet het verwijzen naar een concreet object via dat type.

Maak ontbrekend werk zichtbaar als compilatiefout

Elke concrete subklasse moet implementaties van alle abstracte methoden aanbieden of overerven. Een subklasse die sommige methoden onvoltooid laat, moet ook als abstract worden gedeclareerd. Als een tijdelijke invulling van area() 0 zou teruggeven, zou een onvolledige concrete subklasse kunnen compileren en ongemerkt verkeerde resultaten geven. Door area() abstract te declareren, maak je dat ontbrekende werk zichtbaar voor de compiler.

Abstracte methoden kunnen niet private, static of final zijn: elk van die modifiers zou de overschrijving in de subklasse verhinderen die de operatie levert. Een abstracte klasse mag geen abstracte methoden bevatten; de modifier verhindert dan alleen rechtstreekse constructie. Gebruik dit soort basisklasse als haar subklassen slagen voor de is-een-test en baat hebben bij gedeelde toestand of code.

Belangrijkste punten

  • Een abstracte klasse kan niet rechtstreeks worden geconstrueerd, maar haar constructor wordt uitgevoerd voor subklasseobjecten.
  • Een abstracte methode heeft geen body en vereist een concrete implementatie.
  • Een concrete subklasse moet elke overgeërfde abstracte methode aanvullen.
  • Een abstract type kan worden gebruikt voor variabelen, parameters en lijsten.
  • Abstracte klassen kunnen gemeenschappelijke velden, werkende methoden en vereiste operaties combineren.

3.2 Geef niet-verwante klassen een gemeenschappelijk contract

Een interface benoemt een functionaliteit die verschillende klassen kunnen bieden. Ze legt vast welke methoden via die functionaliteit beschikbaar zijn, zonder dat de klassen dezelfde instantievelden of superklasse hoeven te delen.

Declareer en implementeer de methoden
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
}
class Phone implements Chargeable {
    private int battery;
    Phone(int battery) { this.battery = battery; }
    @Override
    public int batteryPercent() { return battery; }
    @Override
    public void charge(int minutes) {
        battery = Math.min(100, battery + 2 * minutes);
    }
}

De abstracte methoden van de interface zijn impliciet public. De implementerende methoden moeten expliciet public zijn; als je die modifier weglaat, wordt de toegang beperkt en mislukt de compilatie. Een concrete klasse moet geschikte public implementaties van elke abstracte methode aanbieden of overerven. Een abstracte implementerende klasse mag sommige methoden overlaten aan haar subklassen.

Voor een telefoon op 35 procent voegt charge(20) 40 procentpunten toe en komt de lading uit op 75 procent. Nog eens 20 minuten zou vóór de begrenzing 115 opleveren, dus slaat Math.min 100 op. Een ElectricScooter kan dezelfde interface implementeren en daarbij slechts één punt per minuut toevoegen: vanaf 50 procent bereikt de scooter na 20 minuten 70. De interface legt de beschikbare operatie vast, terwijl de klasse het gedrag ervan levert.

Gebruik de interface als type
Chargeable device = new Phone(35);
device.charge(20);
System.out.println(device.batteryPercent());

Dit drukt 75 af. Een aanroeper kan de methoden van de interface en de public methoden gebruiken die op elk object beschikbaar zijn. Operaties die alleen bij de telefoon horen, zijn niet beschikbaar via device, tenzij de gedeclareerde interface die aanbiedt.

Interfaces kunnen ook het type zijn van parameters, retourwaarden en lijstelementen. Een methode die Chargeable accepteert, kan met een telefoon of een scooter werken zonder de indeling van hun velden te kennen. De Java-bibliotheek gebruikt dezelfde aanpak: List heeft implementaties zoals ArrayList en LinkedList; Runnable declareert run(); Comparable declareert een natuurlijke vergelijking.

Combineer meer dan één functionaliteit

Een klasse breidt één klasse uit en mag meerdere interfaces implementeren. De volgorde is class Phone extends Device implements Chargeable, Connectable. Een interface kan meerdere interfaces uitbreiden: interface SmartDevice extends Chargeable, Connectable. Implementerende klassen moeten dan alle overgeërfde abstracte operaties aanbieden.

Een interface heeft geen constructoren en geen instantievelden. new Chargeable() compileert niet. Een gedeclareerd veld zoals int FULL = 100; is impliciet een public static final constante, te lezen als Chargeable.FULL.

Een abstracte interfacemethode levert geen werkende code. De implementatie kan uit de klasse zelf komen of uit een overgeërfde public superklassemethode. Default-methoden zijn de interfacemethoden die overgeërfd instantiegedrag kunnen leveren.

Belangrijkste punten

  • Een interface is een type dat een gemeenschappelijke functionaliteit beschrijft.
  • Een concrete implementerende klasse moet geschikte public implementaties aanbieden of overerven.
  • Een klasse kan meerdere interfaces implementeren en tegelijk één klasse uitbreiden.
  • Interfaces hebben geen constructoren of instantievelden; hun velden zijn constanten.
  • Via het interfacetype kunnen aanroepers het contract gebruiken voor niet-verwante klassen.

3.3 Gebruik gemeenschappelijk interfacegedrag en los conflicten op

Interfaces kunnen zowel werkende code als abstracte methodedeclaraties aanbieden. De modifier van de methode bepaalt of implementerende objecten de methode overerven, aanroepers de interfacenaam gebruiken of alleen code binnen de interface de methode kan gebruiken.

Bouw standaardgedrag op uit het contract
interface Chargeable {
    int batteryPercent();
    void charge(int minutes);
    default boolean needsCharge() {
        return batteryPercent() < 20;
    }
    static int clamp(int percent) {
        return Math.max(0, Math.min(100, percent));
    }
}

Een default-methode is een public instantiemethode met een body. Een telefoon die 15 procent meldt, overerft needsCharge() en geeft waar terug. Bij 20 procent geeft de methode onwaar terug. De default-methode roept de abstracte operatie batteryPercent() aan; die leest geen privaat veld van een telefoon. Een klasse mag de methode overschrijven met een eigen public implementatie.

Een statische interfacemethode hoort bij de interface: Chargeable.clamp(130) geeft 100 terug en Chargeable.clamp(-5) geeft 0 terug. Implementerende klassen overerven de methode niet, dus Phone.clamp(130) is geen geldige aanroep van deze methode. Default-methoden en statische interfacemethoden zijn ingevoerd in Java 8.

Sinds Java 9 mag een interface ook privémethoden declareren om hulpcode tussen haar eigen methoden te delen. Een private instantiehulpmethode ondersteunt default-methoden; een private statische hulpmethode kan ook statische methoden ondersteunen. Implementerende klassen kunnen deze hulpmethoden niet rechtstreeks aanroepen.

Los conflicten tussen default-methoden op

Twee niet-verwante interfaces kunnen dezelfde signatuur aanbieden:

interface Lamp {
    default String describe() { return "lamp"; }
}
interface Speaker {
    default String describe() { return "speaker"; }
}
class SmartLamp implements Lamp, Speaker {
    @Override
    public String describe() {
        return Lamp.super.describe() + " and "
             + Speaker.super.describe();
    }
}

Zonder de overschrijvende methode heeft SmartLamp een conflict en compileert de klasse niet. De gekwalificeerde aanroepen InterfaceName.super.method() selecteren de afzonderlijke default-methoden van de directe superinterfaces. Deze implementatie geeft “lamp and speaker” terug.

Concurrerende implementatiesRegel
Overgeërfde public concrete superklassemethode en default-methode van een interfaceDe klassemethode wint
Default-methode van een interface en van een interface die deze uitbreidtDe specifiekere interface wint
Default-methoden van niet-verwante interfacesDe klasse moet het conflict oplossen

Ook een abstracte declaratie in een superklasse kan vereisen dat een concrete subklasse de methode implementeert; een default-methode van een interface neemt die verplichting niet weg.

Houd rekening met bestaande implementerende klassen

Als je een abstracte methode toevoegt, moet elke concrete implementerende klasse een passende implementatie aanbieden of overerven. Een default-methode voorkomt die wijziging vaak door werkend gedrag te bieden, maar kan een conflict met een andere overgeërfde methode veroorzaken. Default-methoden helpen een interface zich te ontwikkelen; de overervingsregels bepalen nog steeds of de resulterende klassen compileren.

Belangrijkste punten

  • Een default-methode biedt overschrijfbaar public instantiegedrag.
  • Roep een statische interfacemethode aan via de interfacenaam.
  • Privé-interfacemethoden zijn hulpmethoden die alleen binnen de interface zichtbaar zijn.
  • Een concrete klassemethode heeft voorrang op een default-methode van een interface.
  • Conflicterende default-methoden van niet-verwante interfaces vereisen een overschrijving; anders wint de default-methode van de specifiekere interface.
  • Het toevoegen van een default-methode kan conflicten tussen overgeërfde methoden veroorzaken.

3.4 Kies een gemeenschappelijke basis, een functionaliteit of beide

Een abstracte klasse en een interface kunnen allebei vereiste methoden definiëren. De keuze hangt af van de relatie tussen objecten en van de vraag of hun implementatie een gedeelde instantietoestand nodig heeft.

Vergelijk wat beide bieden
VraagAbstracte klasseInterface
Kan die velden per object opslaan?JaNee
Kan die constructoren hebben?JaNee
Kan die werkende instantiemethoden aanbieden?JaJa, met default-methoden
Hoeveel kan een klasse er rechtstreeks gebruiken?Eén superklasseMeerdere geïmplementeerde interfaces
Welke toegangsniveaus zijn beschikbaar?Alle normale niveausPublic methoden en private hulpmethoden

Interfacevelden zijn public static final constanten, geen gegevens per object. Private interfacehulpmethoden ondersteunen de eigen werkende methoden van de interface; ze maken geen private toestand voor elk implementerend object.

Zet nauw verwante toestand in een abstracte klasse

Een tekenprogramma heeft cirkels, rechthoeken en driehoeken. Elke vorm slaat een naam en een positie op en berekent de oppervlakte op een andere manier. Een abstracte Shape kan de gemeenschappelijke velden en constructor bewaren, werkende methoden voor beweging of beschrijving aanbieden en area() abstract declareren.

Alle concrete vormen zijn soorten Shape, dus andere code kan om een vorm vragen en elk van deze vormen ontvangen. Zo kan ook een abstracte Enemy gezondheidspunten en een gemeenschappelijke methode takeDamage(int points) bevatten en de beweging overlaten aan specifieke vijandklassen. De gemeenschappelijke toestand geeft deze basisklassen nuttig werk.

Zet een breed bruikbare functionaliteit in een interface

Hetzelfde tekenprogramma kan vormen, tekstlabels en afbeeldingen exporteren. Exporteren maakt van een tekstlabel geen vorm en deze objecten kunnen al verschillende superklassen nodig hebben. Een interface Exportable kan String exportText() declareren. Elke klasse implementeert de operatie met haar eigen gegevens.

Een klasse mag de twee rollen combineren: class Circle extends Shape implements Exportable. De superklasse biedt de toestand van de vorm en gemeenschappelijke code; de interface maakt de cirkel bruikbaar voor exportcode. Een klasse die meerdere interfaces implementeert, belooft meerdere functionaliteiten zonder instantievelden van die interfaces over te erven.

Combineer een contract met een gedeeltelijke implementatie

Een abstracte klasse kan een interface implementeren en slechts enkele methoden voltooien. Concrete subklassen vullen de resterende operaties aan. List en AbstractList van Java gebruiken deze opzet; ArrayList breidt AbstractList uit. Aanroepers kunnen op de interface vertrouwen terwijl implementerende klassen waar passend een basisklasse hergebruiken.

Met List<String> names = new ArrayList<>(); kunnen aanroepers bijvoorbeeld namen toevoegen, lezen en verwijderen via List. Als je de initialisator vervangt door new LinkedList<>(), blijven die aanroepen werken. Een parameter die als ArrayList<String> is gedeclareerd, zou een LinkedList<String> afwijzen, ook als die alleen lijstoperaties nodig heeft.

Een abstracte klasse met alleen abstracte methoden neemt nog steeds de enige superklasseplaats van de subklasse in. Ze is niet uitwisselbaar met een interface. Kies de interface als alleen het gemeenschappelijke contract nodig is en voeg een basisklasse toe als verwante implementaties baat hebben bij gedeelde toestand of code.

Belangrijkste punten

  • Gebruik een abstracte klasse voor verwante soorten objecten die toestand of code delen.
  • Gebruik een interface voor een functionaliteit die niet-verwante klassen kunnen bieden.
  • Een klasse kan tegelijk een abstracte basisklasse uitbreiden en interfaces implementeren.
  • Een gedeeltelijke abstracte implementatie kan een interface ondersteunen zonder aanroepers tot die basisklasse te beperken.
4

Schrijf flexibele programma’s met polymorfisme

Het type van een variabele en de klasse van een object beantwoorden verschillende vragen. Het gedeclareerde type van de variabele bepaalt welke aanroepen compileren; de werkelijke klasse van het object bepaalt welke overschrijvende methode wordt uitgevoerd. Door die beslissingen gescheiden te houden, kun je onbekende code volgen, casts veilig gebruiken en lussen schrijven die met nieuwe implementaties werken. Dezelfde aanpak ondersteunt bibliotheekinterfaces zoals Comparable, die objecten een natuurlijke sorteervolgorde geeft.

4.1 Onderscheid het referentietype van de objectklasse

Polymorfisme laat een algemene referentie naar objecten van verschillende subtypen verwijzen. Het object behoudt zijn volledige toestand en gedrag; de referentie geeft aanroepers een bepaalde kijk op de beschikbare operaties.

Benoem de twee typen
Person p = new Student("Ada Lovelace", "S-17");

Het gedeclareerde type, ook statisch type genoemd, is Person: het staat in de variabeledeclaratie. Het werkelijke type, of uitvoeringstype, is Student: dat is de klasse waarvan new een instantie maakt. De werkelijke klasse van het object verandert niet na de constructie.

Een studentreferentie toewijzen aan een persoonsreferentie is een upcast. Die gaat automatisch en is veilig omdat elke student een persoon is. Naar dezelfde student kan ook worden verwezen met een Object-variabele. Een implementerend object kan via upcasting worden toegewezen aan een interface die het implementeert, zoals in Chargeable c = new Phone(35);.

Door de toewijzing worden geen studentgegevens verwijderd. Er is nog steeds één studentobject met zijn studentidentificatie, overgeërfde persoonsgegevens en overschreven methoden. Als je het een algemenere referentie geeft, verandert wat de compiler via die referentie toegankelijk maakt.

Controleer aanroepen aan de hand van het gedeclareerde type

Als Person getName() declareert, compileert p.getName(). Als alleen Student getStudentId() declareert, compileert p.getStudentId() niet. De compiler controleert de publieke interface van Person, ook als de initialisator duidelijk een student maakt.

De omgekeerde toewijzing Student s = p; wordt niet geaccepteerd zonder expliciete typeconversie. Een persoonsreferentie kan verwijzen naar een persoon die geen student is. Die beperking beschermt code tegen het behandelen van een object als een subtype dat het niet heeft.

Gedeclareerd typeAanroepen beschikbaar via de referentie
ObjectPublic operaties die Object biedt
PersonToegankelijke operaties van Person, inclusief overgeërfde operaties
StudentToegankelijke studentoperaties en overgeërfde operaties
Laat parameters en lijsten meerdere subtypen accepteren

Een methode static void printCard(Person person) kan een Person-, Student- of Teacher-object ontvangen, aangenomen dat de laatste twee Person uitbreiden. De code kan vertrouwen op het contract van de persoon. Ook een retourtype kan algemeen zijn, bijvoorbeeld bij een methode die een Shape teruggeeft.

Een lijst met een algemeen elementtype kan verschillende vormklassen opslaan:

List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1.5));
shapes.add(new Rectangle(3.0, 4.0));
for (Shape shape : shapes) {
    System.out.println(shape.area());
}

Hier komen List en ArrayList uit java.util en biedt elke concrete vorm area() aan. De lus gebruikt alleen die gemeenschappelijke operatie.

Een List<Circle> is geen List<Shape>. Als dat wel zo was, zou code die de lijst als vormenlijst ontvangt, een rechthoek kunnen toevoegen aan een lijst die beloofd is alleen cirkels te bevatten. Als een methode List<Shape> vereist, maak de lijst dan vanaf het begin met dat elementtype.

Belangrijkste punten

  • Het gedeclareerde type bepaalt welke operaties via een referentie toegankelijk zijn.
  • Het werkelijke type is de klasse van het object en verandert niet na de constructie.
  • Upcasting naar een superklasse of geïmplementeerde interface gaat automatisch en is veilig.
  • Een algemene parameter of een algemeen lijstelementtype kan meerdere concrete subtypen ondersteunen.
  • List<Circle> kan niet als List<Shape> worden gebruikt.

4.2 Volg de selectie van overladen methoden en dynamische binding

Een methodeaanroep wordt in twee fasen afgehandeld. Tijdens de compilatie controleert Java de gedeclareerde typen en kiest het een methodesignatuur. Tijdens de uitvoering kiest dynamische binding de overschrijvende instantiemethode voor de werkelijke klasse van het object.

Volg een aanroep binnen een overgeërfde methode
class A {
    String who() { return "A"; }
    String hello() { return "Hello from " + who(); }
}
class B extends A {
    @Override String who() { return "B"; }
}
class C extends B { }

Voor A x = new C(); is x.hello() toegestaan omdat A hello() declareert. Klasse C overschrijft hello() niet en B doet dat evenmin, dus voert Java de implementatie in A uit.

Binnen die methode betekent who() hetzelfde als this.who(). De ontvanger is nog steeds het C-object. Java zoekt vanaf C en vindt vervolgens de dichtstbijzijnde implementatie van who() in B. Het resultaat is “Hello from B”. Als een overgeërfde methode wordt uitgevoerd, verandert de ontvanger niet in een object van de superklasse.

Kies een overladen methode vóór een overschrijvende methode
static String kind(Object value) { return "object"; }
static String kind(String value) { return "string"; }

Binnen een klasse die deze methoden bevat, kiest Object value = "hi"; kind(value) voor kind(Object) en geeft “object” terug. Het gedeclareerde type van de variabele is Object; Java bekijkt tijdens de uitvoering de overladen methoden niet opnieuw op basis van de werkelijke string.

Bij een instantieaanroep kan dynamische binding nog steeds een overschrijving van de al gekozen signatuur selecteren. Ze kan niet overschakelen naar een andere overladen methodevariant die alleen specifieker lijkt voor het werkelijke argument. Bepaal eerst de signatuur en zoek daarna de overschrijvende methode.

Herken aanroepen en leden die anders worden geselecteerd
Lid of aanroepWat bepaalt de selectie?
Overschreven instantiemethodeWerkelijke klasse van de ontvanger, na selectie van de signatuur
Statische methodeType dat tijdens de compilatie voor de aanroep wordt gebruikt
Verborgen veldGedeclareerd type van de referentie
Private methodeCode van de klasse die de methode declareert
super.method()Implementatie van de superklasse

Als P String label = "P"; declareert en Q extends P zijn eigen String label = "Q"; declareert, leest P p = new Q(); p.label “P”. Velden worden verborgen, niet overschreven. Beide velden bestaan in het object. Gebruik overgeërfde veldnamen liever niet opnieuw, omdat het referentietype ervoor kan zorgen dat toegangen die er hetzelfde uitzien verschillende toestand lezen.

super.method() kiest de implementatie van de superklasse, inclusief een implementatie die de superklasse overerft. Als de gekozen methode zelf een gewone aanroep op this doet, kan die interne aanroep nog steeds worden gebonden aan een overschrijvende methode van een subklasse.

Belangrijkste punten

  • Tijdens de compilatie wordt de aanroepbare signatuur vastgelegd, inclusief de keuze tussen overladen methoden.
  • Dynamische binding kiest de dichtstbijzijnde overschrijving van die signatuur, gezocht vanaf de werkelijke klasse omhoog.
  • Een gewone aanroep binnen een overgeërfde methode gebruikt nog steeds de werkelijke ontvanger.
  • Velden, statische methoden, private methoden en expliciete super-aanroepen gebruiken niet de gewone dynamische selectie van overschrijvende methoden.

4.3 Controleer een subtype voordat je de specifieke methoden gebruikt

Een algemene referentie heeft soms toegang nodig tot een operatie die maar bij één subtype hoort. Een downcast vertelt de compiler dat de referentie als dat subtype moet worden behandeld. Tijdens de uitvoering controleert Java of het object al dat type heeft.

Begrijp wat een expliciete typeconversie verandert

Stel dat Person p naar een student verwijst. Student s = (Student) p; maakt s.getStudentId() mogelijk. Beide variabelen verwijzen nog steeds naar hetzelfde object. De conversie kopieert het object niet, verandert de werkelijke klasse niet en voegt geen studentvelden toe aan een gewone persoon.

Als p werkelijk naar new Person("Grace Hopper") verwijst, werpt die conversie ClassCastException op. Een compiler accepteert de mogelijke relatie van persoon naar student, maar het object moet daar tijdens de uitvoering nog steeds aan voldoen. Een expliciete typeconversie tussen klassen die niet verwant kunnen zijn, zoals een conversie van een Person-referentie naar de final-klasse Integer, mislukt tijdens de compilatie.

Dit verschilt van een primitieve conversie: (int) 3.9 maakt de gehele waarde 3. Een expliciete conversie van een referentie controleert en verandert alleen het type waarmee een bestaande referentie wordt gebruikt.

Combineer de test en de nieuwe variabele

Sinds Java 16 maakt patroonherkenning het veilige geval compact:

static void showStudentId(Person person) {
    if (person instanceof Student student) {
        System.out.println(student.getStudentId());
    }
}

De test is waar voor een student of een subklasse van student. De variabele student is beschikbaar binnen het blok, waar bekend is dat de test is geslaagd. Een gewone persoon slaat het blok over. Een null-referentie maakt de test ook onwaar, zodat het blok nooit probeert een methode op null aan te roepen.

De oudere vorm test person instanceof Student en declareert daarna Student student = (Student) person; binnen de tak voor een geslaagde test. Beide vormen beschermen de expliciete typeconversie. Een test van een niet-gerelateerd object elders beschermt een conversie van person niet; de test en de conversie moeten over dezelfde referentie gaan.

Een upcast heeft geen expliciete typeconversie nodig. Person person = student; compileert al en kan niet mislukken door een typeverschil. Een expliciete conversie van null naar een compatibel referentietype levert null op; als je dat resultaat gebruikt voor een aanroep van een instantiemethode, mislukt die nog steeds met NullPointerException.

Bepaal of een typetest nodig is

Typetests passen bij code die een Object ontvangt, zoals equals(Object other), of een operatie nodig heeft die alleen bij een subtype hoort, zoals een studentidentificatie. Ze kunnen ook helpen bij het werken met klassen die niet kunnen worden gewijzigd.

Een herhaalde keten van typetests voor een gemeenschappelijke operatie is een ander probleem. Als code elke vormklasse controleert om haar oppervlakte te berekenen, vereist elke toegevoegde vorm een extra tak. Door area() in Shape te declareren en subklassen die te laten implementeren, kan de aanroeper in plaats daarvan shape.area() gebruiken.

Een expliciete typeconversie kan een ontbrekende gemeenschappelijke operatie niet voor elk subtype beschikbaar maken. Zet gemeenschappelijk gedrag in het gemeenschappelijke contract en bewaar een subtypetest voor werk dat werkelijk dat specifieke subtype vereist.

Belangrijkste punten

  • Een downcast controleert een bestaand object; die verandert het object niet.
  • Een mogelijke expliciete typeconversie kan compileren en tijdens de uitvoering toch ClassCastException opwerpen.
  • instanceof is onwaar voor null en waar voor instanties van het type of de subtypen ervan.
  • Een instanceof-patroon introduceert een veilig getypeerde variabele waar vaststaat dat de test waar is.
  • Gemeenschappelijke operaties horen in het gemeenschappelijke type; operaties die alleen bij een subtype horen, kunnen een typetest vereisen.

4.4 Schrijf algemene lussen en definieer een natuurlijke volgorde

Een methode blijft bruikbaar voor nieuwe klassen als haar parameter precies de operaties beschikbaar maakt die de methode nodig heeft. De aanroeper levert objecten; dynamische binding levert hun afzonderlijke gedrag.

Tel oppervlakten op zonder klassespecifieke takken
static double totalArea(List<Shape> shapes) {
    double total = 0;
    for (Shape shape : shapes) {
        total += shape.area();
    }
    return total;
}

Bij een cirkel met straal 1,5 m, een rechthoek met afmetingen 3,0 m en 4,0 m en een driehoek met basis 4,0 m en hoogte 2,5 m zijn de oppervlakten ongeveer 7,06858, 12,0 en 5,0 vierkante meter. Als je de niet-afgeronde oppervlakte van de cirkel meetelt, krijg je ongeveer 24,07 vierkante meter. Elk object levert zijn eigen area().

Het toevoegen van een klasse Triangle vereist geen wijziging in de lus. De nieuwe klasse implementeert de bestaande vormoperatie. Een negatief resultaat zou het oppervlaktecontract schenden, ook als de overschrijvende methode compileerde; algemene code vertrouwt zowel op de betekenis van de operatie als op haar signatuur.

Dezelfde aanpak werkt met een interface. Een methode chargeAll(List<Chargeable> devices, int minutes) doorloopt apparaten en roept device.charge(minutes) aan. Na 20 minuten bereikt de telefoon die op 35 procent begint 75 procent en de scooter die op 50 procent begint 70 procent. De lus kiest nooit zelf een laadregel.

Laat aanroepers een lijstimplementatie kiezen

Een parameter List<Shape> accepteert zowel een ArrayList<Shape> als een LinkedList<Shape>. Een parameter ArrayList<Shape> wijst de gekoppelde lijst af, hoewel alleen lijstoperaties nodig zijn. Gebruik het meest algemene type dat de vereiste methoden biedt; alles als Object declareren zou te algemeen zijn omdat dat type area() en lijstoperaties mist.

Maak objecten sorteerbaar met Comparable

Comparable<T> definieert de natuurlijke volgorde van een object. Het resultaat van compareTo is negatief als dit object vóór het andere komt, nul als ze dezelfde sorteerrang hebben en positief als het erna komt. Het teken van het resultaat is van belang; het hoeft niet precies −1 of 1 te zijn.

record Runner(String name, int seconds)
        implements Comparable<Runner> {
    @Override
    public int compareTo(Runner other) {
        return Integer.compare(seconds, other.seconds);
    }
}

Een wijzigbare lijst met Mia op 3.605 s, Ken op 3.490 s en Ana op 3.720 s wordt na Collections.sort(runners) Ken, Mia, Ana. list.sort(null) gebruikt ook de natuurlijke volgorde. Bouw een wijzigbare lijst met new ArrayList<>(List.of(...)); een poging om het niet-wijzigbare resultaat van List.of(...) te sorteren werpt UnsupportedOperationException op.

Gebruik Integer.compare in plaats van de waarden af te trekken. Aftrekken kan bij grote waarden met tegengestelde tekens overlopen en het verkeerde teken teruggeven. Hetzelfde idee geldt voor Double.compare bij kommavelden. In dit voorbeeld met hardlopers hebben gelijke tijden dezelfde sorteerrang, ook als de namen verschillen. Dezelfde sorteerrang en objectgelijkheid hoeven dus niet hetzelfde te betekenen. Geef de voorkeur aan een natuurlijke volgorde waarvan het nulresultaat overeenkomt met gelijkheid; documenteer een bewust verschil.

Belangrijkste punten

  • Algemene code roept een gemeenschappelijke operatie aan en laat elk object de implementatie ervan leveren.
  • Een parameter van een interfacetype kan verschillende implementaties van hetzelfde contract accepteren.
  • compareTo gebruikt een negatief resultaat, nul of een positief resultaat om de volgorde uit te drukken.
  • Integer.compare voorkomt het risico op overloop bij het aftrekken van gehele velden.
  • Sorteren vereist een wijzigbare lijst; List.of(...) alleen levert een niet-wijzigbare lijst.
  • Dezelfde sorteerrang kan verschillen van waardegelijkheid als de vergelijking minder velden gebruikt.
5

Modelleer vaste waarden en gegevens met enums en records

Sommige programma’s hebben een vaste verzameling keuzemogelijkheden nodig, terwijl andere een kleine groep waarden nodig hebben die bij elkaar horen. Enums geven keuzemogelijkheden een eigen type; records leveren veel van de routinecode voor gegevensobjecten. Beide passen in het typesysteem van Java en kunnen interfaces implementeren. Hun ingebouwde gedrag bespaart werk, maar de keuze tussen deze typen en een gewone klasse hangt nog steeds af van wat kan veranderen en wat twee waarden gelijk maakt.

5.1 Geef vaste keuzemogelijkheden weer met een enum

Een enum geeft een vaste verzameling benoemde keuzemogelijkheden een eigen type. enum Size { SMALL, MEDIUM, LARGE } maakt Size size = Size.MEDIUM; mogelijk. Het voorkomt dat een aanroeper de verkeerd gespelde string “MEDUIM” of het gehele getal 7 doorgeeft aan een methode die Size vereist.

Gebruik namen, constanten en switch

Elke enumconstante is één object, dus size == Size.LARGE is een geschikte vergelijking. Een enumvariabele kan ook null zijn. Size.values() geeft alle constanten in declaratievolgorde terug; Size.valueOf("LARGE") vindt de exacte naam. Het opzoeken is hoofdlettergevoelig: “large” veroorzaakt IllegalArgumentException.

name() geeft de gedeclareerde naam terug. ordinal() geeft de positie terug, geteld vanaf nul, dus Size.MEDIUM.ordinal() is 1. Als je een constante invoegt, veranderen latere posities. Daarom is een volgnummer ongeschikt als opgeslagen code of bedrijfswaarde. Standaard geeft toString() ook de naam terug.

Een switch-expressie, standaard sinds Java 14, kan een waarde teruggeven:

String label = switch (size) {
    case SMALL -> "S";
    case MEDIUM -> "M";
    case LARGE -> "L";
};

Door elke constante te noemen, is de expressie volledig zonder default-tak. Deze gevallen gaan ervan uit dat size niet null is; het opsommen van de constanten handelt null niet af.

Geef elke constante betekenisvolle gegevens
enum CupSize {
    SMALL(250), MEDIUM(350), LARGE(500);
    private final int milliliters;
    CupSize(int milliliters) { this.milliliters = milliliters; }
    public int milliliters() { return milliliters; }
}

De puntkomma scheidt de lijst met constanten van de leden. De constructor wordt één keer voor elke constante uitgevoerd wanneer de enum wordt geïnitialiseerd. Hij is impliciet private en aanroepers kunnen geen nieuwe constanten maken met new CupSize(400).

Twee grote bekers en één kleine beker bevatten 1.250 mL, oftewel 1,25 L. Die berekening gebruikt het veld milliliters, niet de posities van de constanten. Als je CupSize.values() doorloopt en elke constante en inhoud afdrukt, krijg je “SMALL: 250 mL”, “MEDIUM: 350 mL” en “LARGE: 500 mL”.

Geef constanten eigen gedrag

Een enum kan een interface implementeren en mag afzonderlijke constanten verschillende methodebody’s geven:

enum Operation {
    ADD { int apply(int a, int b) { return a + b; } },
    MULTIPLY { int apply(int a, int b) { return a * b; } };
    abstract int apply(int a, int b);
}

Operation.MULTIPLY.apply(6, 7) geeft 42 terug. Elke constante vult de abstracte operatie aan. Enums breiden impliciet java.lang.Enum uit; ze kunnen geen andere klasse uitbreiden en gewone klassen kunnen enums niet uitbreiden.

Gebruik een enum voor keuzemogelijkheden die vaststaan wanneer het programma wordt geschreven, zoals richtingen of bestelstatussen. De veranderende productlijst van een winkel hoort bij de gegevens: producten toevoegen zou niet moeten vereisen dat je een enumdeclaratie herschrijft en opnieuw compileert.

Belangrijkste punten

  • Een enum geeft een vaste verzameling constanten een afzonderlijk type.
  • values() behoudt de declaratievolgorde en valueOf zoekt de exacte naam.
  • Sla betekenisvolle getallen op in velden, omdat volgnummers kunnen veranderen.
  • Enumconstructoren initialiseren constanten; aanroepers kunnen new niet gebruiken om constanten toe te voegen.
  • Enums kunnen interfaces implementeren en constanten verschillend gedrag geven.

5.2 Bewaar waarden in een record en valideer die

Een record is een beknopte gegevensklasse, standaard sinds Java 16. De kop bevat de waarden, componenten genoemd, die elke instantie bepalen: record Point(int x, int y) { }.

Gebruik de gegenereerde leden

De compiler levert voor elke component een private final-veld, een canonieke constructor met die componenten in volgorde en public accessors die ernaar zijn genoemd. Ook levert de compiler equals, hashCode en toString op basis van de componenten.

Point a = new Point(3, 4);
Point b = new Point(3, 4);
System.out.println(a.x());
System.out.println(a.equals(b));
System.out.println(a == b);

Dit drukt 3, true en false af op afzonderlijke regels. Overeenkomende componentwaarden maken de records gelijk, maar de twee new-expressies maken afzonderlijke objecten. Gelijke records hebben gelijke hashcodes. Hun bruikbare gegenereerde tekst bevat de namen van het record en de componenten; huidige JDK’s tonen “Point[x=3, y=4]”. Dat exacte tekstformaat is niet gegarandeerd, dus parse het niet als gegevensformaat.

Accessors zijn x() en y(), niet getX() en getY(). Een setter die this.x = x toewijst, compileert niet omdat het componentveld final is. Een methode kan in plaats daarvan een nieuwe waarde teruggeven: Point moveRight(int dx) { return new Point(x + dx, y); }.

Valideer in een compacte constructor
record Temperature(double celsius) {
    Temperature {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("below absolute zero");
        }
    }
}

Een compacte constructor herhaalt de naam van het record zonder parameterlijst. Hij controleert of normaliseert de binnenkomende parameters; Java wijst de componentvelden na de body toe. Deze controle wijst −300 °C af en accepteert 21,5 °C. Een tekstcomponent kan vóór de automatische toewijzing worden genormaliseerd met name = name.trim();.

Een public record heeft een public compacte constructor nodig. Records mogen ook statische velden, statische methoden en instantiemethoden declareren. Een extra constructor geeft de taak met this(...) door aan de canonieke constructor. Extra instantievelden buiten de componenten zijn verboden.

Bescherm veranderlijke componentobjecten

Final componentvelden maken de onveranderlijkheid ondiep. Een record dat een veranderlijke lijst bevat, verwijst nog steeds naar die lijst:

record Team(String name, List<String> members) {
    Team {
        members = List.copyOf(members);
    }
}

Als java.util.List is geïmporteerd, slaat dit een niet-wijzigbare kopie op. Latere toevoegingen aan de oorspronkelijke lijst beïnvloeden het lidmaatschap van het team niet en team.members().add("Lee") werpt UnsupportedOperationException op. De kopie beschermt de lijststructuur; ze maakt veranderlijke elementobjecten niet diep onveranderlijk.

Records breiden impliciet java.lang.Record uit en zijn final. Ze kunnen geen andere klasse uitbreiden en niet worden uitgebreid, maar kunnen interfaces zoals Comparable implementeren. Gebruik ze voor gegevens die door hun waarden worden bepaald; gebruik een gewone klasse voor veranderende toestand, extra verborgen instantievelden of een uitbreidbare klassenhiërarchie.

Belangrijkste punten

  • Een recordkop definieert componenten en de gegenereerde constructor, accessors en waardemethoden.
  • Recordaccessors gebruiken componentnamen zoals x().
  • Een compacte constructor controleert of normaliseert parameters vóór automatische toewijzing aan velden.
  • Referenties in recordcomponenten zijn final, maar objecten waarnaar ze verwijzen kunnen veranderlijk blijven.
  • Een niet-wijzigbare kopie van een lijst beschermt de lijststructuur van een record.
  • Een record is final, kan niet expliciet een klasse uitbreiden en kan interfaces implementeren.

Overerving in Java: voorbeelden

5 Walkthroughs

1

Bouw stap voor stap een apparaat met overerving

12 stappen
Maak een apparaat met een label, voeg een sensorsubtype toe en volg de constructie van hetzelfde object.
  1. 1
    IntroductieEén object met een algemene en een specifieke rol

    Een sensor is een soort apparaat. Elk apparaat heeft een label nodig, terwijl een sensor ook een meeteenheid nodig heeft. Met overerving beheert het apparaat zijn label en voegt de sensor zijn eenheid toe.

    We bouwen die relatie op, gebruiken een overgeërfde methode en volgen hoe constructoraanroepen elk deel initialiseren. De belangrijkste vraag is welke klasse elk veld beheert en dus welke constructor er een waarde aan moet toewijzen.

  2. 2
    VoorbeeldBegin met de toestand die elk apparaat nodig heeft
    class Device {
        private final String label;
        Device(String label) { this.label = label; }
        public String label() { return label; }
    }
    

    Device beheert zijn private label. Andere code leest het via label(). Een subklasse zal die public methode overerven, maar kan niet rechtstreeks een waarde aan het private veld toewijzen. Zo blijft de initialisatie van het label op één plek.

  3. 3
    VoorbeeldVoeg alleen de sensorspecifieke toestand toe
    class Sensor extends Device {
        private final String unit;
        Sensor(String label, String unit) {
            super(label);
            this.unit = unit;
        }
        public String unit() { return unit; }
    }
    

    Sensor breidt één klasse uit en voegt één veld toe. De aanroep super(label) geeft de verantwoordelijkheid voor het label door aan Device; de sensorconstructor slaat de eenheid zelf op.

  4. 4
    KernpuntLees beide delen via de sensor

    Na Sensor s = new Sensor("Room", "C"); geeft s.label() “Room” terug en s.unit() “C”. De eerste methode is overgeërfd; de tweede hoort bij Sensor.

    Beide aanroepen gaan over hetzelfde object. Door Device uit te breiden is geen tweede apparaatobject naast de sensor gemaakt. Het private label is aanwezig, ook al benadert sensorcode het via een methode.

  5. 5
    UitdagingKies de relatie voor een laboratorium

    Een Lab bevat meerdere sensoren. Moet Lab Sensor uitbreiden of referenties naar sensoren bewaren? Leg de relatie uit.

    ---reveal---

    Bewaar sensorreferenties in velden of een collectie. Een laboratorium heeft sensoren maar is zelf geen sensor, dus compositie past. Als het laboratorium Sensor zou uitbreiden, zou dat betekenen dat het overal kan worden gebruikt waar een sensor vereist is, ook bij operaties die de eenheid van één sensor beschrijven.

  6. 6
    UitlegGa de superklasseconstructor binnen

    De aanroep new Sensor("Room", "C") kiest de sensorconstructor met twee argumenten. De aanroep super(label) daarin roept Device(String) aan voor datzelfde object.

    Device bereikt eerst zijn eigen superklasse, Object, en wijst daarna het label toe. Wanneer Device terugkeert, worden de initialisatie van sensorvelden en de rest van de sensorconstructorbody uitgevoerd. Tot slot komt de nieuwe referentie beschikbaar voor de aanroeper.

  7. 7
    TipZoek de ontbrekende constructor zonder parameters

    Verwijder super(label) uit de sensorconstructor en Java voegt super() in. Voor die aanroep is geen passende constructor: Device declareert alleen Device(String).

    De constructorfout gaat over argumenten, niet over het private veld. Als je de aanroep vervangt door new Device(label), maak je een niet-gerelateerd object en blijft het probleem met de impliciete super() bestaan. Geef het label in plaats daarvan door aan de superklasseconstructor.

  8. 8
    VoorbeeldDelegeer voor een optionele standaardeenheid

    Voeg deze constructor toe binnen Sensor:

    Sensor(String label) {
        this(label, "unknown");
    }
    

    new Sensor("Hall") geeft de taak door aan de bestaande constructor met twee argumenten, die super(label) aanroept. Het resulterende object heeft label “Hall” en eenheid “unknown”. Schrijf niet ook super(...) in de delegerende constructor: één constructor heeft hoogstens één expliciete constructoraanroep.

  9. 9
    VerbandValideer een parameter vóór de constructie

    Standaard Java 25 staat beperkt werk toe vóór een expliciete constructoraanroep. Zo kan een sensorconstructor een binnenkomend leeg label afwijzen voordat hij super(label) aanroept.

    Die vroege code mag parameters gebruiken, maar kan geen velden van het object lezen of de instantiemethoden ervan aanroepen. Beperkte eenvoudige toewijzingen aan velden van de huidige klasse zonder initialisator bij de declaratie zijn ook toegestaan. Houd de aanroep bij standaardtaalversies van oudere versies als eerste.

  10. 10
    UitdagingVoorspel een constructorbericht

    In een afzonderlijke versie drukt Device “device” af na de eigen superklasseaanroep. Sensor drukt “sensor” af na super(label). Welk bericht verschijnt als eerste bij het maken van een sensor en waarom?

    ---reveal---

    “device” verschijnt als eerste. De sensorconstructor roept de apparaatconstructor aan voordat hij doorgaat naar zijn eigen afdrukinstructie. Het object is tijdens de hele constructie een sensor; deze berichten tonen de volgorde van het constructorwerk, geen veranderingen in de klasse.

  11. 11
    UitdagingHerstel een toewijzing aan een overgeërfd veld

    Een nieuwe subklasse probeert het private apparaatlabel te initialiseren met this.label = label; en laat super(label) weg. Welke twee samenhangende problemen veroorzaakt dit?

    ---reveal---

    De subklasse kan niet rechtstreeks een waarde aan het private veld toewijzen. Ook is er geen passende apparaatconstructor voor de impliciete super(). Gebruik super(label) zodat Device zijn eigen private toestand toewijst en wijs daarna alleen waarden toe aan velden die in de subklasse zijn gedeclareerd.

  12. 12
    SamenvattingZoek de oorzaak van een constructiefout

    Als een subtype niet kan worden geconstrueerd, zoek dan de eerste constructoraanroep waarvoor geen passend toegankelijk doel bestaat. Controleer daarna of elke klasse de velden initialiseert die ze beheert.

    Houd naast dat verloop ook de relatietest aan: een subtype is bruikbaar als zijn basistype; een ingesloten object hoort in een veld. Deze controles richten zich op verschillende soorten ontwerp- en compilerfouten.

2

Druk catalogusitems correct af en vergelijk ze

14 stappen
Overschrijf gedrag, bouw een methode voor waardegelijkheid en koppel die aan hashcodes.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieBepaal wat een catalogusitem moet betekenen
  2. 2
    VoorbeeldBreid een basisbeschrijving uit
  3. 3
    TipVoorkom dat de overschrijvende methode zichzelf aanroept
  4. 4
    UitdagingControleer een schijnbare vervanging
  5. 5
    VoorbeeldGeef het afdrukken een exacte methode om aan te roepen
  6. 6
    KernpuntLees de werkelijke klasse zonder die te veranderen
  7. 7
    UitdagingOnderzoek afgedrukte tekst die op de standaarduitvoer lijkt
  8. 8
    VoorbeeldDeclareer de gegevens voor gelijkheidsbepaling
  9. 9
    VoorbeeldWijs het verkeerde object af voordat je velden vergelijkt
  10. 10
    VoorbeeldBereken de hashcode uit dezelfde velden voor gelijkheidsbepaling
  11. 11
    UitdagingHerken de overladen methodevariant die een collectie mist
  12. 12
    KernpuntBescherm vergelijkingen in beide richtingen
  13. 13
    UitdagingOnderscheid gelijke tekst van gelijke waarden
  14. 14
    SamenvattingControleer een waardeklasse via haar publieke interface
3

Bouw een contract en deel de implementatie

18 stappen
Gebruik een abstracte bron, voeg een interface toe en los een conflict tussen overgeërfde default-methoden op.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieOnderscheid de gemeenschappelijke gegevens van de gemeenschappelijke functionaliteit
  2. 2
    VoorbeeldBegin een abstracte berichtenbron
  3. 3
    VoorbeeldVul de ontbrekende operatie aan
  4. 4
    UitdagingBepaal of een subklasse concreet kan zijn
  5. 5
    VoorbeeldBenoem lezen als functionaliteit
  6. 6
    VerbandLaat de bestaande basisklasse aan de interface voldoen
  7. 7
    VoorbeeldSchrijf een aanroeper voor het contract
  8. 8
    UitdagingHerstel een implementatie met beperktere toegang
  9. 9
    VoorbeeldVoeg een bruikbare default-operatie toe
  10. 10
    UitlegHoud statische en private hulpmethoden bij hun rol
  11. 11
    VoorbeeldMaak een werkelijk conflict tussen default-methoden
  12. 12
    VoorbeeldKies expliciet een default-methode
  13. 13
    UitdagingGeef de klasse-implementatie voorrang
  14. 14
    KernpuntBied een basis zonder elke implementatie tot gebruik te verplichten
  15. 15
    UitdagingPlaats een label en een constructor in het juiste type
  16. 16
    TipControleer nieuwe default-methoden naast overgeërfde methoden
  17. 17
    UitdagingVoeg een nieuwe lezer toe zonder de bestaande superklasse te veranderen
  18. 18
    SamenvattingBeoordeel een voorgesteld gemeenschappelijk type
4

Volg een rapportaanroep via algemene typen

17 stappen
Volg één ontvanger door de keuze tussen overladen methoden, dynamische binding tijdens de uitvoering en een veilige subtypetest, en bouw daarna een algemene lus.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieVolg wat de variabele belooft en wat het object doet
  2. 2
    VoorbeeldGeef een gespecialiseerd rapport een algemene referentie
  3. 3
    UitlegControleer wat via de referentie beschikbaar is
  4. 4
    UitdagingKies typen voor een gemengde rapportenlijst
  5. 5
    VoorbeeldBepaal eerst welke methode de buitenste aanroep uitvoert
  6. 6
    VoorbeeldBepaal welke methode de binnenste aanroep op dezelfde ontvanger uitvoert
  7. 7
    VoorbeeldKies een overladen methode op basis van het gedeclareerde argumenttype
  8. 8
    UitdagingOnderscheid twee beslissingen in één aanroep
  9. 9
    TipMarkeer velden en statische aanroepen afzonderlijk in een uitvoeringsverloop
  10. 10
    VoorbeeldVraag veilig om de operatie die alleen bij verkooprapporten hoort
  11. 11
    UitdagingVoorspel een expliciete typeconversie als het object verandert
  12. 12
    VoorbeeldVerzamel kopteksten via de gemeenschappelijke operatie
  13. 13
    VerbandGebruik vergelijking als een andere gemeenschappelijke operatie
  14. 14
    UitdagingSorteer waarden met een gekozen lijstimplementatie
  15. 15
    KernpuntGebruik subtypetests voor subtypespecifiek werk
  16. 16
    UitdagingOndersteun een auditkoptekst zonder nieuwe tak
  17. 17
    SamenvattingVolg een onbekende aanroep in volgorde
5

Bouw een vaste keuze en een beschermde gegevenswaarde

15 stappen
Voeg enumgegevens toe, maak een record en voorkom dat veranderlijke lijstcomponenten ongemerkt veranderen.

Sla deze les op om elke Walkthrough stap voor stap te volgen.

  1. 1
    IntroductieKies tussen benoemde opties en gegroepeerde gegevens
  2. 2
    VoorbeeldGeef servicekeuzes een eigen type
  3. 3
    VoorbeeldKoppel bruikbare getallen aan de keuzemogelijkheden
  4. 4
    TipHoud de servicegegevens onafhankelijk van de declaratievolgorde
  5. 5
    UitdagingVoorspel het resultaat van opzoeken en een bruikbaar veld
  6. 6
    VerbandHoud gedrag bij de vaste keuze
  7. 7
    VoorbeeldDeclareer de componenten van een leveringsverzoek
  8. 8
    VoorbeeldNormaliseer binnenkomende gegevens vóór de toewijzing
  9. 9
    UitdagingVergelijk de genormaliseerde verzoeken
  10. 10
    VoorbeeldZoek het veranderlijke object binnen een final-component
  11. 11
    VoorbeeldSla een niet-wijzigbare kopie op
  12. 12
    TipGeef een nieuw record terug als waarden moeten veranderen
  13. 13
    UitdagingKies de juiste oplossing voor een veranderende route
  14. 14
    UitdagingModelleer nieuwe serviceopties en klantverzoeken
  15. 15
    SamenvattingOnderzoek gegenereerd gedrag voordat je code toevoegt

Overerving in Java: oefenvragen

50 Oefenvragen in 5 sets

Set 1: Bouw klassen en volg overgeërfd gedrag10 Oefenvragen

Vraag 1 van 10

Een Library slaat meerdere Shelf-objecten op en elke boekenplank bevat boeken. Hoe moet Library de relatie met Shelf weergeven?
  1. Breid Shelf uit om plankmethoden over te erven.
  2. Sla boekenplanken op in velden of een collectie.
  3. Maak van Shelf een subklasse van Library.
  4. Maak van Library een final-subklasse van Shelf.
Toon antwoordVerberg antwoord
Antwoord: B
Een bibliotheek heeft boekenplanken, dus velden of een collectie modelleren de relatie via compositie. Als de bibliotheek Shelf zou uitbreiden, zou dat ook betekenen dat elke bibliotheek een boekenplank is en overal kan worden gebruikt waar een boekenplank wordt verwacht. Het hergebruiken van plankmethoden maakt die is-een-relatie niet waar.

Vraag 2 van 10

Account declareert een privaat veld balance en een public methode getBalance(). SavingsAccount extends Account. Hoe kan een methode in SavingsAccount de overgeërfde rekeningtoestand lezen?
  1. Lees het veld als this.balance.
  2. Lees het veld als super.balance.
  3. Maak een rekening en lees het saldo ervan.
  4. Roep de overgeërfde methode getBalance() aan.
Toon antwoordVerberg antwoord
Antwoord: D
De public getter wordt overgeërfd en kan het private veld binnen Account lezen. De private toestand hoort nog steeds bij het spaarrekeningobject, maar code in de subklasse kan dat veld niet rechtstreeks benaderen. Door super. toe te voegen, omzeil je de regel voor private toegang niet.

Vraag 3 van 10

Wat gebeurt er als deze code wordt gecompileerd?

class Machine {
    Machine(int serial) { }
}
class Printer extends Machine {
    Printer() { }
}
  1. Compilatie mislukt omdat super() geen passende constructor heeft.
  2. Compilatie slaagt en gebruikt standaard Machine(0).
  3. Compilatie slaagt zonder het machinedeel te construeren.
  4. Compilatie mislukt omdat subklassen geen constructoren zonder parameters mogen hebben.
Toon antwoordVerberg antwoord
Antwoord: A
De lege printerconstructor roept impliciet super() aan. Machine declareert alleen een constructor die een geheel getal ontvangt, dus er bestaat geen constructor zonder parameters. Java verzint geen argumentwaarde; Printer() kan zonder parameters blijven als de body expliciet een serienummer aan super doorgeeft.

Vraag 4 van 10

Wat wordt afgedrukt door new Leaf();?

class Root {
    Root() { System.out.print("R"); }
}
class Branch extends Root {
    Branch() { System.out.print("B"); }
}
class Leaf extends Branch {
    Leaf() { this(7); System.out.print("L"); }
    Leaf(int n) { System.out.print(n); }
}
  1. L7BR
  2. RBL7
  3. RB7L
  4. RRB7L
Toon antwoordVerberg antwoord
Antwoord: C
Leaf() geeft de taak door aan Leaf(int), waarvan de impliciete superklasseaanroep eerst Branch en daarna Root binnengaat. Hun body’s drukken “R” en “B” af, daarna drukt Leaf(int) 7 af en drukt de delegerende constructor “L” af. Door de delegatie wordt het rootdeel niet voor een tweede keer geconstrueerd.

Vraag 5 van 10

Meter declareert public int read(). Een subklasse schrijft @Override public int read(int offset). Waarom mislukt de compilatie?
  1. Het retourtype moet bij elke overschrijving in een subklasse veranderen.
  2. De annotatie verhindert dat public methoden worden overgeërfd.
  3. De parameterlijst verandert, dus de methode is een overladen variant.
  4. De subklasse moet vóór het overschrijven elk veld opnieuw declareren.
Toon antwoordVerberg antwoord
Antwoord: C
De overgeërfde methode ontvangt geen argumenten, terwijl de nieuwe declaratie een geheel getal ontvangt. Dat is een andere signatuur, dus de methode overlaadt read in plaats van die te overschrijven. @Override vangt het verschil op; het retourtype int is verenigbaar met de oorspronkelijke methode.

Vraag 6 van 10

Wat drukt de laatste instructie af?

class Parcel {
    public String toString() { return "parcel"; }
}
class ChilledParcel extends Parcel {
    @Override public String toString() {
        return super.toString() + " cold";
    }
}
Parcel item = new ChilledParcel();
System.out.println("Send " + item);
  1. “Send parcel”
  2. “Send parcel cold”
  3. “Send ChilledParcel cold”
  4. “Send null cold”
Toon antwoordVerberg antwoord
Antwoord: B
Concatenatie haalt de tekst van het object op via toString() en het werkelijke object voor een gekoeld pakket voert zijn overschrijvende methode uit. Die methode haalt “parcel” op via super.toString() en voegt “ cold” toe. Als je de referentie als Parcel declareert, onderdruk je de overschrijvende methode niet.

Vraag 7 van 10

Een final-klasse Label heeft alleen public boolean equals(Label other), die haar tekst vergelijkt, en overerft equals(Object). Twee afzonderlijke labels hebben dezelfde tekst. Beide referenties zijn opgeslagen in variabelen die als Object zijn gedeclareerd. Wat geeft a.equals(b) terug?
  1. false, omdat de gekozen methode nog steeds identiteit vergelijkt.
  2. true, omdat gelijke tekst de overladen Label-methode kiest.
  3. true, omdat Object elk veld automatisch vergelijkt.
  4. Compilatie mislukt omdat Object geen gelijkheidsmethode heeft.
Toon antwoordVerberg antwoord
Antwoord: A
Bij Object-referenties kiest de aanroep equals(Object), die Label niet heeft overschreven. De overgeërfde implementatie vergelijkt de twee afzonderlijke objecten op identiteit en geeft onwaar terug. De gelijknamige methode die Label ontvangt, is een overladen methodevariant en wordt niet gekozen op basis van de klasse van het argument tijdens de uitvoering.

Vraag 8 van 10

abstract class Sensor declareert public abstract double reading();. Wat moet een concrete LightSensor extends Sensor aanbieden of overerven?
  1. Een public constructor met de naam Sensor.
  2. Een static implementatie van double reading().
  3. Een private implementatie van double reading().
  4. Een public implementatie van double reading().
Toon antwoordVerberg antwoord
Antwoord: D
Een concrete subklasse moet de overgeërfde abstracte operatie aanvullen met een verenigbare instantiemethode. De implementatie moet de public toegang behouden die Sensor belooft. Een statische methode is geen overschrijvende instantiemethode en een private methode zou de vereiste toegang beperken.

Vraag 9 van 10

Waarom compileert deze klasse niet?

interface Resettable { void reset(); }
class Counter implements Resettable {
    void reset() { }
}
  1. reset() moet static zijn in Counter.
  2. reset() moet public zijn in Counter.
  3. Resettable heeft een constructorbody nodig.
  4. Counter moet Resettable uitbreiden.
Toon antwoordVerberg antwoord
Antwoord: B
De abstracte methode reset() van de interface is impliciet public. De implementatie heeft pakkettoegang, waardoor ze de beloofde toegang beperkt en de compilatie mislukt. Een klasse implementeert een interface; als je de relatie verandert in extends, herstel je de methodedeclaratie niet.

Vraag 10 van 10

Wat geeft new TimedJob().cost() terug?

class Job {
    public int rate() { return 3; }
    public int cost() { return 4 * rate(); }
}
class TimedJob extends Job {
    @Override public int rate() { return 5; }
    @Override public int cost() { return super.cost() + 2; }
}
  1. 22
  2. 14
  3. 20
  4. 28
Toon antwoordVerberg antwoord
Antwoord: A
De overschrijvende methode gaat eerst via super.cost() naar Job.cost(). De gewone aanroep van rate() daarin wordt nog steeds uitgevoerd op het object van de getimede taak en geeft dus 5 terug; de overgeërfde berekening geeft 20 en de buitenste overschrijvende methode telt er 2 bij op. Als je 3 voor de binnenste aanroep gebruikt, schakel je dynamische binding ten onrechte uit voor de hele superklassemethode.
Set 2: Gebruik contracten en algemene referentietypen10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een TextDocument en een niet-verwante klasse Image moeten allebei exportText() ondersteunen. Elk breidt al een andere klasse uit. Welk gemeenschappelijk type geeft deze functionaliteit het best weer?
  1. Een klasse Exportable die beide als tweede superklasse uitbreiden.
  2. Een superklasse Image die ook tekstdocumenten bevat.
  3. Een interface Exportable die beide implementeren.
  4. Een variabele Object die exportText() rechtstreeks aanbiedt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Compileert deze code en waarom?

interface Named { String name(); }
class Entity {
    public String name() { return "entity"; }
}
class Asset extends Entity implements Named { }
  1. Ja; de overgeërfde public methode voldoet aan het interfacecontract.
  2. Nee; elke implementerende klasse moet elke interfacemethode opnieuw declareren.
  3. Nee; de superklasse moet expliciet dezelfde interface implementeren.
  4. Ja; Java genereert een lege body voor ontbrekende interfacemethoden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Wat geeft new Screen().label() terug?

interface Tagged {
    default String label() { return "tagged"; }
}
class Panel {
    public String label() { return "panel"; }
}
class Screen extends Panel implements Tagged { }
  1. “tagged”
  2. “paneltagged”
  3. De declaraties veroorzaken een compilatiefout.
  4. “panel”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Fast en Safe zijn niet-verwante interfaces, elk met default String mode(). Een concrete Service implementeert beide en overerft geen klasse-implementatie van mode(). Wat moet Service doen?
  1. Vertrouwen op de interface die als eerste na implements staat.
  2. mode() overschrijven om het overgeërfde conflict op te lossen.
  3. Beide interfacemethoden binnen de klassebody hernoemen.
  4. Service.super.mode() aanroepen vanuit de constructor.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Robot implementeert Movable. Wat is in Movable unit = new Robot(); het gedeclareerde type van unit?
  1. Robot
  2. Movable
  3. Object
  4. Zowel Movable als Robot
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Vehicle heeft move() en alleen Bicycle heeft ringBell(). Bicycle extends Vehicle. Wat gebeurt er met v.ringBell() na Vehicle v = new Bicycle();?
  1. De aanroep compileert niet omdat Vehicle geen methode ringBell() heeft.
  2. De aanroep compileert en voert de fietsimplementatie van ringBell() uit.
  3. De aanroep compileert maar werpt een exceptie op omdat het object een Vehicle is geworden.
  4. De aanroep compileert en voert een lege methode uit die van Object is overgeërfd.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

interface Percent declareert static int cap(int n) en Gauge implements Percent declareert geen methode cap. Hoe moet code de interfacehulpmethode met 120 aanroepen?
  1. Gauge.cap(120)
  2. new Gauge().cap(120)
  3. Percent.super.cap(120)
  4. Percent.cap(120)
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Een methode doorloopt alleen vormen en roept area() aan. Ze moet zowel ArrayList<Shape> als LinkedList<Shape> kunnen accepteren. Welk parametertype biedt de benodigde lijstoperaties?
  1. ArrayList<Shape>
  2. LinkedList<Shape>
  3. List<Shape>
  4. Object
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Wat gebeurt er bij het compileren en uitvoeren van new Row().render()?

interface Renderable { String render(); }
abstract class Base implements Renderable {
    public String render() { return "[" + body() + "]"; }
    protected abstract String body();
}
class Row extends Base {
    @Override protected String body() { return "row"; }
}
  1. Het mislukt omdat Row render() opnieuw moet declareren.
  2. Het compileert en geeft “[row]” terug.
  3. Het mislukt omdat body() public moet zijn.
  4. Het compileert en geeft “[]” terug.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een rapportframework heeft een abstracte Report met private titeltoestand, een constructor die die toestand instelt en een abstracte body(). Rapporten en niet-verwante grafiekobjecten moeten ook afdrukbaar zijn. Welke structuur behoudt beide vereisten?
  1. Vervang Report door een interface met alleen default-methoden die elke titel opslaat.
  2. Laat grafieken Report uitbreiden, ook als het geen rapportobjecten zijn.
  3. Behoud Report en voeg een interface Printable toe aan de relevante klassen.
  4. Laat rapportklassen zowel Report als een abstracte Printable uitbreiden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 3: Volg aanroepen, verborgen leden en gecontroleerde expliciete typeconversies10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Op Shape s = null; volgt s instanceof Circle. Circle extends Shape. Wat is het resultaat van de test?
  1. false
  2. true
  3. Er wordt een ClassCastException opgeworpen.
  4. Er wordt een NullPointerException opgeworpen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat gebeurt er met de laatste instructie?

class Animal { }
class Fox extends Animal { }
Animal a = new Animal();
Fox f = (Fox) a;
  1. De instructie compileert niet omdat downcasts nooit zijn toegestaan.
  2. De instructie maakt een Fox met de oorspronkelijke toestand van Animal.
  3. De instructie compileert en werpt daarna ClassCastException op.
  4. De instructie slaagt en geeft f hetzelfde gewone Animal-object.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Wat geeft pick(value) terug?

static String pick(Object x) { return "object"; }
static String pick(CharSequence x) { return "sequence"; }
// Inside a method of the class containing these overloads:
Object value = "notes";
  1. “sequence”
  2. “notes”
  3. De aanroep werpt ClassCastException op.
  4. “object”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Welke tekst levert p.label + ":" + p.show() op?

class Parent {
    String label = "P";
    String show() { return label; }
}
class Child extends Parent {
    String label = "C";
    @Override String show() { return label; }
}
Parent p = new Child();
  1. “P:P”
  2. “P:C”
  3. “C:C”
  4. “C:P”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een methode accepteert List<Vehicle> en mag een bus toevoegen. Bicycle extends Vehicle en Bus extends Vehicle. Waarom kan een List<Bicycle> er niet aan worden doorgegeven?
  1. Een Bicycle-object kan aan geen enkele Vehicle-referentie worden toegewezen.
  2. Lijsten verhinderen overerving zodra hun elementen veranderlijke objecten zijn.
  3. De methode zou een Bus kunnen toevoegen aan een lijst die alleen fietsen mag bevatten.
  4. De methode zou elk fietsobject omzetten in een gewone Vehicle.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Wat geeft b.test() terug?

class Base {
    String use(Object x) { return "BO"; }
    String use(String x) { return "BS"; }
}
class Sub extends Base {
    @Override String use(Object x) { return "SO"; }
    @Override String use(String x) { return "SS"; }
    String test() {
        Object text = "x";
        return super.use(text) + ":" + use(text);
    }
}
Sub b = new Sub();
  1. “BO:SO”
  2. “BS:SS”
  3. “BO:SS”
  4. “SO:SO”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat drukt new Sub(); af met standaard Java 25?

class Base {
    Base() { System.out.print(value()); }
    int value() { return -1; }
}
class Sub extends Base {
    int n = 8;
    Sub() { super(); n = 11; }
    @Override int value() { return n; }
}
  1. -1
  2. 8
  3. 11
  4. 0
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Wat wordt door deze code afgedrukt? Tool declareert geen methoden.

class Tool { }
class Drill extends Tool {
    int speed() { return 6; }
}
Tool tool = new Drill();
Object copy = tool;
if (copy instanceof Drill d) {
    System.out.print(d.speed());
}
System.out.print(":" + (copy == tool));
  1. “6:false”
  2. “6:true”
  3. “0:true”
  4. De aanroep van speed compileert niet.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Creator heeft public Object create(). TextCreator extends Creator overschrijft die methode. Welke combinatie van retourtype en toegang is geldig?
  1. protected String create()
  2. public String create()
  3. public int create()
  4. private Object create()
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een plug-insysteem heeft veel subklassen van Document. De voorbeeldweergavemethode gebruikt instanceof-takken om het aantal woorden van elk document te berekenen. Er worden vaak nieuwe documentklassen toegevoegd. Welke wijziging laat nieuwe klassen de voorbeeldweergave ondersteunen zonder die keten van typetests te bewerken?
  1. Converteer elk Document expliciet naar Object en behoud dezelfde subtypetakken.
  2. Verplaats de subtypetakken naar een statische methode in de voorbeeldweergaveklasse.
  3. Geef elk subtype een veld met dezelfde naam en lees dat via Document.
  4. Declareer wordCount() in Document en implementeer die in elk concreet subtype.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 4: Modelleer waarden, vergelijk en sorteer ze10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Een applicatie gebruikt enum Status { NEW, SENT, DONE }. Wat doet Status.valueOf("sent")?
  1. De aanroep geeft Status.SENT terug.
  2. De aanroep geeft de string “sent” terug.
  3. De aanroep geeft Status.NEW terug.
  4. De aanroep werpt IllegalArgumentException op.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Welke gegenereerde aanroep leest bij record Reading(int count, String unit) { } de telling uit een variabele r?
  1. r.getCount()
  2. r.count()
  3. r.count
  4. r.readCount()
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

enum Band { LOW(2), HIGH(9); ... } slaat het constructorargument op in een onveranderlijk veld code. Een nieuwe constante MID(5) wordt tussen LOW en HIGH ingevoegd. Hoe veranderen de ordinal() en opgeslagen code van HIGH?
  1. Het volgnummer wordt 2; de code blijft 9.
  2. Het volgnummer blijft 1; de code wordt 2.
  3. Het volgnummer wordt 2; de code wordt 2.
  4. Het volgnummer blijft 1; de code blijft 9.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Twee ongelijke Token-objecten geven dezelfde hashcode terug. Hun implementaties van equals(Object) vergelijken correct de velden die elke token bepalen. Schendt de gedeelde hashcode het contract voor gelijkheid en hashcodes?
  1. Ja; elk object moet een unieke hashcode hebben.
  2. Ja; door een hashcode te delen worden de tokens gelijk.
  3. Nee; ongelijke objecten mogen een hashcode delen.
  4. Nee; hashcollecties negeren gelijkheid na het berekenen van de hashcode.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

record Result(int seconds) implements Comparable<Result> geeft Integer.compare(seconds, other.seconds) terug. Welk teken heeft new Result(418).compareTo(new Result(405))?
  1. Negatief.
  2. Positief.
  3. Nul.
  4. Er is geen resultaat omdat records geen interfaces kunnen implementeren.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Wat drukt de laatste instructie af?

record Tag(String text) {
    Tag { text = text.trim(); }
}
Tag a = new Tag(" north ");
Tag b = new Tag("north");
System.out.println(a.equals(b));
  1. true
  2. false
  3. “north”
  4. Compilatie mislukt omdat parameters niet opnieuw kunnen worden toegewezen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Een basisimplementatie van Ticket.equals accepteert elke Ticket met dezelfde code. PriorityTicket extends Ticket accepteert alleen PriorityTicket-objecten en vergelijkt ook de prioriteit. Een gewoon ticket t en een prioriteitsticket p hebben dezelfde code. Wat geven t.equals(p) en p.equals(t) terug en voldoet dat aan symmetrie?
  1. Beide geven onwaar terug; het paar voldoet aan symmetrie.
  2. Beide geven waar terug; het paar voldoet aan symmetrie.
  3. Ze geven waar en onwaar terug; het paar schendt symmetrie.
  4. Ze geven onwaar en waar terug; het paar schendt symmetrie.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Wat wordt afgedrukt? List en ArrayList zijn geïmporteerd.

record Batch(List<String> names) { }
var original = new ArrayList<String>(List.of("Rin"));
Batch first = new Batch(original);
Batch second = new Batch(new ArrayList<>(original));
original.add("Sol");
System.out.println(first.equals(second) + ":"
    + first.names().size() + ":" + second.names().size());
  1. “true:2:1”
  2. “false:1:1”
  3. “true:1:1”
  4. “false:2:1”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Wat gebeurt er wanneer Collections.sort(list) wordt uitgevoerd? De benodigde typen uit java.util zijn geïmporteerd.

record Rank(int score) implements Comparable<Rank> {
    public int compareTo(Rank other) {
        return Integer.compare(score, other.score);
    }
}
List<Rank> list = List.of(new Rank(8), new Rank(3));
  1. De lijst wordt gesorteerd op score 3, daarna 8.
  2. Compilatie mislukt omdat Rank geen vergelijkingsmethode heeft.
  3. Er wordt UnsupportedOperationException opgeworpen.
  4. De scores blijven 8, daarna 3 omdat de recordvolgorde vastligt.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Wat geeft Rule.DOUBLE.apply(Rule.NEXT.apply(4)) terug?

enum Rule {
    NEXT {
        int apply(int n) {
            return switch (n) { case 4 -> 6; default -> n + 1; };
        }
    },
    DOUBLE { int apply(int n) { return 2 * n; } };
    abstract int apply(int n);
}
  1. 12
  2. 10
  3. 9
  4. 16
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Set 5: Combineer Java-regels en onderzoek fouten10 Oefenvragen

Sla deze les op om elk antwoord te zien en elke set te oefenen.

Vraag 1 van 10

Welk Java-type past bij een vaste verzameling exportkeuzes met de namen PDF, TEXT en HTML?
  1. Een record met drie veranderlijke keuzevelden.
  2. Een enum die die drie constanten declareert.
  3. Een geheel getal waarvan de geldige waarden niet zijn vastgelegd.
  4. Een superklasse met één subklasse voor elk bestand.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 2 van 10

Wat geeft new Terminal().name() terug?

interface Basic {
    default String name() { return "basic"; }
}
interface Detailed extends Basic {
    default String name() { return "detail"; }
}
class Terminal implements Basic, Detailed { }
  1. “basic”
  2. “basicdetail”
  3. “detail”
  4. De klasse heeft een onopgelost conflict tussen default-methoden.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 3 van 10

Een List<Notifier> bevat objecten van verschillende niet-verwante klassen die Notifier.send() implementeren. Een lus roept n.send() aan op elk element. Een nieuwe PushNotifier implementeert hetzelfde contract correct. Welke wijziging is in de lus nodig om instanties daarvan te ondersteunen?
  1. Geen wijziging; de bestaande interfaceaanroep wordt aan elke implementatie gebonden.
  2. Voeg vóór elke aanroep van send() een cast naar PushNotifier toe.
  3. Vervang de interfacevariabele voor elk element door een Object-variabele.
  4. Voeg een tak toe zodat alle oudere objecten de nieuwe klasse-implementatie aanroepen.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 4 van 10

Wat wordt afgedrukt met standaard Java 25?

class Base {
    Base(int n) { System.out.print("B" + n); }
}
class Sub extends Base {
    Sub(int n) {
        if (n < 0) throw new IllegalArgumentException();
        super(n + 1);
        System.out.print("S" + n);
    }
}
new Sub(2);
  1. “S2B3”
  2. “B2S2”
  3. Compilatie mislukt omdat super niet als eerste staat.
  4. “B3S2”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 5 van 10

Een veranderlijke klasse Code definieert gelijkheid en hashcodes met behulp van haar tekstveld. Een HashSet<Code> bevat c met de tekst “red”. Het programma moet de tekst veranderen in “blue”, hetzelfde object behouden en betrouwbaar opzoeken in de verzameling mogelijk houden. In welke volgorde moet het programma het object en de verzameling bijwerken?
  1. Verander de tekst, verwijder het object en voeg het opnieuw toe.
  2. Voeg het object opnieuw toe, verander de tekst en verwijder het.
  3. Verwijder het object, voeg het opnieuw toe en verander de tekst.
  4. Verwijder het object, verander de tekst en voeg het opnieuw toe.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 6 van 10

Wat levert x.call(arg) + ":" + x.call("x") op?

class Alpha {
    String call(Object x) { return "AO"; }
    String call(String x) { return "AS"; }
}
class Beta extends Alpha {
    @Override String call(Object x) { return "BO"; }
    @Override String call(String x) { return "BS"; }
}
Alpha x = new Beta();
Object arg = "data";
  1. “AO:AS”
  2. “BO:BS”
  3. “BO:AS”
  4. “BS:BS”
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 7 van 10

Wat drukt de laatste instructie af? List en ArrayList zijn geïmporteerd.

record Group(List<String> names) {
    Group { names = List.copyOf(names); }
}
var original = new ArrayList<String>();
original.add("Uma");
Group group = new Group(original);
original.add("Zed");
System.out.println(group.names().size() + ":" + original.size());
  1. “1:2”
  2. “2:2”
  3. “1:1”
  4. De recordconstructor compileert niet.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 8 van 10

Wat gebeurt er als deze code wordt gecompileerd?

interface Ready {
    default String state() { return "ready"; }
}
abstract class Base {
    public abstract String state();
}
class Console extends Base implements Ready { }
  1. Compilatie slaagt; Console overerft de default-implementatie van Ready.
  2. Compilatie slaagt; de methode van Base geeft null terug totdat ze wordt overschreven.
  3. Compilatie mislukt; Console moet de overgeërfde abstracte methode implementeren.
  4. Compilatie mislukt; abstracte klassen kunnen niet met interfaces worden gecombineerd.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 9 van 10

Twee objecten van record Player(String name, int points) hebben verschillende namen maar hetzelfde aantal punten. Hun zelfgeschreven compareTo vergelijkt alleen punten met Integer.compare. Wat melden equals en compareTo voor deze twee objecten?
  1. equals is waar; compareTo is nul.
  2. equals is onwaar; compareTo moet ongelijk aan nul zijn.
  3. equals is waar; compareTo moet ongelijk aan nul zijn.
  4. equals is onwaar; compareTo is nul.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Vraag 10 van 10

Een bibliotheek biedt al interface Storable { String saveText(); } aan. Veel nieuwe implementaties delen een privaat padveld, constructorvalidatie en hulpcode, terwijl sommige bestaande implementaties andere klassen uitbreiden. Hoe kan de bibliotheek hergebruik aanbieden en tegelijk de gemeenschappelijke publieke interface behouden?
  1. Voeg een abstracte Storable-implementatie toe die klassen mogen uitbreiden.
  2. Vervang Storable door een klasse die elke implementatie moet uitbreiden.
  3. Verplaats het private instantiepadveld naar de interface Storable.
  4. Voeg een tweede superklasse voor gedeelde opslag toe aan bestaande implementaties.
Toon antwoordVerberg antwoord

Sla deze les op om dit antwoord te zien.

Kom je niet uit je eigen opgave? Huiswerkhulp

Kernbegrippen

72 Kernbegrippen, elk met een definitie

A

Aanroep van een superklasseconstructor
Een aanroep van een superklasseconstructor gebruikt super(...) om argumenten door te geven aan een constructor van de directe superklasse voor het huidige object.
Een aanroep van een superklasseconstructor geeft de naam van een student door aan Person, dat het private naamveld beheert.
Abstracte klasse
Een abstracte klasse is een klasse waarvan je niet rechtstreeks een instantie kunt maken en die gedeelde velden en code kan combineren met onvoltooide operaties.
Een abstracte klasse Shape kan een naam opslaan en tegelijk vereisen dat concrete cirkels en rechthoeken een oppervlakte leveren.
Abstracte methode
Een abstracte methode declareert een vereiste operatie zonder body en laat concrete subklassen de implementatie aanbieden of overerven.
De abstracte methode area() voorkomt dat een onvoltooide concrete vorm ongemerkt een tijdelijke invulling met oppervlakte nul gebruikt.
Accessor
Een accessor is een methode die een opgeslagen waarde teruggeeft; een gegenereerde recordaccessor gebruikt de exacte naam van zijn component zonder gettervoorvoegsel.
De accessor voor een recordcomponent count is count(), dus getCount() is niet beschikbaar tenzij die expliciet is gedeclareerd.

B

Beschermde toegang
Beschermde toegang staat toegang binnen een pakket en beperkte toegang voor subklassen over pakketgrenzen heen toe, zonder een lid public te maken.
Beschermde toegang laat een subklasse een hulpmethode van de superklasse gebruiken zonder die hulpmethode in de publieke interface op te nemen.

C

Canonieke constructor
Een canonieke constructor is een recordconstructor waarvan de parameters overeenkomen met de componentenlijst en de componentvelden van het record initialiseren.
De canonieke constructor van Point(int x, int y) accepteert de x-coördinaat gevolgd door de y-coördinaat.
ClassCastException
Een ClassCastException is een uitvoeringsfout die wordt veroorzaakt door een expliciete conversie van een referentie waarvan het object niet null is en niet verenigbaar is met het gevraagde doeltype.
Een ClassCastException ontstaat wanneer een gewoon Animal-object expliciet wordt geconverteerd naar de specifiekere subklasse Fox.
Compacte constructor
Een compacte constructor is een recordconstructorbody die zonder herhaling van de parameters wordt geschreven, waarna de componenten automatisch worden toegewezen.
Een compacte constructor kan witruimte rond een binnenkomende naam verwijderen voordat die genormaliseerde waarde aan het final-componentveld wordt toegewezen.
Comparable
Comparable is de Java-interface waarmee een type een natuurlijke volgorde definieert door een instantie met een andere te vergelijken met compareTo.
Een hardloper implementeert Comparable<Runner> zodat een sorteermethode hardlopers op eindtijd kan ordenen.
Compositie
Compositie modelleert een heeft-een-relatie door referenties naar andere objecten in velden op te slaan, zonder te stellen dat het object die andere objecten is.
Door compositie kan een Customer een Address bevatten zonder dat elke klant als adres bruikbaar wordt.
Concrete klasse
Een concrete klasse is een niet-abstracte klasse die implementaties van alle vereiste abstracte methoden aanbiedt of overerft en waarvan instanties kunnen bestaan.
Rectangle is een concrete klasse zodra ze de oppervlakteoperatie aanvult die haar abstracte vormbasisklasse vereist.
Concrete methode
Een concrete methode heeft een implementatiebody, zodat aanroepers haar code kunnen uitvoeren in plaats van erop te vertrouwen dat een subklasse een declaratie aanvult.
De concrete methode Shape.describe() kan de naam hergebruiken en de implementatie van area() van elke subklasse aanroepen.
Conflict tussen default-methoden
Een conflict tussen default-methoden ontstaat wanneer niet-verwante interfaces concurrerende overgeërfde default-methoden aanbieden die de implementerende klasse moet afhandelen.
Een conflict tussen default-methoden vereist dat SmartLamp de overeenkomstige beschrijvingen van Lamp en Speaker overschrijft.
Constantespecifieke klassebody
Een constantespecifieke klassebody is de eigen klassebody van een enumconstante, die wordt gebruikt om gedrag te bieden dat specifiek is voor die benoemde waarde van de enum.
Een constantespecifieke klassebody laat MULTIPLY een operatie anders implementeren dan de constante ADD van de enum.
Constructorketen
Een constructorketen is een reeks aanroepen van constructoren van dezelfde klasse en van superklassen die de delen van één nieuw object initialiseert.
Een constructorketen voor een eindklasse bereikt de basisconstructoren voordat de rest van de body van de eindklasse wordt uitgevoerd.
Covariant retourtype
Een covariant retourtype is een subtype dat als referentieretourtype van een overschrijvende methode wordt gebruikt in plaats van het oorspronkelijke, algemenere retourtype.
Een covariant retourtype staat toe dat een overschrijvende methode String teruggeeft wanneer de superklassemethode Object teruggeeft.

D

default-methode
Een default-methode is een public instantie-interfacemethode met een body die implementerende klassen overerven en door een overschrijving mogen vervangen.
Een default-methode needsCharge() kan batteryPercent() gebruiken zonder de indeling van private velden van een apparaat te kennen.
Downcasting
Downcasting behandelt een referentie na een compatibiliteitscontrole tijdens de uitvoering als een specifieker type, zonder het object om te zetten of te vervangen.
Downcasting van een persoonsreferentie naar een student werkt alleen als het object waarnaar wordt verwezen al het studenttype heeft.
Dynamische binding
Dynamische binding kiest tijdens de uitvoering een overschreven instantiemethode op basis van de werkelijke klasse van het ontvangende object.
Door dynamische binding kan een overgeërfde hello() die who() aanroept, B.who() bereiken wanneer de ontvanger bij klasse B hoort.

E

Enkelvoudige overerving
Enkelvoudige overerving betekent dat een Java-klasse hoogstens één directe superklasse heeft, hoewel ze deel kan uitmaken van een langere keten van subklassen.
Enkelvoudige overerving verhindert dat een klasse zowel Student als Teacher als directe superklasse uitbreidt.
Enumconstante
Een enumconstante is één benoemde waarde die in een enum is gedeclareerd, met één instantie die gegevens kan bevatten en constantespecifiek gedrag kan bieden.
De enumconstante CupSize.LARGE kan een inhoud van 500 mL opslaan en op basis van identiteit worden vergeleken.

F

final-methode
Een final-methode is een methode waarvan de implementatie niet door een subklasse kan worden overschreven, zodat die implementatie in afgeleide klassen behouden blijft.
Een final-methode voor het berekenen van een gecontroleerde waarde verhindert dat een subklasse die berekening vervangt.
Flexibele constructorbody
Een flexibele constructorbody staat beperkte instructies toe vóór een expliciete constructoraanroep en is een standaardfunctie in Java 25.
Een flexibele constructorbody kan een negatieve parameter afwijzen voordat die aan de superklasseconstructor wordt doorgegeven.
Functionaliteit
Een functionaliteit is een operatie of verzameling operaties die een object belooft te ondersteunen, vaak uitgedrukt door een interface voor niet-verwante klassen.
Exporteren is een functionaliteit die een tekstlabel en een cirkel allebei kunnen bieden terwijl ze verschillende superklassen behouden.

G

Gedeclareerd type
Het gedeclareerde type is het statische type dat in de broncode aan een variabele is toegekend en bepaalt welke operaties de compiler via die variabele toestaat.
In Person p = new Student(...) is het gedeclareerde type Person, dus methoden die alleen bij studenten horen zijn niet rechtstreeks beschikbaar.
Gedeeltelijke implementatie
Een gedeeltelijke implementatie biedt een deel van het interfacegedrag in een abstracte klasse en laat concrete subklassen de vereiste operaties voltooien.
Een gedeeltelijke implementatie van Storable kan padvalidatie delen terwijl concrete klassen de opgeslagen tekst leveren.
Gelijke sorteerrang
Een gelijke sorteerrang ontstaat wanneer compareTo nul teruggeeft, wat betekent dat twee objecten volgens de gekozen sorteerregel dezelfde vergelijkingsrang innemen.
Een gelijke sorteerrang op basis van wedstrijdtijd kan voorkomen bij hardlopers met verschillende namen, ook als hun records ongelijk zijn.
Gelijkheidscontract
Het gelijkheidscontract vereist reflexieve, symmetrische, transitieve en consistente vergelijkingen, waarbij elk object dat niet null is ongelijk is aan null.
Het gelijkheidscontract sluit uit dat een boek een e-book accepteert terwijl het e-book hetzelfde boek andersom afwijst.
Gemeenschappelijke toestand
Gemeenschappelijke toestand in een superklasse bestaat uit de gemeenschappelijke gegevens per object die verwante subklassen als onderdeel van het basisklassedeel van elk object krijgen.
De naam en positie van een vorm zijn gemeenschappelijke toestand als één abstracte superklasse die voor elke vorm definieert.

H

Hashbotsing
Een hashbotsing ontstaat als ongelijke objecten dezelfde hashcode teruggeven. Dat is toegestaan omdat gelijkheidsbepaling ze kan onderscheiden.
Een hashbotsing tussen twee verschillende tokens vereist niet dat hun gelijkheidsmethode waar teruggeeft.
Hashcode
Een hashcode is een geheel getal dat wordt gebruikt om kandidaten in een hashcollectie te vinden; objecten die bij vergelijking gelijk zijn, moeten dezelfde hashcode teruggeven.
De hashcode van een boek kan worden opgebouwd uit de cataloguscode en editie als die velden ook de gelijkheid bepalen.

I

Identiteit
Identiteit betekent dat referenties naar precies hetzelfde object verwijzen, ongeacht of afzonderlijke objecten toevallig gelijke waarden bevatten.
Twee afzonderlijk geconstrueerde boeken kunnen gelijke codes hebben terwijl hun verschillende identiteit a == b onwaar maakt.
Implementatie
Een implementatie is werkende code die aan een methodecontract voldoet en door een klasse wordt aangeboden of via een geschikte methodedeclaratie wordt overgeërfd.
Een public implementatie die van Entity is overgeërfd, kan voor een subklasse Asset voldoen aan Named.name().
instanceof
De operator instanceof test of een referentie een instantie van een bepaald type of subtype aanduidt en geeft onwaar terug wanneer de referentie null is.
Met een instanceof-test kan code controleren of een persoon het studenttype heeft voordat een methode wordt gebruikt die alleen bij studenten hoort.
Interface
Een interface is een benoemd typecontract waarmee klassen een gemeenschappelijke functionaliteit kunnen bieden zonder één superklasse met toestand te delen.
De interface Chargeable laat een telefoon en een scooter dezelfde aanroepen ondersteunen terwijl ze met verschillende snelheden opladen.
Interfaceconstante
Een interfaceconstante is een veld dat impliciet public, static en final is gedeclareerd en bij de interface hoort in plaats van bij afzonderlijke objecten.
De interfaceconstante Chargeable.FULL kan 100 bevatten terwijl elk apparaat zijn eigen veranderende batterijveld opslaat.
Interfacetype
Een interfacetype is een type dat voor referenties, parameters of resultaten wordt gebruikt om een functionaliteit beschikbaar te maken en tegelijk verschillende implementerende klassen toe te staan.
Door List als interfacetype te gebruiken, kan één methode zowel lijsten op basis van arrays als gekoppelde lijsten accepteren.

M

Methodesignatuur
Een methodesignatuur identificeert een methode aan de hand van haar naam en parametertypen voor selectie tussen overladen methoden en voor overschrijven; alleen het retourtype is niet genoeg.
Als je een parameter van Object naar String verandert, verandert de methodesignatuur en ontstaat een overladen methodevariant.

N

Natuurlijke volgorde
Een natuurlijke volgorde is de standaardvolgorde die door de vergelijkingsmethode van een type is gedefinieerd en wordt gebruikt door sorteeroperaties die geen alternatieve volgorde vragen.
De natuurlijke volgorde van een hardloper kan bij het sorteren van een lijst kleinere eindtijden vóór grotere plaatsen.
Niet-wijzigbare kopie
Een niet-wijzigbare kopie beschermt de inhoud van een collectie tegen latere structurele wijzigingen via de oorspronkelijke of de teruggegeven collectiereferentie.
Een niet-wijzigbare kopie van List.copyOf beschermt de ledenlijst van een team tegen toevoegingen aan de veranderlijke lijst van de aanroeper.

O

Object
Object is de basisklasse van de klassenhiërarchie van Java en biedt gemeenschappelijke methoden die gewone klassen direct of indirect overerven.
Een Object-variabele kan een student of een array bevatten en beide ondersteunen de methoden die elk object heeft.
Ondiepe onveranderlijkheid
Ondiepe onveranderlijkheid legt de eigen veldreferenties of waarden van een object vast, terwijl veranderlijke objecten die via die referenties worden bereikt kunnen veranderen.
Door ondiepe onveranderlijkheid behoudt een record dezelfde lijstreferentie, ook als een andere referentie een naam aan die lijst toevoegt.
Ontvanger
Een ontvanger is het object waarop een instantiemethode wordt aangeroepen en waarvan de werkelijke klasse de dynamische binding van de gekozen methodesignatuur bepaalt.
In shape.area() is de ontvanger het vormobject, ook wanneer de variabele een abstract gedeclareerd type heeft.
Opsommingstype
Een opsommingstype is een Java-type dat een vaste verzameling benoemde constanten definieert en voorkomt dat gewone aanroepers willekeurige strings of getallen gebruiken.
Een opsommingstype voor leveringsstatussen kan een bestelling beperken tot de benoemde waarden NEW, SENT en DONE.
Overerving
Overerving is een relatie waarbij een subklasse voortbouwt op een superklasse en de operaties ondersteunt die via die superklasse beschikbaar zijn.
Door overerving kan een Student Person.getName() hergebruiken en tegelijk eigen studiepunten en studiegedrag toevoegen.
Overladen
Overladen declareert methoden met dezelfde naam maar verschillende parameterlijsten, zodat de compiler een toepasbare signatuur kan kiezen.
Door overladen kan print(String text) naast print(int number) worden toegevoegd zonder een van beide methoden te vervangen.
Overloop bij gehele getallen
Overloop bij gehele getallen treedt op wanneer een berekening met gehele getallen het bereik van weergeefbare waarden overschrijdt, waardoor aftrekken een onjuist teken voor de volgorde kan opleveren.
Overloop bij gehele getallen kan een vergelijking op basis van aftrekken verstoren; Integer.compare voorkomt die vergelijkingsfout.
Override-annotatie
De Override-annotatie, geschreven als @Override, laat de compiler controleren of een methode een overgeërfde methode overschrijft of implementeert.
De Override-annotatie vangt tostring() af wanneer de programmeur toString() wilde vervangen.
Overschrijven
Overschrijven levert een implementatie in een subklasse van een overgeërfde instantiemethode met dezelfde naam en overeenkomende parametertypen.
Door yearlyPay() te overschrijven kan een manager een bonus toevoegen terwijl aanroepers de werknemersoperatie blijven gebruiken.

P

Patroonvariabele
Een patroonvariabele is een getypeerde lokale variabele die door een geslaagd typepatroon wordt geïntroduceerd en beschikbaar is waar vaststaat dat het patroon overeenkomt.
De patroonvariabele student in person instanceof Student student maakt de identificatiemethode van het overeenkomende object veilig beschikbaar.
Polymorfisme
Polymorfisme laat een algemene referentie via gemeenschappelijke operaties met verschillende subtypen werken, waarbij elk object zijn eigen gedrag levert.
Door polymorfisme kan één lus area() aanroepen op cirkels, rechthoeken en driehoeken zonder afzonderlijke typetakken.
Privé-interfacemethode
Een privé-interfacemethode is een hulpmethode met een body die alleen zichtbaar is voor code binnen die interface, zodat haar werkende methoden code kunnen delen.
Een privé-interfacemethode kan dubbele controles uit twee default-methoden wegnemen zonder deel van het publieke contract te worden.

R

Record
Een record is een beknopte Java-klasse voor een vaste groep componentwaarden, met gegenereerd gedrag voor constructie, toegang, gelijkheidsbepaling, hashcodes en tekst.
Een record Point(int x, int y) groepeert twee coördinaten en biedt gelijkheidsbepaling op basis van componenten zonder handgeschreven methoden.
Recordcomponent
Een recordcomponent is een waarde die in een recordkop is gedeclareerd en een privaat final-veld, een constructorparameter en een public accessor bepaalt.
De recordcomponent seconds kan uit een hardloper worden gelezen met de gegenereerde methode runner.seconds().

S

Specifiekere interface
Een specifiekere interface breidt een andere interface uit en heeft voorrang wanneer beide een overeenkomstige default-methode voor een implementerende klasse bieden.
Een specifiekere interface Detailed levert de gekozen name() wanneer ze Basic uitbreidt en de default-methode daarvan vervangt.
Standaardconstructor
Een standaardconstructor is een constructor zonder parameters die Java levert als een klasse geen constructor declareert, met een aanroep van super() in de body.
Door Person(String name) toe te voegen, verdwijnt de standaardconstructor waarop een subklasse mogelijk vertrouwde.
Statische interfacemethode
Een statische interfacemethode is een methode die bij de interface zelf hoort, via haar naam wordt aangeroepen en niet door implementerende klassen wordt overgeërfd.
De statische interfacemethode Chargeable.clamp(130) kan 100 teruggeven zonder een apparaatobject te maken.
Subklasse
Een subklasse is een klasse die is gedeclareerd om een andere klasse uit te breiden en toestand of gedrag toevoegt terwijl ze bruikbaar blijft als dat algemenere type.
Een subklasse GraduateStudent kan voortbouwen op Student en nog steeds worden gebruikt waar een Person wordt verwacht.
Superinterface
Een superinterface is een interface die door een andere interface wordt uitgebreid en waarvan de vereiste operaties deel worden van het uitbreidende contract.
Chargeable is een superinterface van SmartDevice wanneer die laatste haar samen met Connectable uitbreidt.
Superklasse
Een superklasse is een klasse waarvan een subklasse de toegankelijke leden overerft en die het algemenere type vormt in hun klassenrelatie.
Person is de superklasse van Student, dus een methode die een persoon accepteert, kan ook een student ontvangen.
switch-expressie
Een switch-expressie kiest een resultaat uit overeenkomende gevallen en moet de toegestane invoer afdekken, waarbij enumconstanten beknopte gevallen mogelijk maken.
Een switch-expressie die alle drie constanten van een enum voor maten opsomt, kan “S”, “M” of “L” opleveren zonder default-tak.
Symmetrie
Symmetrie bij gelijkheid vereist dat het omwisselen van twee objecten in een gelijkheidsvergelijking in beide richtingen hetzelfde resultaat geeft.
Symmetrie ontbreekt als book.equals(ebook) waar is maar ebook.equals(book) voor dezelfde objecten onwaar is.

T

Tekstrepresentatie
Een tekstrepresentatie is de string die toString() voor een object teruggeeft en die wordt gebruikt wanneer het object wordt afgedrukt of met andere tekst wordt samengevoegd.
De tekstrepresentatie van een student kan een naam en studiepunten tonen in plaats van de standaardtekst met klasse en hashcode.
Transitiviteit
Transitiviteit bij gelijkheid vereist dat een object dat gelijk is aan een tweede object ook gelijk is aan een derde object als dat tweede object gelijk is aan het derde.
Transitiviteit zorgt ervoor dat drie boeken met dezelfde code en editie voor gelijkheidsbepaling overeenkomen in onderlinge vergelijkingen.

U

Upcasting
Upcasting behandelt een referentie als een superklasse- of geïmplementeerd interfacetype en verbreedt zo veilig het type waarmee je de referentie gebruikt zonder het object zelf te veranderen.
Upcasting van een telefoon naar Chargeable behoudt de batterijgegevens maar biedt alleen de operaties die via dat type beschikbaar zijn.

V

Veldinitialisator
Een veldinitialisator is een expressie in een velddeclaratie die een beginwaarde aan het veld toewijst wanneer de initialisatiefase van dat veld wordt uitgevoerd.
Een instantieveldinitialisator in een subklasse wordt uitgevoerd nadat de superklasseconstructor van dat object is teruggekeerd.
Verbergen van methoden
Het verbergen van methoden treedt op als een subklasse een statische methode declareert met dezelfde signatuur als een statische methode die via haar superklasse beschikbaar is.
Bij het verbergen van methoden bepaalt het type tijdens de compilatie welke statische methode wordt gekozen, ook als een referentie een subklasseobject bevat.
Verbergen van velden
Het verbergen van velden treedt op als een subklasse een veld declareert met dezelfde naam als een overgeërfd veld, waardoor afzonderlijke velden overblijven die op basis van het gedeclareerde type worden gekozen.
Door het verbergen van velden kan p.label het veld van de ouderklasse lezen terwijl p.show() een overschrijving in de kindklasse uitvoert die een ander label gebruikt.
Volgnummer
Een volgnummer is de declaratiepositie van een enumconstante, geteld vanaf nul, die kan veranderen wanneer vóór die waarde constanten worden ingevoegd.
In een enum die is gedeclareerd als LOW, HIGH verandert het volgnummer van HIGH van 1 naar 2 wanneer MID ervoor wordt ingevoegd.

W

Waardegelijkheid
Waardegelijkheid behandelt objecten als gelijk wanneer hun gekozen gegevens overeenkomen, ook als de referenties naar afzonderlijke objectinstanties verwijzen.
Waardegelijkheid kan twee afzonderlijk gemaakte boeken gelijk maken wanneer hun cataloguscode en editie overeenkomen.
Werkelijk type
Het werkelijke type is de klasse van het object waarnaar een referentie verwijst, vastgelegd wanneer dat object wordt gemaakt en gebruikt bij het kiezen van overschrijvende instantiemethoden.
Een Person-referentie kan naar een student verwijzen van wie het werkelijke type na de upcast Student blijft.
Werkelijke klasse
De werkelijke klasse is de klasse van een object die getClass() meldt, onafhankelijk van het gedeclareerde type van de referentie.
De werkelijke klasse van een student blijft Student, ook als een methode die via een parameter van het type Person ontvangt.

Over deze les

Door StudeliFout melden

Maak je eigen Studiemateriaal

Maak van je aantekeningen, slides en documenten Leermodules, Oefenvragen, Flashcards en 6 andere tools.

Aan de slag