Saturday, April 1, 2006

Java tutorial - Objecten

Intro|Basics|Elementen|Bouwstenen|Datatypen|Operatoren|Toegangscontrole|Draaiboek|Foutenafhandeling|Objecten|Uitvoeren

Hier draait het om bij OOP

Overerving (inheritance)

Een typisch kenmerk van een object-georiënteerde programmeertaal is het kunnen overerven van de variabelen en de methoden van de klassen onderling. Een klasse kan zo één superklasse (de hogere klasse) en/of meerdere subklassen (de lagere klassen) hebben. Abstract gezien is de klasse Auto een subklasse van de superklasse Voertuig die op zich abstract gezien weer meerdere subklassen kan hebben, zoals Bus, Motorfiets, Tractor, etc. De klasse Auto kan op zich dan weer een superklasse zijn van de subklassen AlfaRomeo, Maserati, BMW, etc.

Je kan in de declaratie van een klasse aangeven of het een superklasse heeft. Dit kan met behulp van de extends bepaling (breidt uit; is een uitbreiding van). Deze klasse heeft dan direct toegang tot de zichtbare variabelen en de methoden van de superklasse. Hieronder wordt de klasse AlfaRomeo beschreven als een subklasse van de superklasse Auto.

De code van de superklasse Auto kun je overigens onderaan het eerste hoofdstuk vinden: Basics - Voorbeeld code.

package nl.balusc.voertuigen.autos;

import nl.balusc.voertuigen.Auto;

// De klasse "AlfaRomeo" is een uitbreiding van de klasse "Auto".
public class AlfaRomeo extends Auto {

    // De eigenschappen die per Alfa Romeo kunnen verschillen.
    public String alfaRomeoType = "";

    // De standaard constructeur.
    public AlfaRomeo() {
        maakAlfaRomeo();
        aantalAutos++;
    }

    // De constructeur, waarbij je een specifieke type kunt opgeven.
    public AlfaRomeo(String type) {
        alfaRomeoType = type;
        maakAlfaRomeo();
        aantalAutos++;
    }

    // De eigenschappen worden ingesteld, afhankelijk van het merk en de type.
    private void maakAlfaRomeo() {
        if (alfaRomeoType.equals("156 1.9 JTD SW")) {
            cylinderinhoud = 1910;
            gewicht = 1330;
            minimumToerental = 850;
            maximumToerental = 4500;
            maximumVersnelling = 5;
            maximumZitplaatsen = 5;
            brandstofSoort = "Diesel";
            // De bovenstaande variabelen zijn reeds in de superklasse "Auto"
            // gedeclareerd.
        } else if (alfaRomeoType.equals("Brera 3.2 JTS Q4")) {
            cylinderinhoud = 3195;
            gewicht = 1630;
            minimumToerental = 800;
            maximumToerental = 6200;
            maximumVersnelling = 6;
            maximumZitplaatsen = 4;
            brandstofSoort = "Benzine";
        } else { // Onbekende Alfa Romeo, dus we maken een "standaard" auto.
            maakAuto();
        }
    }

}

Als je goed kijkt, dan zie je dat de subklasse AlfaRomeo een aantal variabelen en methoden van de superklasse Auto kan gebruiken zonder deze binnen de klasse te hoeven declareren, zoals de variabelen aantalAuto, cylinderinhoud, gewicht, etc. en de methode maakAuto().

Terug naar boven

Veelvormigheid (polymorphism)

Een ander typisch kenmerk van een object-georiënteerde programmeertaal is de veelvormigheid van de klassen geimplementeerd uit een abstracte klasse of een interface. Een interface is een soort volledig abstracte klasse. Een abstracte klasse kan ten minste een lege methode hebben en een interface mag alleen maar lege methoden hebben. Een lege methode is een methode, waarvan alléén het element type, de benaming en de eventuele parameters zijn gedefinieerd. Het heeft dus alleen een signature en geen body.

Een abstracte klasse definieer je met abstract class en een interface met enkel interface. Bij een interface kun je maar één toegangsmodifier gebruiken: public. De overige toegangsmodifiers zijn niet toegestaan, met uitzondering van abstract. Echter aangezien een interface impliciet abstract is, is deze modifier overbodig.

