Hoofdstuk 3 Klassen - Info

advertisement
Info-books
HO37
Toegepaste Informatica
Deel 37: Programmeren in Java
K. Behiels - J. Gils
Hoofdstuk 3
3.1
Klassen
Klassen, variabelen en methoden
3.1.1 Wat is een klasse?
Een struct van ANSI C is een goede manier om een reeks van gerelateerde variabelen te
groeperen. Veronderstel dat je informatie over films wenst te verwerken. In zo’n struct kun
je echter alleen maar variabelen opnemen. Objectgericht programmeren betekent dat je niet
alleen de gegevens, maar ook de functies die je op die gegevens gaat toepassen, in één
geheel moet groeperen. Daarvoor bestaat in Java de klasse (class). In tegenstelling tot een
struct van ANSI C bevat een klasse naast variabelen (variables --- ook attributes, fields of
data members genoemd) ook methoden (methods). Dit zijn de functies die je op de
variabelen kunt toepassen. Methoden onderscheiden zich van variabelen door het gebruik
van ronde haakjes. Je gebruikt ze op dezelfde manier als de variabelen.
Een voorbeeld:
/** Voorbeeld klasse01 --- Film.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Film {
private String mTitel;
private int mJaar;
private String mActeur;
private String mActrice;
public String getTitel() {
return mTitel;
}
public int getJaar() {
return mJaar;
}
public String getActeur() {
return mActeur;
}
public String getActrice() {
return mActrice;
}
public void vulGegevensIn(String titel, int jaar,
String acteur, String actrice) {
mTitel = titel;
mJaar = jaar;
mActeur = acteur;
mActrice = actrice;
}
}
K. Behiels - J. Gils
Java
39
De gereserveerde woorden public en private regelen de toegankelijkheid van de
variabelen en de methoden. Het gereserveerde woord public wijst op algemene
toegankelijkheid, het gereserveerde woord private duidt aan dat je gegevens alleen
onrechtstreeks kunt benaderen. Een van de bedoelingen van objectgeoriënteerd werken is
het inkapselen van de gegevens. Daarom moet je de variabelen altijd private maken.
3.1.2 Objectvariabelen
De klasse zelf is slechts een beschrijving van het datatype. Om variabelen van dit type te
kunnen gebruiken moet je zogenaamde objecten van dit type declareren. Een object kun je
beschouwen als een variabele van een klasse-type. Objecten in Java zijn altijd dynamisch.
Voor je ze kunt gebruiken moet je ze eerst met behulp van de new-operator creëren.
Objectvariabelen zijn referenties naar objecten, min of meer te vergelijken met
pointervariabelen uit ANSI C (maar dan zonder de nadelen ervan).
Film movie;
Op dit moment is er alleen een declaratie van het movie-object gedaan. Er bestaat nog
geen object! Om een object te creëren moet je het volgende schrijven:
movie = new Film();
Meestal zul je beide voorgaande opdrachten tot één opdracht combineren:
Film movie = new Film();
Je kunt ook objecten aan mekaar toekennen:
Film favoriet = movie;
In dit geval is er geen extra geheugenruimte gecreëerd, favoriet en movie verwijzen nu
naar dezelfde geheugenruimte!
Je kunt een objectvariabele ook expliciet aan null gelijkstellen. Hiermee duid je aan dat
de variabele nergens naar verwijst.
Film favoriet = null;
3.1.3 Werken met objecten
Om een uitvoerbaar programma te verkrijgen moet je net zoals in ANSI C een mainfunctie voorzien. In Java kan deze main echter niet op zichzelf bestaan, ze moet altijd deel
uitmaken van een klasse. De notatie die je bij objecten gebruikt is dezelfde als bij een
struct in ANSI C.
In het volgende voorbeeld maak je tweemaal een object van de klasse Film, vul je de
gegevens in en druk je daarna de inhoud van het object af:
40
Java
K. Behiels – J. Gils
/** Voorbeeld klasse01 --- DemoFilm.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoFilm {
public static void main(String[] args) {
Film silence = new Film();
silence.vulGegevensIn("The silence of the lambs", 1991,
"Anthony Hopkins", "Jodie Foster");
System.out.println("titel: " + silence.getTitel());
System.out.println("jaar: " + silence.getJaar());
System.out.println("acteur: " + silence.getActeur());
System.out.println("actrice: " + silence.getActrice());
System.out.println();
Film sleepless = new Film();
sleepless.vulGegevensIn("Sleepless in Seattle", 1993,
"Tom Hanks", "Meg Ryan");
System.out.println("titel: " + sleepless.getTitel());
System.out.println("jaar: " + sleepless.getJaar());
System.out.println("acteur: " + sleepless.getActeur());
System.out.println("actrice: "
+ sleepless.getActrice());
System.out.println();
}
}
De afdruk van dit programma is:
titel: The silence of the lambs
jaar: 1991
acteur: Anthony Hopkins
actrice: Jodie Foster
titel: Sleepless in Seattle
jaar: 1993
acteur: Tom Hanks
actrice: Meg Ryan
? Parameteroverdracht
In ANSI C heb je geen keuze, in alle gevallen wordt de waarde van de parameter
doorgegeven (call by value). In Java is dit ook zo. Bij primitieve datatypes wordt de
waarde doorgegeven, bij objecten en tabellen als parameter is de doorgegeven waarde een
verwijzing naar het object of de tabel.
Dit kan op het eerste gezicht tot vervelende situaties leiden, hoe schrijf je bijvoorbeeld een
methode om twee getallen te verwisselen?
K. Behiels - J. Gils
Java
41
Eerst zoals in ANSI C:
public class Poging1 {
public static void main(String[] args) {
long een = 100;
long twee = 200;
System.out.println("een: " + een + "
verwissel(een, twee);
System.out.println("een: " + een + "
twee: " + twee);
twee: " + twee);
}
public static void verwissel(long x, long y) {
long hulp = x;
x = y;
y = hulp;
}
}
De afdruk is:
een: 100
een: 100
twee: 200
twee: 200
Dit lukt niet. Lokaal in verwissel worden de getallen wel verwisseld, in de mainmethode wijzigt er niets. De enige manier is ervoor te zorgen dat er een object als
parameter wordt doorgegeven.
We maken eerst afzonderlijke klasse Getallenpaar (in feite de vervanging van de struct
van ANSI C) en gebruiken ze in de programmaklasse Poging2:
class GetallenPaar {
public long een = 100;
public long twee = 200;
}
public class Poging2 {
public static void main(String[] args) {
GetallenPaar gp = new GetallenPaar();
System.out.println("een: " + gp.een
+ "
twee: " + gp.twee);
verwissel(gp);
System.out.println("een: " + gp.een
+ "
twee: " + gp.twee);
}
public static void verwissel(GetallenPaar p) {
long hulp = p.een;
p.een = p.twee;
p.twee = hulp;
}
}
De afdruk is:
een: 100
een: 200
42
twee: 200
twee: 100
Java
K. Behiels – J. Gils
Nu roep je de methode verwissel met een object als argument op. De compiler past dan
schijnbaar call by reference toe. De methode verwissel wordt alleen voor
Getallenpaar objecten gebruikt, daarom is het beter ze in de klasse zelf op te nemen. Zo
kom je tot de volgende, verbeterde oplossing:
public class GetallenPaar {
private long mEen = 100;
private long mTwee = 200;
public long getEen() {
return mEen;
}
public long getTwee() {
return mTwee;
}
public void setEen(long een) {
mEen = een;
}
public void setTwee(long twee) {
mTwee = twee;
}
De bovenstaande klasse pas je toe in de uitvoerbare klasse Poging3:
public class Poging3 {
public static void main(String[] args) {
GetallenPaar gp = new GetallenPaar();
System.out.println("een:
+ " twee: "
gp.verwissel();
System.out.println("een:
+ " twee: "
" + gp.getEen()
+ gp.getTwee());
" + gp.getEen()
+ gp.getTwee());
}
}
Ook nu is de afdruk:
een: 100
een: 200
twee: 200
twee: 100
Een alternatief is een tabel met één element als argument te gebruiken. Tabellen worden
immers altijd schijnbaar via call by reference doorgegeven. Zie de klasse Poging4:
public class Poging4 {
public static void main(String[] args) {
long een = 100;
long twee = 200;
System.out.println("een: " + een + "
twee: " + twee);
long[] eenObj = new long[1];
eenObj[0] = een;
long[] tweeObj = new long[1];
tweeObj[0] = twee;
K. Behiels - J. Gils
Java
43
verwissel(eenObj, tweeObj);
een = eenObj[0];
twee = tweeObj[0];
System.out.println("een: " + een + "
twee: " + twee);
}
public static void verwissel(long[] eenObject,
long[] tweeObject) {
long temp = eenObject[0];
eenObject[0] = tweeObject[0];
tweeObject[0] = temp;
}
}
3.2
Specifieke methoden
3.2.1 Objecten creëren
Als je een object van een bepaalde klasse maakt wordt altijd een speciale methode
uitgevoerd, de constructor. Indien je zelf geen constructor in je klasse voorziet zal de
compiler een standaardconstructor die niets doet voorzien.
De bedoeling van de constructor is, naast de creatie van de objecten, ervoor te zorgen dat
de variabelen van de juiste beginwaarden worden voorzien. Door zelf een constructor te
schrijven kun je fouten die het gevolg zijn van foutieve beginwaarden vermijden. Als je
zelf geen beginwaarden geeft vult Java in elk geval nulwaarden in.
De constructor heeft twee eigenschappen waardoor hij van de gewone methoden verschilt:
?
?
een constructor heeft altijd dezelfde naam als de klasse waartoe hij behoort;
een constructor heeft nooit een return-waarde, je mag zelfs geen void
vermelden.
We herschrijven het voorbeeld met de klasse Film als volgt:
/** Voorbeeld klasse02 --- Film.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Film {
private String mTitel;
private int mJaar;
private String mActeur;
private String mActrice;
44
Java
K. Behiels – J. Gils
public Film(String titel, int jaar,
String acteur, String actrice) {
mTitel = titel;
mJaar = jaar;
mActeur = acteur;
mActrice = actrice;
}
public String getTitel() {
return mTitel;
}
public int getJaar() {
return mJaar;
}
public String getActeur() {
return mActeur;
}
public String getActrice() {
return mActrice;
}
}
In het volgende programma gebruiken we de klasse Film :
/** Voorbeeld klasse02 --- DemoFilm.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoFilm {
public static void main(String[] args) {
Film silence = new Film("The silence of the lambs",
1991,"Anthony Hopkins", "Jodie Foster");
System.out.println("titel: " + silence.getTitel());
System.out.println("jaar: " + silence.getJaar());
System.out.println("acteur: " + silence.getActeur());
System.out.println("actrice: " + silence.getActrice());
System.out.println();
Film sleepless = new Film("Sleepless in Seattle", 1993,
"Tom Hanks", "Meg Ryan");
System.out.println("titel: " + sleepless.getTitel());
System.out.println("jaar: " + sleepless.getJaar());
System.out.println("acteur: " + sleepless.getActeur());
System.out.println("actrice: "
+ sleepless.getActrice());
System.out.println();
}
}
De afdruk van dit programma is:
titel: The silence of the lambs
jaar: 1991
acteur: Anthony Hopkins
K. Behiels - J. Gils
Java
45
actrice: Jodie Foster
titel: Sleepless in Seattle
jaar: 1993
acteur: Tom Hanks
actrice: Meg Ryan
Vanaf het moment dat je zelf een constructor schrijft zal de compiler geen standaard
constructor meer voorzien. Wil je dit toch dan moet je hem expliciet in de klasse opnemen.
Je mag verschillende versies va n de constructor schrijven op voorwaarde dat de parameters
in aantal, volgorde of datatype verschillen. Deze manier van werken heet overloading.
Bekijk in dit verband het volgende voorbeeld:
/** Voorbeeld klasse03 --- Film.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Film {
private String mTitel;
private int mJaar;
private String mActeur;
private String mActrice;
public Film() {
mTitel = "";
mActeur = "";
mActrice = "";
}
public Film(String titel, int jaar, String acteur,
String actrice) {
mTitel = titel;
mJaar = jaar;
mActeur = acteur;
mActrice = actrice;
}
public String getTitel() {
return mTitel;
}
public int getJaar() {
return mJaar;
}
public String getActeur() {
return mActeur;
}
public String getActrice() {
return mActrice;
}
}
46
Java
K. Behiels – J. Gils
Een programma ter demonstratie:
/** Voorbeeld klasse03 --- DemoFilm.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoFilm {
public static void main(String[] args) {
Film silence = new Film("The silence of the lambs",
1991, "Anthony Hopkins", "Jodie Foster");
System.out.println("titel: " + silence.getTitel());
System.out.println("jaar: " + silence.getJaar());
System.out.println("acteur: " + silence.getActeur());
System.out.println("actrice: " + silence.getActrice());
System.out.println();
Film sleepless = new Film("Sleepless in Seattle", 1993,
"Tom Hanks", "Meg Ryan");
System.out.println("titel: " + sleepless.getTitel());
System.out.println("jaar: " + sleepless.getJaar());
System.out.println("acteur: " + sleepless.getActeur());
System.out.println("actrice: "
+ sleepless.getActrice());
System.out.println();
Film leeg = new Film();
System.out.println("titel: " + leeg.getTitel());
System.out.println("jaar: " + leeg.getJaar());
System.out.println("acteur: " + leeg.getActeur());
System.out.println("actrice: " + leeg.getActrice());
System.out.println();
}
}
De afdruk van dit programma is:
titel: The silence of the lambs
jaar: 1991
acteur: Anthony Hopkins
actrice: Jodie Foster
titel: Sleepless in Seattle
jaar: 1993
acteur: Tom Hanks
actrice: Meg Ryan
titel:
jaar: 0
acteur:
actrice:
K. Behiels - J. Gils
Java
47
3.2.2 Objecten verwijderen
In Java is een zogenaamde destructor overbodig: er wordt met een garbage collection
gewerkt. De Java Virtuele Machine zorgt zelf voor het terug vrijgeven van het dynamisch
geheugen wanneer het object niet meer toegankelijk is. Dit is een proces dat op de
achtergrond draait en een lage prioriteit heeft. Normaal gesproken wordt er geen garbage
collection uitgevoerd tot het programma ergens op wacht of als er extra geheugen nodig is.
Bij de uitvoering van de garbage collection wordt eerst de finalize-methode
opgeroepen. Je kunt, zoals in het onderstaande voorbeeld, zelf een finalize-methode in
je klasse opnemen. Het uitvoeren van deze methode is niet altijd gegarandeerd: de garbage
collection treedt dikwijls pas in werking bij het beëindigen van he t programma. Via de
methode System.gc kun je ervoor zorgen dat de finalize-methode voor alle objecten
wordt uitgevoerd. We demonstreren dit aan de hand van het volgende theoretische
voorbeeld:
/** Voorbeeld klasse04 --- Demo.java
* @author Kristiaan Behiels
* @version 1.1 28/02/2001
*/
import
import
import
import
java.io.IOException;
java.io.File;
java.io.FileWriter;
java.io.PrintWriter;
public class Demo {
private static
private static
private static
private static
boolean msGcRun = false;
boolean msStop = false;
int msGemaakt = 0;
int msFinalized = 0;
private String mTekst;
private int mNr = 0;
public Demo(String bericht) {
mNr = ++msGemaakt;
mTekst = bericht;
if (msGemaakt == 100) {
System.out.println("nr 100 gemaakt");
}
}
public static boolean getStop() {
return msStop;
}
public static int getGemaakt() {
return msGemaakt;
}
public static int getFinalized() {
return msFinalized;
}
48
Java
K. Behiels – J. Gils
protected void finalize() {
if (!msGcRun) {
msGcRun = true;
System.out.println("Begin met finalize"
+ " na creatie van "
+ msGemaakt + " objecten.");
}
if (mNr == 100) {
System.out.println("Finalize voor object"
+ " nr 100");
System.out.println("Zet creatieswitch stop"
+ " op true");
msStop = true;
}
msFinalized++;
if (msFinalized >= msGemaakt) {
System.out.println("Finalize voor alle "
+ msFinalized + " objecten");
}
}
}
In het programma maken we Demo-objecten tot de finalize-methode in actie treedt:
/** Voorbeeld klasse04 --- TestDemo.java
* @author Kristiaan Behiels
* @version 1.1 28/02/2001
*/
import java.io.IOException;
public class TestDemo {
public static void main(String[] args) throws IOException {
while (!Demo.getStop()) {
new Demo("Test finalize!");
}
System.out.println("Totaal gemaakt:
"
+ Demo.getGemaakt() + " objecten.");
System.out.println("Totaal finalized: "
+ Demo.getFinalized() + " objecten.");
System.gc();
System.out.println("Einde!");
}
}
Zonder de System.gc-methode kregen we het volgende resultaat (niet voor alle objecten
werd de finalize-methode uitgevoerd):
nr 100 gemaakt
Begin met finalize na creatie van 7396 objecten.
Finalize voor object nr 100
Zet creatieswitch stop op true
Totaal gemaakt:
12443 objecten.
Totaal finalized: 7396 objecten.
Einde!
Met de System.gc-methode werd voor alle objecten de finalize-methode uitgevoerd:
K. Behiels - J. Gils
Java
49
public static boolean isEuroMunt(String munt) {
for (int i = 0; i < munten.length; i++) {
if (munten[i].getMuntCode().equalsIgnoreCase(
munt))
return true;
}
return false;
}
}
Een demonstratie van het gebruik van de EuroConversie-klasse vind je hierna:
/** Voorbeeld klasse13 --- DemoEuro.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class DemoEuro {
public static void main(String[] args) throws IOException {
BufferedReader is =
new BufferedReader(
new InputStreamReader(System.in));
System.out.print("Geef het bedrag: ");
double bedrag = Double.parseDouble(is.readLine());
System.out.print("Geef de euromunt (code): ");
String munt = is.readLine().toUpperCase();
System.out.println("\n" + bedrag + " " + munt + " = "
+ EuroConversie.naarEuro(munt, bedrag)
+ " €");
System.out.println(bedrag + " € = "
+ EuroConversie.naarLokaleMunt(munt,
bedrag)
+ " " + munt);
}
}
Een mogelijke uitvoer van dit programma is:
Geef het bedrag: 958000
Geef de euromunt (code): BEF
958000.0 BEF = 23748.199673276333 €
958000.0 € = 3.86456242E7 BEF
In dit programma gebeurt nog geen controle op foutieve invoer, om praktisch bruikbaar te
zijn moet je nog exception handling toevoegen (zie later).
K. Behiels - J. Gils
Java
69
3.3.3 Klassen in klassen
In Java kun je ook een klasse binnen een andere klasse definiëren. Dergelijke klassen noem
je inner classes. Je kunt ze onderverdelen in 4 soorten:
?
Een static member-klasse is een static member van een andere klasse. De klasse
gedraagt zich als een gewone klasse. Ze heeft ook toegang tot alle static
members van eventuele andere static member-klassen, inclusief die van de
klasse waarin ze gedefinieerd is. De naam van de static member-klassen moet
verschillen van die van de omhullende klasse.
?
Een member-klasse is te vergelijken met een instance-variabele of - methode. Ze
maakt altijd deel uit van een object van de klasse waarbinnen ze gedefinieerd is en
ze heeft toegang tot al de bijhorende variabelen en methoden.
?
Een local klasse is gedefinieerd binnen een blok Java-code. Net zoals een lokale
variabele is de klasse alleen geldig binnen dat blok.
?
Een anonymous klasse is een soort van lokale klasse zonder naam. Ze combineert
de syntaxis voor een klassedefinitie met de syntaxis voor de objectdeclaratie.
? Static member-klasse
Als voorbeeld wordt het voorbeeld van de euroconversie hernomen. Je kunt de klasse
EuroMunt als static member-klasse in de klasse Euroconversie opnemen, het
voordeel hiervan is dat je nu geen getters meer nodig hebt.
/** Voorbeeld klasse14 --- EuroConversie.java (static inner class)
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class EuroConversie
private static class
private double
private String
private String
{
EuroMunt {
mOmrekeningsKoers;
mMuntCode;
mMunt;
public EuroMunt(double omrekeningsKoers,
String muntCode, String munt) {
mOmrekeningsKoers = omrekeningsKoers;
mMuntCode = muntCode;
mMunt = munt;
}
}
private static final EuroMunt munten[] = {
new EuroMunt(13.7603, "ATS", "Oostenrijkse schilling"),
new EuroMunt(40.3399, "BEF", "Belgische frank"),
new EuroMunt(1.95583, "DEM", "Duitse mark"),
new EuroMunt(166.386, "ESP", "Spaanse peseta"),
new EuroMunt(5.94573, "FIM", "Finse mark"),
new EuroMunt(6.55957, "FRF", "Franse frank"),
new EuroMunt(340.750, "GRD", "Griekse drachme"),
new EuroMunt(0.787564, "IEP", "Iers pond"),
new EuroMunt(1936.27, "ITL", "Italiaanse lire"),
70
Java
K. Behiels – J. Gils
new EuroMunt(40.3399, "LUF", "Luxemburgse frank"),
new EuroMunt(2.20371, "NLG", "Nederlandse gulden"),
new EuroMunt(200.482, "PTE", "Portugese escudo")
};
private static int euroMuntIndex(String munt) {
for (int i = 0; i < munten.length; i++) {
if (munten[i].mMuntCode.equalsIgnoreCase(munt))
return i;
}
return -1;
}
public static double naarEuro(String munt, double bedrag) {
return bedrag /
munten[EuroMuntIndex(munt)].mOmrekeningsKoers;
}
public static double naarLokaleMunt(String munt,
double bedrag) {
return bedrag *
munten[EuroMuntIndex(munt)].mOmrekeningsKoers;
}
public static boolean isEuroMunt(String munt) {
for (int i = 0; i < munten.length; i++) {
if (munten[i].mMuntCode.equalsIgnoreCase(munt))
return true;
}
return false;
}
}
De afzonderlijke klasse EuroMunt vervalt nu. In de klasse DemoEuro hoef je niets te
wijzigen.
? Member-klasse
Member-klassen kunnen niet dezelfde naam hebben als de omhullende klasse of de
package waartoe ze behoren. Ze kunnen geen static members (= variabelen, methoden
of klassen) bevatten, wel constanten (= zowel static als final). De objecten ervan
hebben toegang tot alle variabelen en methoden van de klasse waarin ze zijn vervat.
Als voorbeeld hernemen we de laatste versie van het Film-voorbeeld. De afzonderlijke
klasse Acteur vervalt. Zij wordt nu binnen de klasse Film gedefinieerd. In het
hoofdprogramma hoef je niets te wijzigen.
K. Behiels - J. Gils
Java
71
/** Voorbeeld klasse15 --- Film.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Film {
public class Acteur {
private String mNaam;
// overige gegevens
public Acteur() {
mNaam = "";
}
public Acteur(String naam) {
mNaam = naam;
}
public Acteur(Acteur acteur) {
mNaam = acteur.mNaam;
}
public boolean isIngevuld() {
return mNaam.equals("") != true;
}
public String getNaam() {
return mNaam;
}
}
private
private
private
private
String mTitel;
int mJaar;
Acteur mActeur;
Acteur mActrice;
public Film(String titel, int jaar, String acteur,
String actrice) {
mTitel = titel;
mJaar = jaar;
mActeur = new Acteur(acteur);
mActrice = new Acteur(actrice);
}
public Film(String titel, int jaar) {
this(titel, jaar, "", "");
}
public Film(String titel) {
this(titel, 0, "", "");
}
public Film() {
this("", 0, "", "");
}
public Film(Film film) {
this(film.mTitel, film.mJaar,
film.getActeurNaam(),
film.getActriceNaam());
}
72
Java
K. Behiels – J. Gils
public String getTitel() {
return mTitel;
}
public String getJaar() {
if (mJaar != 0)
return (new Integer(mJaar)).toString();
else
return "onbekend";
}
public String getActeurNaam() {
if (mActeur.isIngevuld())
return mActeur.getNaam();
else
return "onbekend";
}
public String getActriceNaam() {
if (mActeur.isIngevuld())
return mActrice.getNaam();
else
return "onbekend";
}
}
In het volgende programma maak je gebruik van de klasse Film waarin de klasse Acteur
als inner class vervat zit:
/** Voorbeeld klasse15 --- DemoFilm.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoFilm {
public static void main(String[] args) {
Film silence = new Film("The silence of the lambs",
1991, "Anthony Hopkins", "Jodie Foster");
System.out.println("titel: " + silence.getTitel());
System.out.println("jaar: " + silence.getJaar());
System.out.println("cast: " + silence.getActeurNaam()
+ "
" + silence.getActriceNaam()
+ "\n");
Film sleepless = new Film("Sleepless in Seattle",
1993);
System.out.println("titel: " + sleepless.getTitel());
System.out.println("jaar: " + sleepless.getJaar());
System.out.println("cast: " + sleepless.getActeurNaam()
+ "
" + sleepless.getActriceNaam()
+ "\n");
Film voorkeur = new Film("The Matrix");
System.out.println("titel: " + voorkeur.getTitel());
System.out.println("jaar: " + voorkeur.getJaar());
System.out.println("cast: " + voorkeur.getActeurNaam()
+ "
" + voorkeur.getActriceNaam()
+ "\n");
}
}
K. Behiels - J. Gils
Java
73
De afdruk van dit programma is zoals bij het Klasse6 voorbeeld:
titel: The silence of the lambs
jaar: 1991
cast: Anthony Hopkins
Jodie Foster
titel: Sleepless in Seattle
jaar: 1993
cast: onbekend
onbekend
titel: The Matrix
jaar: onbekend
cast: onbekend
onbekend
? Lokale klasse
Indien je een member-klasse alleen binnen één methode van de haar omvattende klasse
gebruikt kun je ze in de betreffende methode als lokale klasse opnemen.
Zoals bij alle member-klassen hebben de objecten van een lokale klasse toegang tot alle
variabelen en methoden, ook al zijn ze private, van de klasse waarin ze gedefinieerd
zijn. Daarenboven hebben ze toegang tot alle final lokale variabelen en final
parameters van de lokale methode (final kun je ook toepassen op lokale variabelen en
zelfs op methodeargumenten).
Een lokale klasse is alleen geldig binnen het blok waarbinnen ze gedefinieerd is. Ze kan
niet public, protected, private of static gedeclareerd worden. Zoals bij de
member-klassen kunnen ze geen static variabelen, methoden of klassen bevatten met
uitzondering van constanten die static final gedeclareerd worden.
Een puur theoretisch voorbeeld:
/** Voorbeeld klasse16 --- Resultaat.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Resultaat {
private int mAantal;
private double[] mResultaten;
public Resultaat() {
mResultaten = new double[3];
}
public Resultaat(double[] resultaten) {
mAantal = resultaten.length;
int aantal = 3;
if (mAantal > 3)
aantal = mAantal;
mResultaten = new double[aantal];
for (int i = 0; i < mAantal; ++i) {
mResultaten[i] = resultaten[i];
}
}
74
Java
K. Behiels – J. Gils
public String topDrie(String race) {
class Tierce {
private String mRaceNaam;
public Tierce(String raceNaam) {
mRaceNaam = raceNaam;
}
public String geefTopDrie() {
return mRaceNaam + ": "
+ mResultaten[0] + " "
+ mResultaten[1] + " "
+ mResultaten[2];
}
}
Tierce top = new Tierce(race);
return top.geefTopDrie();
}
}
Ook hier een klein testprogrammaatje:
/** Voorbeeld klasse16 --- DemoResultaat.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoResultaat {
public static void main(String[] args) {
double[] data = { 49.05, 49.61, 50.21, 50.46, 50.49 };
Resultaat result = new Resultaat(data);
System.out.println(result.topDrie("Kentucky Derby"));
}
}
De afdruk van dit programma is:
Kentucky Derby: 49.05 49.61 50.21
? Anonieme klasse
Een anonieme klasse is een lokale klasse zonder naam. Ze wordt gedefinieerd en
gedeclareerd in een enkelvoudige uitdrukking waarbij de new-operator wordt gebruikt.
Omdat het een lokale klasse is gelden hiervoor dezelfde restricties als bij de lokale klasse.
Omdat de klasse geen naam heeft kan er ook geen constructor zijn.
De formele syntaxis is als volgt:
new class-name ( [argument-list]) {
class-body
}
K. Behiels - J. Gils
Java
75
ofwel:
new interface-name() {
class-body
}
Wanneer gebruik je een anonieme klasse? Je mag ze uitsluitend gebruiken als de klasse uit
erg weinig code bestaat, er slechts één object van nodig is en ze onmiddellijk na de
definitie ervan gebruikt wordt.
Een puur theoretisch voorbeeld, gebaseerd op het voorbeeld van de lokale klasse:
/** Voorbeeld klasse17 --- Resultaat.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class Resultaat {
private int mAantal;
private double[] mResultaten;
public Resultaat() {
mResultaten = new double[3];
}
public Resultaat(double[] resultaten) {
mAantal = resultaten.length;
int aantal = 3;
if (mAantal > 3)
aantal = mAantal;
mResultaten = new double[aantal];
for (int i = 0; i < mAantal; ++i) {
mResultaten[i] = resultaten[i];
}
}
public Object topDrie() {
return new Object() {
private String mRaceNaam = "Kentucky Derby";
};
public String toString() {
return mRaceNaam + ": "
+ mResultaten[0] + " "
+ mResultaten[1] + " "
+ mResultaten[2];
}
// einde van return!
}
}
De klasse Object kun je altijd als klassenaam gebruiken op die plaatsen waar er een nodig
is.
76
Java
K. Behiels – J. Gils
Het hoofdprogramma wordt dan:
/** Voorbeeld klasse17 --- DemoResultaat.java
* @author Kristiaan Behiels
* @version 1.0 26/02/2001
*/
public class DemoResultaat {
public static void main(String[] args) {
double[] data = { 49.05, 49.61, 50.21, 50.46, 50.49 };
Resultaat result = new Resultaat(data);
System.out.println(result.topDrie());
}
}
De afdruk van dit programma is:
Kentucky Derby: 49.05 49.61 50.21
Anonieme klassen komen veel voor bij grafische toepassingen. In het volgende voorbeeld
zie je een anonieme klasse als argument van de addWindowListener-methode (verder
zonder verklaring):
/** Voorbeeld klasse18 --- DemoAnonymous.java
* @author Kristiaan Behiels
* @version 1.1 01/03/2001
*/
import
import
import
import
import
import
java.awt.Color;
java.awt.Font;
java.awt.Graphics;
java.awt.event.WindowAdapter;
java.awt.event.WindowEvent;
javax.swing.JFrame;
public class DemoAnonymous extends JFrame {
static private final int REGEL = 15;
static private final int LETTER = 5;
public DemoAnonymous() {
setBackground(Color.lightGray);
setForeground(Color.blue);
}
public void paint(Graphics g) {
g.drawRect(2 * LETTER, 2 * REGEL, 46 * LETTER,
7 * REGEL);
Font f = new Font("Helvetica", Font.ITALIC, 36);
g.setFont(f);
g.drawString("Java is Leuk!", 4* LETTER, 6 * REGEL);
}
K. Behiels - J. Gils
Java
77
public static void main(String[] args) {
JFrame frame = new DemoAnonymous();
frame.setTitle("DemoAnonymous");
frame.setSize(50 * LETTER, 10 * REGEL);
frame.setVisible(true);
// anonieme klasse van het type WindowsAdapter
frame.addWindowListener( new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}
78
Java
K. Behiels – J. Gils
Besluit
Een klasse is een raamwerk voor het maken van objecten. In een klasse neem je zowel
variabelen als methoden die je op die variabelen kunt toepassen op. Een klasse kan twee
soorten variabelen en methoden bevatten. Normaal horen de variabelen en methoden bij de
objecten maar je kunt (met behulp van static) ook klassevariabelen en klassemethoden
voorzien. Die behoren tot de klasse zelf en zijn onafhankelijk van de objecten.
Objecten zijn altijd dynamisch, je moet ze met de new-operator creëren. De algemene
vorm is:
klassenaam objectvariabelenaam =
new klassenaam(argument, argument, …);
De creatie van een object gebeurt altijd via een constructor (een speciale klassemethode).
Als je er zelf geen voorziet wordt een standaardconstructor uitgevoerd die niets doet. Bij
de creatie van een object krijgen de variabelen automatisch nulwaarden (bij dynamische
variabelen zijn dit nullpointers!). Een destructor is overbodig omdat Java met een garbage
collection werkt die de objecten die niet meer nodig zijn automatisch opruimt.
Om een programma te kunnen uitvoeren moet je een main-methode hebben. Ze moet altijd
static zijn en deel uitmaken van een klasse.
Parameteroverdracht gebeurt in Java altijd via call by reference, behalve bij de primitieve
datatypen waar call by value van toepassing is.
Naast variabelen van de primitieve datatypen kun je in een object ook objecten van andere
klassen als variabele opnemen.
Het gereserveerde woord this kun je gebruiken om naar het eigen object te verwijzen of
om in een constructor een andere constructor van de eigen klasse op te roepen.
Binnen een klasse kun je naast variabelen en methoden ook andere klassedefinities
opnemen. Dit noemt men inner classes. Er zijn vier soorten, met name de static member,
de member, de local en de anonymous inner class.
Wat je moet kennen en kunnen:
?
?
?
?
?
?
?
een klassedefinitie uitschrijven, daarin de nodige constructors en andere methoden
voorzien en er objecten van creëren;
een klasse met een main-methode schrijven om met objecten van eigen klassedefinities
te werken;
de verschillen uitleggen tussen de gewone variabelen en methoden en de klasseversies
ervan en ze op de juiste manier toepassen;
klassen uitschrijven waarin als variabelen niet alleen primitieve datatypen maar ook
objecten voorkomen;
inner classes op een juiste manier toepassen;
de wijze van parameteroverdracht in Java begrijpen en er rekening mee houden;
op een correcte wijze van het gereserveerde woord this gebruik maken.
K. Behiels - J. Gils
Java
79
Opdrachten
1. Beantwoord de volgende vragen:
a. Welke constructor wordt bij het object leeg gebruikt in het voorbeeld Klasse03?
Wat gebeurt er als je de standaard constructor weglaat? Wat is de afdruk als je de
standaard constructor leeg laat?
b. Naar aanleiding van het programmavoorbeeld Klasse04: bestaat er ook een
standaard copy-constructor naar analogie van de standaard constructor (Java
voorziet wel een clone-methode)?
c. Geef aan welke van de drie gemiddelde methoden bij de verschillende oproepen in
de main-methode van het voorbeeld Klasse10 (klasse DemoTierce) gebruikt
worden.
d. Wat gebeurt er als je in het programmavoorbeeld Klasse13 bij de uitvoering van de
main-methode van de DemoEuro-klasse een foutieve muntcode ingeeft?
e. Waarom staat in het voorbeeld Klasse16 de uitdrukking mResultaten = new
double[3]; in de standaard constructor?
2. Bubble2
Vertrek van de bubblesort opgave van hoofdstuk 2. Maak nu gebruik van twee
afzonderlijke classes in twee afzonderlijke bestanden, een klasse Tabel voor de tabel en
een klasse TestTabel voor de main-methode. In de klasse Tabel moet je methoden
voorzien om getallen in te lezen, de getallen in de tabel van klein naar groot te sorteren en
een methode om de inhoud van de tabel op het scherm te tonen. Laat de gebruiker de
getallen manueel ingeven.
3. Palindroom2
Vertrek van de palindroomopgave van hoofdstuk 2. Maak nu gebruik van een afzonderlijke
klasse voor een methode met de naam isPalindroom. Indien je dit nog niet gedaan hebt
moet je nu ook palindroomzinnen herkennen (met andere woorden: negeer spaties en
leestekens in de te onderzoeken string). Gebruik voor elke klasse een afzonderlijk .javabestand.
4. Punt
Maak een klasse Punt met als variabelen mX en mY (de coördinaten van een punt in een
vlak). Voorzie een standaard constructor, een constructor met 2 parameters en een functie
om mX en mY in de vorm (x, y) op het scherm te tonen.
Schrijf daarna een testprogramma waarin je een tabel reserveert met de volgende xywaarden: 1,2 - 2,4 - 0,3 - 2,3 - 1,3. In de main-methode moet je ook de coördinaten van de
punten in originele volgorde op het scherm brengen.
5. Punt2
Vertrek van de klasse Punt van de vorige opgave. Maak een afzonderlijke klasse Punten
waarin je een tabel van Punt-objecten opneemt. Vul de tabel met dezelfde waarden.
Sorteer voor je de tabel afdrukt de punten in oplopende volgorde vo lgens de afstand tot de
80
Java
K. Behiels – J. Gils
oorsprong van het XY-assenstelsel. Deze afstand bereken je door met de standaardmethode
Math.sqrt() de vierkantswortel uit x² + y² te trekken
6. Punt3
Vertrek van de klasse Punt van de Punt-opgave. Maak een klasse Lijnstuk als
uitbreiding van de klasse Punt. Een lijnstuk is de verbinding tussen 2 punten. Zorg voor de
nodige constructors. Maak gebruik van een tabel van 4 lijnstukken met de volgende
coördinaten: van 1,1 naar 1,4 - van 2,1 naar 3,4 - van 2,3 naar 4,1 - van 1,4 naar 6,3. Geef
een ove rzicht van de lijnstukken met zowel hun coördinaten als de lengte van het lijnstuk.
Tracht zoveel mogelijk code te hergebruiken en gebruik ten minste één private methode.
De lengte van een lijnstuk bereken je door de vierkantswortel uit de som der kwadraten
van de verschillen van de beide x-waarden en de verschillen van de beide y-waarden te
trekken.
7. Tabel1
Schrijf in een afzonderlijk bestand de klasse Tabel zodanig dat het onderstaande
testprogramma zonder fouten wordt uitgevoerd.
public class TestTabel {
public static void main(String[] args) {
Tabel dummy = new Tabel();
dummy.toonTabel();
String[] stringTabel = {"een", "twee", "drie"};
Tabel demo = new Tabel(stringTabel);
demo.voegElementToe("vier");
System.out.print("tabel een ("
+ demo.aantalElementen() + " strings):
demo.toonTabel();
");
Tabel test = demo.kopieerTabel();
/* Tabel test = demo;
// <--- is foutief! */
test.wijzigElement(2, "vier");
// index 2 is het derde element!
System.out.println("\nNa wijzigen tabel twee:\n");
System.out.print("tabel een: ");
demo.toonTabel();
System.out.print("tabel twee: ");
test.toonTabel();
}
}
Verwachte uitvoer:
tabel een (4 strings):
een twee drie vier
Na wijzigen tabel1:
tabel een: een twee drie vier
tabel twee: een twee vier vier
K. Behiels - J. Gils
Java
81
8. Tabel2
Vervolledig de onderstaande klasse:
public class Tabel {
private int[] mTabel;
/** default constructor
*/
public Tabel() {
}
/** In de onderstaande constructor wordt een tabel
* voor 'aantal' int's aangemaakt.
*/
public Tabel(int aantal) {
}
/** In onderstaande constructor wordt de tabel mTabel
* gevuld met de waarden van de argument tabel.
*/
public Tabel(int[] tabel) {
}
/** In de methode sommeer worden de getallen van tabel a
* en tabel b element per element samengeteld,
* het resultaat komt in de 'this'-tabel.
*/
public void sommeer(Tabel a, Tabel b) {
}
/** In de methode concat worden de elementen van
* de tabelen a en b achter mekaar in de 'this'-tabel
* samengevoegd.
*/
public void concat(Tabel a, Tabel b) {
}
/** In de methode merge worden de elementen van de
* tabelen a en b in oplopende volgorde in de
* 'this'-tabel geplaatst.
*/
public void merge(Tabel a, Tabel b) {
}
/** In de methode toonGetallen wordt de inhoud van de
* tabel op het scherm gebracht.
*/
public void toonGetallen() {
}
}
82
Java
K. Behiels – J. Gils
Test de uitgewerkte klasse met het volgende testprogramma uit:
public class TestTabel {
public static void main(String[] args) {
int[] tab1 = {1, 4, 9, 16, 25, 36};
int[] tab2 = {2, 4, 6, 8, 10, 12, 14, 16};
Tabel v1 = new Tabel(tab1);
Tabel v2 = new Tabel(tab2);
Tabel res = new Tabel();
v1.toonGetallen();
v2.toonGetallen();
res.sommeer(v1, v2);
res.toonGetallen();
res.sommeer(v2, v1);
res.toonGetallen();
res.concat(v1, v2);
res.toonGetallen();
res.concat(v2, v1);
res.toonGetallen();
res.merge(v1, v2);
res.toonGetallen();
}
}
De verwachte uitvoer is:
1
2
3
3
1
2
1
4
4
8
8
4
4
2
9 16 25 36
6 8 10 12 14 16
15 24 35 48 14 16
15 24 35 48 14 16
9 16 25 36 2 4 6 8 10 12 14 16
6 8 10 12 14 16 1 4 9 16 25 36
4 4 6 8 9 10 12 14 16 16 25 36
K. Behiels - J. Gils
Java
83
Hoofdstuk 4
4.1
Overerving
Inleiding
In programmeertalen betekent overerving (inheritance) dat de eigenschappen van een
kindklasse een uitbreiding zijn van die van de ouderklasse. Met behulp van overerving leid
je van een bestaande klasse nieuwe klassen af. Hierdoor worden alle methoden en
variabelen van de superklasse automatisch door de subklasse overgenomen.
Wanneer leid je een klasse van een andere klasse af?
Volgens de theorie van objectoriëntatie is overerving alleen toelaatbaar als voldaan is aan
de volgende voorwaarden:
?
?
?
tussen de subklasse en de superklasse moet een is een soort van relatie bestaan;
de nieuwe klasse mag geen rol van de superklasse zijn;
de subklasse moet een uitbreiding van de superklasse zijn.
Voorbeelden van de is een soort van-relatie zijn Auto – Voertuig, Koelkast – Kast,
Voordeur – Deur. Bij Auto – Brandstoftank, Koelkast – Vriesvak, Deur – Slot is er sprake
van een heeft een-relatie. In die situatie neem je beter de tweede klasse als variabele in de
eerste klasse op (delegatie).
Van de klasse Persoon mag je geen klasse Student afleiden omdat objecten van de klasse
Student in de loop van de tijd objecten van bijvoorbeeld de klasse OudStudent of Docent
kunnen worden. De klasse Student is in dit verband een voorbeeld van een rol.
Met uitbreiding bedoelen we dat de afgeleide klasse iets extra aan de originele klasse moet
toevoegen, de subklasse moet extra attributen of methoden aan de superklasse toevoegen.
Waarom zou je overerving gebruiken?
Hiervoor kunnen verschillende redenen zijn:
?
?
?
?
?
84
je wilt de ouderklasse behouden omdat ze al in het programma of in andere
programma’s bestaat, door overerving toe te passen beperk je het schrijfwerk door
hergebruik van code;
de superklasse maakt deel uit van een klassenbibliotheek die door verschillende
programmeurs gebruikt wordt, in dit geval mag je de superklasse niet wijzigen;
de broncode van de superklasse is niet toegankelijk of niet beschikbaar, om overerving
te kunnen toepassen volstaat de gecompileerde .class-vorm van de superklasse;
de superklasse is een abstracte klasse, ze dient uitsluitend om een algemene
gegevensstructuur in de top van een klassenhiërarchie te definiëren, niet om er
rechtstreeks objecten van te maken;
je wilt gebruik maken van polymorfisme (late binding).
Java
K. Behiels – J. Gils
Download