De methoden van een interface zijn impliciet public abstract, deze modifiers zijn dan ook overbodig voor de methoden van een interface. De variabelen van een interface zijn impliciet public static final, deze modifiers zijn dan ook overbodig voor de variabelen van een interface. Andere toegangsmodifiers dan public zijn dan ook niet toegestaan voor de methoden en variabelen van een interface.

public abstract class Voertuig {

    // Hierin worden de methoden starten() en stoppen() gedefinieerd.
    public abstract void starten();
    public abstract void stoppen();

}
public interface Airco {

    // Hierin worden de methoden aircoStarten() en aircoStoppen()
    // gedefinieerd.
    void aircoStarten();
    void aircoStoppen();

}
public interface Automaat {

    // Hierin worden de methoden automaatOpschakelen() en
    // automaatTerugschakelen() gedefinieerd.
    void automaatOpschakelen();
    void automaatTerugschakelen();

}

Ofschoon je abstracte klassen en interfaces in principe door elkaar kunt gebruiken, is het belangrijkste ontwerpverschil dat een abstracte klasse gemeenschappelijke gedragingen beschrijft die alle subklassen zouden moeten overnemen en dat een interface specifieke gedragingen beschrijft die niet op alle subklassen van toepassing hoeven te zijn. Iedere subklasse kan dan ook maar één abstracte klasse en meerdere interfaces overnemen. In het geval van voertuigen kun je interfaces vergelijken met de opties die een voertuig kán hebben. Bijvoorbeeld Airco, Automaat, NavigatieSysteem, etcetera.

Je kunt een klasse uitbreiden met een abstracte klasse middels de extends bepaling. Let wel, met de extends bepaling kun je dus de klasse uitbreiden met óf een abstracte klasse óf een "normale" klasse, zoals ook hierboven bij Overerving staat beschreven. Verder kun je een of meer interfaces implementeren met de implements bepaling. Wanneer er meerdere interfaces zijn, dan moet je ze met komma's scheiden.

public class Auto extends Voertuig implements Airco {

    // Hierin worden de methoden verder uitgebouwd.
    public void starten() {
        ...
    }
    public void stoppen() {
        ...
    }
    public void aircoStarten() {
        ...
    }
    public void aircoStoppen() {
        ...
    }

}
public class Bus extends Voertuig implements Airco, Automaat {

    // Hierin worden de methoden verder uitgebouwd.
    public void starten() {
        ...
    }
    public void stoppen() {
        ...
    }
    public void aircoStarten() {
        ...
    }
    public void aircoStoppen() {
        ...
    }
    public void automaatOpschakelen() {
        ...
    }
    public void automaatTerugschakelen() {
        ...
    }

}

Het belangrijkste voordeel van veelvormigheid is dat je voor alle subklassen van de superklasse Voertuig gemeenschappelijke benamingen voor methoden hebt, wat veel handiger en overzichtelijker is.

Auto.starten();
Auto.stoppen();
Auto.aircoStarten();
Auto.aircoStoppen();
Bus.starten();
Bus.stoppen();
Bus.aircoStarten();
Bus.aircoStoppen();
etc ..

Uiteraard kunnen ze zich elk op een iets andere manier gedragen, sterker, je moet de definitie van abstracte methoden toch letterlijk overnemen en vervolgens de body per subklasse verder uitbouwen.

Terug naar boven

Methoden overschrijven (method overriding)

Wanneer je met extends een non-abstracte klasse (of een abstracte klasse met non-abstracte methoden) uitbreidt, dan kun je de non-static methoden van deze klasse overschrijven. Oftewel, je definieert in een subklasse een methode met exact dezelfde signature (het element type, de benaming en de eventuele parameters) als de methode van de superklasse en je past enkel de body aan. Ook kun je de zichtbaarheid vergroten (en niet verkleinen) met behulp van de zichtbaarheids toegangsmodifiers. Van klein naar groot zijn deze dus: geen zichtbaarheidsmodifier, protected, public.

public class Auto extends Voertuig implements Airco {

    // De methode "starten".
    protected void starten() {
        toerental = minimumToerental;
        verlichtingAan = true;
    }

    // De methode "aircoStarten".
    public void aircoStarten() {
        aircoAan = true;
        aircoTemperatuur = standaardAircoTemperatuur;
    }

}
public class AlfaRomeo extends Auto {

    // De methode "starten" wordt overschreven.
    public void starten() {
        toerental = minimumToerental;
        verlichtingAan = false;
        aircoStarten();
    }

}

De methode starten() gedraagt zich bij de klasse AlfaRomeo dus (iets) anders dan bij de klasse Auto.

Let wel, methoden overschrijven is niet mogelijk wanneer de methode van de superklasse een van deze toegangsmodifiers heeft: private, static en final.

Terug naar boven

Methoden overladen (method overloading)

Met methoden overladen wordt bedoeld dat je binnen een klasse meerdere methoden met dezelfde benaming mag definieren, maar wel elk met verschillende invoerparameters.

public class AlfaRomeo extends Auto {

    // De methode "starten" zonder enige invoerparameters.
    public void starten() {
        toerental = minimumToerental;
        verlichtingAan = false;
        aircoStarten();
    }

    // De methode "starten" met een integer invoerparameter.
    public void starten(int argMinimumToerental) {
        toerental = argMinimumToerental;
        verlichtingAan = true;
        aircoStarten();
    }

    // De methode "starten" met een boolean invoerparameter.
    public void starten(boolean argVerlichtingAan) {
        toerental = minimumToerental;
        verlichtingAan = argVerlichtingAan;
        aircoStarten();
    }

    // De methode "starten" met een integer invoerparameter en een boolean
    // invoerparameter.
    public void starten(int argMinimumToerental, boolean argVerlichtingAan) {
        toerental = argMinimumToerental;
        verlichtingAan = argVerlichtingAan;
        aircoStarten();
    }

    // De methode "starten" met twee boolean invoerparameters.
    public void starten(boolean argVerlichtingAan, boolean argAircoStarten) {
        toerental = minimumToerental;
        verlichtingAan = argVerlichtingAan;

        if (argAircoStarten) {
            aircoStarten();
        }
    }

}

Je kunt deze methoden dus elk op een verschillende manier aanroepen:

Auto.starten();
Auto.starten(900);
Auto.starten(false);
Auto.starten(850, true);
Auto.starten(false, false);

Terug naar boven

Variabelen schaduwen (variable shadowing)

Variabelen schaduwen volgt hetzelfde principe als methoden overschrijven. Kortom: je definieert in een subklasse een variabele met exact dezelfde signature (het element type en de benaming) als de variabele van de superklasse en je past enkel de toegewezen waarde aan. Ook kun je de zichtbaarheid vergroten (en niet verkleinen) met behulp van de zichtbaarheids toegangsmodifiers. Van klein naar groot zijn deze dus: geen zichtbaarheidsmodifier, protected, public. Dit geldt overigens alleen voor de variabelen van de klasse en niet voor de variabelen van de methode. Immers, de variabelen van de methode zijn per definitie onzichtbaar buiten de methode.

public class Auto extends Voertuig implements Airco {

    // De variabele "standaardAircoTemperatuur".
    protected int standaardAircoTemperatuur = 20;

}
public class AlfaRomeo extends Auto {

    // De variabele "standaardAircoTemperatuur" wordt overschreven.
    public int standaardAircoTemperatuur = 18;

}

Let wel, variabelen schaduwen is niet mogelijk wanneer de variabele van de superklasse een van deze toegangsmodifiers heeft: private, static en final.

Terug naar boven

Inkapselen (encapsulation, information hiding)

Het inkapselen van informatie wordt met name op de variabelen van de klasse toegepast. Hiermee kun je wat meer controle hebben over het opvragen danwel het instellen van de waarde van een klasse-variabele. De klasse-variabele wordt eerst private gemaakt, waarmee de klasse-variabele dus onzichtbaar wordt buiten de klasse. Daarna worden er zichtbare getter een setter methoden in dezelfde klasse gemaakt. Met deze getter en setter kun je de waarde van de variabele opvragen respectievelijk instellen. Maar er is dankzij de inkapseling dus het verschil dat je aan de getter danwel de setter methoden eventueel wat meer controles of voorbewerkingen kunt doen, voordat de variabele wordt uitgelezen respectievelijk weggeschreven.

Even een voorbeeldje van de "normale" manier om een klasse-variabele vanuit een andere klasse te opvragen danwel instellen:

public class Klasse {

    // Een variabele die overal zichtbaar is.
    public int variabele;

}
public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();

        // Stel de waarde van de variabele in.
        klasse.variabele = 10;

        // Vraag de waarde van de variabele op.
        System.out.println(klasse.variabele);
    }

}

En dan nu een voorbeeldje die gebruik maakt van het inkapselen van de klasse-variabele:

public class Klasse {

    // De variabele wordt onzichtbaar gemaakt.
    private int variabele;

    // De variabele wordt ingekapseld met een zichtbare getter.
    // Je kunt dus alleen op deze manier de waarde opvragen.
    public int getVariabele() {
        return variabele;
    }

    // De variabele wordt ingekapseld met een zichtbare setter.
    // Je kunt dus alleen op deze manier de waarde instellen.
    public void setVariabele(int argVariabele) {
        variabele = argVariabele;
    }

}
public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();

        // Stel de waarde van de variabele in met een setter.
        klasse.setVariabele(10);

        // Vraag de waarde van de variabele op met een getter.
        System.out.println(klasse.getVariabele());
    }

}

Je kunt dit uiteraard ook toepassen op methoden. Alle private methoden zijn onzichtbaar buiten de klasse en je zou deze alleen via de zichtbare methoden kunnen aanroepen.

Terug naar boven

Objecten converteren (casting)

Je kunt objecten converteren (hierna casten genoemd) van een subklasse naar een superklasse en vice versa. Met de cast-operator (SubKlasse) kun je een referentie-variabele converteren van een superklasse naar een subklasse. Dit wordt officieel downcasting (omlaag converteren) genoemd. Het omgekeerde hiervan heet upcasting (omhoog converteren), hierbij wordt de cast-operator (SubKlasse) niet gebruikt.

// Instantieer een String tegen diens superklasse Object.
// Dit is een upcast. Hierbij is de cast-operator (SubKlasse) niet nodig.
Object object = new String("casting");

// Je kunt op deze instantie van Object geen methoden van String gebruiken,
// ondanks dat het is geinstantieerd als een String.
System.out.println(object.substring(0,4));

// Wanneer je de String als zodanig wilt gebruiken, dan moet je het terug
// converteren (downcasten) naar een instantie van String.
String string = (String) object;
System.out.println(string.substring(0,4));

Het voordeel van het downcasten van een superklasse naar een subklasse is dat je hierna alle methoden van de geinstantieerde subklasse direct kunt benaderen. Het upcasten biedt eigenlijk weinig zichtbare voordelen. Over het algemeen wordt de upcast alleen toegepast in de parameter van een methode en dan wordt in die methode het type object nader uitgezocht met behulp van instanceof. Ook wordt de upcast vaak gebruikt om diverse objecten te groeperen in een generieke collectie of map.

Wanneer je bij het casten een ClassCastException krijgt, dan betekent het dat het te casten object domweg geen subklasse of superklasse is van het object waar je naar toe wil converteren.

package test;

public class Test {

    public static void main(String[] args) {

        // Instantieer een String tegen diens superklasse Object.
        Object object = new String("123");

        // Probeer deze te downcasten naar Integer.
        Integer integer = (Integer) object;
    }

}

Exception in thread "main" java.lang.ClassCastException: java.lang.String
    at Test.main(Test.java:11)

De foutmelding spreekt voor zich: je kunt een object van het type java.lang.String niet converteren naar een Integer. Je kunt het type van het object overigens met de getClass() van iedere object opvragen:

package test;

public class Test {

    public static void main(String[] args) {

        // Instantieer een String tegen diens superklasse Object.
        Object object = new String("123");

        // Laat de geinstantieerde klasse zien.
        System.out.println(object.getClass());
    }

}

class java.lang.String

Terug naar boven

Object referenties (object references)

Met de object referenties this en super kun je expliciet verwijzen naar een methode danwel variabele van de huidige klasse of die van de superklasse. Dit wordt met name gedaan wanneer de benaming van een parameter identiek is aan die van een variabele van de huidige klasse en wanneer de benaming van de methode danwel de variabele identiek is aan die van de superklasse (overgeschreven methoden en geschaduwde variabelen).

De this object referentie verwijst expliciet naar de huidige klasse. Eigenlijk is dit de standaard referentie, maar in sommige gevallen zul je gedwongen worden om deze expliciet te gebruiken. Dit komt veel voor bij setters waarbij de naam van de parameter identiek is aan die van de lokale variabele. Een voorbeeldje:

public class Klasse {

    // Declareer en initialiseer een variabele.
    public int variabele;

    // Declareer een setter methode.
    public void setVariabele(int variabele) {

        // Wijs de waarde van de methode parameter "variabele"
        // toe aan de variabele "variabele" van deze klasse.
        this.variabele = variabele;
    }

}

De super object referentie verwijst expliciet naar de superklasse. Een voorbeeldje:

package test;

public class SuperKlasse {

    // Declareer en initialiseer een variabele.
    public String variabele = "SuperVariabele";

    // Declareer een methode.
    public void methode() {

        // Laat zien welke methode dit is.
        System.out.println("SuperMethode");
    }

}
package test;

public class Klasse extends SuperKlasse {

    // Schaduw de variabele van de superklasse.
    public String variabele = "SubVariabele";

    // Overschrijf de methode van de superklasse.
    public void methode() {

        // Laat zien welke methode dit is.
        System.out.println("SubMethode");

        // Roep de methode van de superklasse aan.
        super.methode();

        // Laat de variabele van de huidige klasse zien.
        System.out.println(variabele);

        // Laat de variabele van de superklasse zien.
        System.out.println(super.variabele);
    }

}
package test;

public class Test {

    public static void main(String[] args) {

        // Declareer en instantieer de Klasse.
        Klasse klasse = new Klasse();

        // Roep de methode van de Klasse aan.
        klasse.methode();
    }

}

De uitkomst:

SubMethode
SuperMethode
SubVariabele
SuperVariabele

Super en this referenties

Terug naar boven

Constructeurs koppelen (chaining constructors)

Je kunt constructeurs koppelen met de this() en super() constructeurs. De this() constructeur verwijst naar de constructeur van de huidige klasse en de super() constructeur verwijst vanzelfsprekend naar de constructeur van de superklasse.

Je weet dat een klasse meerdere constructeurs kunt hebben:

public class Auto {

    // De standaard constructeur.
    public Auto() {
        maakAuto();
        aantalAutos++;
    }

    // De tweede constructeur.
    public Auto(String merkType) {
        autoMerkType = merkType;
        maakAuto();
        aantalAutos++;
    }

}

Dit kan dus netter worden opgelost met de this() constructeur:

public class Auto {

    // De standaard constructeur.
    public Auto() {

        // Stuur door naar de tweede constructeur.
        this(null);

        // Hiermee wordt dus de tweede constructeur aangeroepen met een lege
        // parameter. De tweede constructeur doet verder toch hetzelfde.
    }

    // De tweede constructeur.
    public Auto(String merkType) {
        autoMerkType = merkType;
        maakAuto();
        aantalAutos++;
    }

}

De super() constructeur kan gebruikt worden om vanuit de subklasse de constructeur van de superklasse te aanroepen. Dit wordt erg veel gebruikt bij zelfgemaakte excepties. In het onderstaand stukje code zullen we een NullPointerException afvangen en doorgooien als StartenMisluktException.

package test;

import nl.balusc.voertuigen.excepties.StartenMisluktException;

public class Klasse {

    public void methode() throws StartenMisluktException {
        Auto standaardAuto = null;

        try {
            standaardAuto.starten();
            System.out.println("De auto is succesvol gestart.");
        } catch (NullPointerException e) {
            throw new StartenMisluktException("De auto bestaat niet.");
        }
    }

}

De nieuwe StartenMisluktException klasse ziet er als volgt uit:

package nl.balusc.voertuigen.excepties;

public class StartenMisluktException extends Exception {

    public StartenMisluktException(String foutmelding) {

        // Stuur de foutmelding door naar de constructeur
        // van de Exception superklasse.
        super(foutmelding);
    }

}

Even testen:

package test;

import nl.balusc.voertuigen.excepties.StartenMisluktException;

public class Test {

    public static void main(String[] args) {
        Klasse klasse = new Klasse();

        try {
            klasse.methode();
        } catch (StartenMisluktException e) {
            System.out.println("Er is een exceptie opgetreden.");
            e.printStackTrace();
        }
    }

}

Er is een exceptie opgetreden.
nl.balusc.voertuigen.excepties.StartenMisluktException: De auto bestaat niet.
    at test.Klasse.methode(Klasse.java:16)
    at test.Test.main(Test.java:11)

Constructeurs koppelen

Terug naar boven

Opsommingen (enumerations, enums)

Wanneer je een groep constanten hebt en deze op meerdere plaatsen wilt gebruiken, dan kun je deze het beste opsommen in een enum. In het voorbeeld van de klasse AlfaRomeo (zie Overerving hierboven) is de alfaromeoType gedefinieerd als een String, wat je dus uit de losse pols moet invoeren. Wanneer je de feitelijke waarde ervan wilt wijzigen, dan zul je het op meerdere plaatsen moeten aanpassen, wat onhandig kan zijn. Het is dan handiger om deze waarden in een enum te zetten en deze te gebruiken in alfaromeoType.

Je kunt een enum op bijna dezelfde manier bouwen als een class. Er zijn wel enkele ontwerpverschillen: de constructeur van een enum is impliciet private en je kunt deze dus niet als public declareren; de constanten zijn impliciet public static final en je moet deze kommagescheiden geheel bovenaan in de enum definieren, vóór de constructeur en de eventuele initialisatie.

Hieronder staat een voorbeeld hoe zo'n enum kan eruitzien:

package nl.balusc.voertuigen.autotypen;

public enum AlfaRomeoType {

    // Definieer de constanten. Je kunt meerdere constanten scheiden met een komma.
    AR_156_19_JTD_SW(1910, 1330, 850, 4500, 5, 5, "Diesel"),
    AR_BRERA_32_JTS_Q4(3195, 1630, 800, 6200, 6, 4, "Benzine");

    // Initialiseer de ingekapselde variabelen.
    private int cylinderinhoud;
    private int gewicht;
    private int minimumToerental;
    private int maximumToerental;
    private int maximumVersnelling;
    private int maximumZitplaatsen;
    private String brandstofSoort;

    // De constructeur. Deze wordt automatisch aangeroepen zodra je een enum aanroept.
    // De constructeur van een enum is impliciet private. De "private" declaratie is
    // in feite overbodig en "public" is niet toegestaan.
    AlfaRomeoType(
        int cylinderinhoud,
        int gewicht,
        int minimumToerental,
        int maximumToerental,
        int maximumVersnelling,
        int maximumZitplaatsen,
        String brandstofSoort)
    {
        this.cylinderinhoud = cylinderinhoud;
        this.gewicht = gewicht;
        this.minimumToerental = minimumToerental;
        this.maximumToerental = maximumToerental;
        this.maximumVersnelling = maximumVersnelling;
        this.maximumZitplaatsen = maximumZitplaatsen;
        this.brandstofSoort = brandstofSoort;
    }

    // De "getters" om de ingekapselde constanten te opvragen.
    public int getCylinderinhoud() {
        return cylinderinhoud;
    }

    public int getGewicht() {
        return gewicht;
    }

    public int getMinimumToerental() {
        return minimumToerental;
    }

    public int getMaximumToerental() {
        return maximumToerental;
    }

    public int getMaximumVersnelling() {
        return maximumVersnelling;
    }

    public int getMaximumZitplaatsen() {
        return maximumZitplaatsen;
    }

    public String getBrandstofSoort() {
        return brandstofSoort;
    }

}

We passen de klasse AlfaRomeo hier even op aan:

package nl.balusc.voertuigen.autos;

import nl.balusc.voertuigen.Auto;
import nl.balusc.voertuigen.autotypen.AlfaRomeoType;

// De klasse "AlfaRomeo" is een uitbreiding van de klasse "Auto".
public class AlfaRomeo extends Auto {

    // De eigenschappen die per Alfa Romeo kunnen verschillen.
    private AlfaRomeoType alfaRomeoType;

    // De standaard constructeur.
    public AlfaRomeo() {
        this(null);
    }

    // De constructeur, waarbij je een specifieke type kunt opgeven.
    public AlfaRomeo(AlfaRomeoType alfaRomeoType) {
        this.alfaRomeoType = alfaRomeoType;
        aantalAutos++;
    }

    // De volgende methode is dus eigenlijk overbodig geworden.
    /* private void maakAlfaRomeo() {
        ...
    }*/

    ...

    // O.a. de methode "starten" zou je dan als volgt moeten aanpassen.
    public void starten() {
        toerental = alfaRomeoType.getMinimumToerental();
        verlichtingAan = true;
    }
}

Deze kun je dan als volgt aanroepen:

public class AndereKlasse {

    // Declareer en instantieer de referentie "alfaRomeo" met een enum als parameter.
    AlfaRomeo alfaRomeo = new AlfaRomeo(AlfaRomeoType.AR_156_19_JTD_SW);

}

De gegeven AlfaRomeoType enum werkt op exact dezelfde manier als de volgende klasse:

package nl.balusc.voertuigen.autotypen;

public final class AlfaRomeoType {

    // Definieer de constanten.
    public static final AlfaRomeoType AR_156_19_JTD_SW = 
        new AlfaRomeoType(1910, 1330, 850, 4500, 5, 5, "Diesel");
    public static final AlfaRomeoType AR_BRERA_32_JTS_Q4 = 
        new AlfaRomeoType(3195, 1630, 800, 6200, 6, 4, "Benzine");

    // Initialiseer de ingekapselde variabelen.
    private int cylinderinhoud;
    private int gewicht;
    private int minimumToerental;
    private int maximumToerental;
    private int maximumVersnelling;
    private int maximumZitplaatsen;
    private String brandstofSoort;

    // De constructeur is private gemaakt zodat je de klasse niet zélf kunt
    // instantieren, net als bij de enums. Dit is om te voorkomen dat je zelf
    // je constanten gaat definieren buiten een enum, daar waar het hoort.
    private AlfaRomeoType(
        int cylinderinhoud,
        int gewicht,
        int minimumToerental,
        int maximumToerental,
        int maximumVersnelling,
        int maximumZitplaatsen,
        String brandstofSoort)
    {
        this.cylinderinhoud = cylinderinhoud;
        this.gewicht = gewicht;
        this.minimumToerental = minimumToerental;
        this.maximumToerental = maximumToerental;
        this.maximumVersnelling = maximumVersnelling;
        this.maximumZitplaatsen = maximumZitplaatsen;
        this.brandstofSoort = brandstofSoort;
    }

    // De "getters" om de ingekapselde constanten te opvragen.
    public int getCylinderinhoud() {
        return cylinderinhoud;
    }

    public int getGewicht() {
        return gewicht;
    }

    public int getMinimumToerental() {
        return minimumToerental;
    }

    public int getMaximumToerental() {
        return maximumToerental;
    }

    public int getMaximumVersnelling() {
        return maximumVersnelling;
    }

    public int getMaximumZitplaatsen() {
        return maximumZitplaatsen;
    }

    public String getBrandstofSoort() {
        return brandstofSoort;
    }

}

De belangrijkste voordelen van enums boven zulke final klassen zijn dat de enums kunnen worden gebruikt in switch statementen en dat je de enums kunt serialiseren (opslaan als een bestand, zegmaar) en dat je de enums zonder verdere extra code direct met elkaar kunt vergelijken met behulp van de equals() methode.

Terug naar boven


In het volgende hoofdstuk wordt uitgelegd hoe je je Java code buiten een IDE om kunt uitvoeren: Uitvoeren.

Copyright - Niets van deze pagina mag worden overgenomen zonder uitdrukkelijke toestemming.

(C) April 2006, BalusC

2 comments:

julia said...

Beste Balusc,

Een heel fijne tutorial, bedankt voor de goede uitleg!

Ik had een vraagje waarmee je me misschien kan helpen. Ik wil graag een distributie aan de constructor geven van een bepaald type auto, dus 20% van de eerste type, 30% van de tweede e.d.! Kan ik dit op de een of andere manier erin verwerken?

Alvast bedankt en nogmaals hele fijne tutorial!

BalusC said...

@julia: dat kun je doen door een nieuwe constructeur toe te voegen, of door een nieuw argument aan de bestaande constructeur toe te voegen, zoiets als

public AlfaRomeo(String type, int distributie) {
    alfaRomeoType = type;
    alfaRomeoDistributie = distributie;
    maakAlfaRomeo();
    aantalAutos++;
}