3. Klassen en interfaces
3.1. Het object aan de hand van een voorbeeld
3.1.1. Algemeen
We gaan nu aan de hand van een voorbeeld in op objectgeoriënteerd programmeren. Een object is een entiteit die gegevens bevat die de toestand ervan bepalen (deze worden attributen of eigenschappen genoemd) en functies (deze worden methoden genoemd). Een object wordt aangemaakt volgens een model dat een klasse wordt genoemd:
public class C1{
type1 p1; // eigenschap p1
type2 p2; // eigenschap p2
…
type3 m3(…){ // methode m3
…
}
type4 m4(…){ // methode m4
…
}
…
}
Op basis van de vorige klasse C1 kunnen we talrijke objecten aanmaken: O1, O2, … Ze zullen allemaal de eigenschappen p1, p2, … en de methoden m3, m4, … hebben. Ze zullen verschillende waarden hebben voor hun eigenschappen pi en hebben dus elk een eigen toestand.
Als O1 een object is van het type C1, dan verwijst O1.p1 naar de eigenschap p1 van O1 en O1.m1 naar de methode m1 van O1.
Laten we een eerste objectmodel bekijken: de klasse personne.
3.1.2. Definitie van de klasse ‘persoon’
De definitie van de klasse personne luidt als volgt:
import java.io.*;
public class personne{
// attributen
private String prenom;
private String nom;
private int age;
// methode
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// methode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Dit is de definitie van een klasse, dus een gegevenstype. Wanneer we variabelen van dit type aanmaken, noemen we ze objecten. Een klasse is dus een mal op basis waarvan objecten worden geconstrueerd.
De leden of velden van een klasse kunnen gegevens of methoden (functies) zijn. Deze velden kunnen een van de volgende drie attributen hebben:
privé: Een privéveld (private) is alleen toegankelijk via de interne methoden van de klasse
public: Een openbaar veld is toegankelijk voor elke functie, ongeacht of deze al dan niet binnen de klasse is gedefinieerd
protégé: Een beschermd veld (protected) is uitsluitend toegankelijk via de interne methoden van de klasse of van een afgeleid object (zie later het concept van overerving).
Over het algemeen worden de gegevens van een klasse als privé gedeclareerd, terwijl de methoden als openbaar worden gedeclareerd. Dit betekent dat de gebruiker van een object (de programmeur)
a: geen directe toegang heeft tot de privégegevens van het object
b: gebruik kan maken van de openbare methoden van het object, en met name van die methoden die toegang geven tot de privégegevens ervan.
De syntaxis voor het declareren van een object is als volgt:
public class nomClasse{
private donnée ou méthode privée
public donnée ou méthode publique
protected donnée ou méthode protégée
}
Remarques
- De volgorde waarin de attributen private, protected en public worden gedeclareerd, is willekeurig.
3.1.3. De methode initialise
Laten we teruggaan naar onze klasse **person die** als volgt is gedeclareerd:
import java.io.*;
public class personne{
// attributen
private String prenom;
private String nom;
private int age;
// methode
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// methode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Wat is de functie van de methode initialise? Omdat achternaam, voornaam en leeftijd privégegevens zijn van de klasse 'persoon', zijn de instructies
zijn ongeldig. We moeten een object van het type personne initialiseren via een openbare methode. Dat is de taak van de methode initialise. We schrijven dan:
De notatie p1.initialise is toegestaan, omdat initialise openbaar toegankelijk is.
3.1.4. De operator new
De reeks instructies
is onjuist. De instructie
definieert p1 als een verwijzing naar een object van het type personne. Dit object bestaat nog niet en daarom is p1 niet geïnitialiseerd. Het is alsof men het volgende zou schrijven:
waarbij met het sleutelwoord null expliciet wordt aangegeven dat de variabele p1 nog niet naar een object verwijst.
Wanneer we vervolgens schrijven
dan roepen we de methode initialise aan van het object waarnaar p1 verwijst. Dit object bestaat echter nog niet en de compiler zal de fout melden. Om ervoor te zorgen dat p1 naar een object verwijst, moet je het volgende schrijven:
Hierdoor wordt een nog niet geïnitialiseerd object van het type personne aangemaakt: de attributen nom en prenom, die verwijzingen zijn naar objecten van het type String, krijgen de waarde null, en age de waarde 0. Er vindt dus een standaardinitialisatie plaats. Nu p1 naar een object verwijst, is de initialisatie-instructie voor dit object
geldig.
3.1.5. Het sleutelwoord this
Laten we de code van de methode initialise eens bekijken:
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
De instructie this.prenom=P betekent dat het attribuut prenom van het huidige object (this) de waarde P krijgt. Het sleutelwoord this verwijst naar het huidige object: het object waarin de uitgevoerde methode zich bevindt. Hoe weten we dat? Laten we eens kijken hoe het object waarnaar p1 verwijst, wordt geïnitialiseerd in het aanroepende programma:
Het is de methode initialise van het object p1 die wordt aangeroepen. Wanneer in deze methode naar het object this wordt verwezen, wordt in feite naar het object p1 verwezen. De methode initialise had ook als volgt geschreven kunnen worden:
public void initialise(String P, String N, int age){
prenom=P;
nom=N;
this.age=age;
}
Wanneer een methode van een object verwijst naar een attribuut A van dat object, is de notatie this.A impliciet. Deze moet expliciet worden gebruikt wanneer er een conflict is tussen identificatoren. Dit is het geval bij de instructie:
this.age=age;
waarbij age verwijst naar zowel een attribuut van het huidige object als naar de parameter age die door de methode wordt ontvangen. De dubbelzinnigheid moet dan worden opgeheven door het attribuut age aan te duiden als this.age.
3.1.6. Een testprogramma
Hier volgt een testprogramma:
public class test1{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
p1.identifie();
}
}
De klasse personne is gedefinieerd in het bronbestand personne.java en wordt gecompileerd:
E:\data\serge\JAVA\BASES\OBJETS\2>javac personne.java
E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002 09:21 473 personne.java
10/06/2002 09:22 835 personne.class
10/06/2002 09:23 165 test1.java
We doen hetzelfde voor het testprogramma:
E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
E:\data\serge\JAVA\BASES\OBJETS\2>dir
10/06/2002 09:21 473 personne.java
10/06/2002 09:22 835 personne.class
10/06/2002 09:23 165 test1.java
10/06/2002 09:25 418 test1.class
Het is opmerkelijk dat het programma test1.java de klasse personne niet importeert met de instructie:
Wanneer de compiler in de broncode een verwijzing naar een klasse tegenkomt die niet in datzelfde bronbestand is gedefinieerd, zoekt hij de klasse op verschillende plaatsen:
- in de pakketten die zijn geïmporteerd via de instructies import
- in de map van waaruit de compiler is gestart
In ons voorbeeld is de compiler gestart vanuit de map die het bestand personne.class bevat, wat verklaart waarom hij de definitie van de klasse personne heeft gevonden. Als je in dit geval een instructie import toevoegt, leidt dit tot een compilatiefout:
E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import personne;
^
1 error
Om deze fout te voorkomen en er tegelijkertijd aan te herinneren dat de klasse person moet worden geïmporteerd, schrijven we voortaan aan het begin van het programma:
We kunnen nu het bestand test1.class uitvoeren:
Het is mogelijk om meerdere klassen in één bronbestand samen te voegen. Laten we daarom de klassen personne en test1 samenvoegen in het bronbestand test2.java. De klasse test1 wordt hernoemd naar test2 om rekening te houden met de naamswijziging van het bronbestand:
// geïmporteerde pakketten
import java.io.*;
class personne{
// attributen
private String prenom; // voornaam van mijn persoon
private String nom; // zijn achternaam
private int age; // zijn/haar leeftijd
// methode
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}//initialiseert
// methode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}//identificeert
}//klasse
public class test2{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
p1.identifie();
}
}
Merk op dat de klasse personne het attribuut public niet meer heeft. In feite kan in een Java-bronbestand slechts één klasse het attribuut public hebben. Dit is de klasse met de functie main. Bovendien moet het bronbestand de naam van deze laatste dragen. Laten we het bestand test2.java compileren:
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36 633 test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>javac test2.java
E:\data\serge\JAVA\BASES\OBJETS\3>dir
10/06/2002 09:36 633 test2.java
10/06/2002 09:41 832 personne.class
10/06/2002 09:41 418 test2.class
Merk op dat er voor elke klasse in het bronbestand een .class-bestand is gegenereerd. Laten we nu het bestand test2.class uitvoeren:
Vervolgens zullen we beide methoden door elkaar gebruiken:
- klassen gebundeld in één bronbestand
- één klasse per bronbestand
3.1.7. Een andere methode initialiseert
Laten we nogmaals de klasse personne bekijken en de volgende methode eraan toevoegen:
public void initialise(personne P){
prenom=P.prenom;
nom=P.nom;
this.age=P.age;
}
We hebben nu twee methoden met de naam initialise: dat is toegestaan zolang ze verschillende parameters accepteren. Dat is hier het geval. De parameter is nu een P-verwijzing naar een persoon. De attributen van de persoon P worden vervolgens toegewezen aan het huidige object (this). Merk op dat de methode initialise directe toegang heeft tot de attributen van het object P, hoewel deze van het type private zijn. Dit geldt altijd: de methoden van een object O1 van een klasse C hebben altijd toegang tot de privé-attributen van andere objecten van dezelfde klasse C.
Hier volgt een test van de nieuwe klasse personne:
// persoon importeren;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new personne();
p1.initialise("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new personne();
p2.initialise(p1);
System.out.print("p2=");
p2.identifie();
}
}
en de resultaten:
3.1.8. Constructors van de klasse 'persoon'
Een constructor is een methode die de naam van de klasse draagt en die wordt aangeroepen bij het aanmaken van het object. Deze wordt doorgaans gebruikt om het object te initialiseren. Het is een methode die argumenten kan accepteren, maar geen resultaat retourneert. Het prototype of de definitie ervan wordt niet voorafgegaan door een type (zelfs niet void).
Als een klasse een constructor heeft die n argumenten argi accepteert, kunnen de declaratie en initialisatie van een object van deze klasse als volgt plaatsvinden:
classe objet =new classe(arg1,arg2, ... argn);
of
classe objet;
…
objet=new classe(arg1,arg2, ... argn);
Wanneer een klasse één of meerdere constructors heeft, moet er verplicht één van deze constructors worden gebruikt om een object van deze klasse aan te maken. Als een klasse C geen constructors heeft, beschikt deze over een standaardconstructor, namelijk de constructor zonder parameters: public C(). De attributen van het object worden dan geïnitialiseerd met standaardwaarden. Dit is wat er gebeurde toen we in de vorige programma's het volgende hadden geschreven:
Laten we twee constructors maken voor onze klasse personne:
public class personne{
// attributen
private String prenom;
private String nom;
private int age;
// constructors
public personne(String P, String N, int age){
initialise(P,N,age);
}
public personne(personne P){
initialise(P);
}
// methode
public void initialise(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public void initialise(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
// methode
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
}
Onze twee constructors volstaan met het aanroepen van de bijbehorende methoden initialise. Ter herinnering: wanneer in een constructor bijvoorbeeld de notatie initialise(P) voorkomt, vertaalt de compiler dit naar this.initialise(P). In de constructor wordt de methode initialise dus aangeroepen om te werken aan het object waarnaar wordt verwezen door this, dat wil zeggen het huidige object, het object dat op dat moment wordt geconstrueerd.
Hier volgt een testprogramma:
// import persoon;
import java.io.*;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
System.out.print("p1=");
p1.identifie();
personne p2=new personne(p1);
System.out.print("p2=");
p2.identifie();
}
}
en de verkregen resultaten:
3.1.9. De objectreferenties
We gebruiken steeds dezelfde klasse personne. Het testprogramma ziet er als volgt uit:
// import persoon;
import java.io.*;
public class test1{
public static void main(String arg[]){
// p1
personne p1=new personne("Jean","Dupont",30);
System.out.print("p1="); p1.identifie();
// p2 verwijst naar hetzelfde object als p1
personne p2=p1;
System.out.print("p2="); p2.identifie();
// p3 verwijst naar een object dat een kopie zal zijn van het object waarnaar p1 verwijst
personne p3=new personne(p1);
System.out.print("p3="); p3.identifie();
// we wijzigen de status van het object waarnaar p1 verwijst
p1.initialise("Micheline","Benoît",67);
System.out.print("p1="); p1.identifie();
// aangezien p2 = p1, moet de toestand van het object waarnaar p2 verwijst, zijn gewijzigd
System.out.print("p2="); p2.identifie();
// aangezien p3 niet naar hetzelfde object verwijst als p1, moet het object waarnaar p3 verwijst niet zijn veranderd
System.out.print("p3="); p3.identifie();
}
}
De verkregen resultaten zijn als volgt:
p1=Jean,Dupont,30
p2=Jean,Dupont,30
p3=Jean,Dupont,30
p1=Micheline,Benoît,67
p2=Micheline,Benoît,67
p3=Jean,Dupont,30
Wanneer de variabele p1 wordt gedeclareerd met
verwijst p1 naar het object personne("Jean","Dupont",30), maar is niet het object zelf. In C zouden we zeggen dat het een pointer is, c.a.d, het adres van het aangemaakte object. Als we vervolgens schrijven:
Het is niet het object personne("Jean","Dupont",30) dat wordt gewijzigd, maar de referentie p1 die een andere waarde krijgt. Het object persoon("Jean","Dupont",30) gaat "verloren" als er geen enkele andere variabele naar verwijst.
Wanneer men schrijft:
wordt de pointer p2 geïnitialiseerd: deze „wijst” naar hetzelfde object (verwijst naar hetzelfde object) als de pointer p1. Als we dus het object wijzigen waarnaar p1 „wijst” (of waarnaar wordt verwezen), wijzigen we ook het object waarnaar p2 verwijst.
Wanneer we schrijven:
wordt er een nieuw object aangemaakt, een kopie van het object waarnaar wordt verwezen door p1. Naar dit nieuwe object wordt verwezen door p3. Als het object waarnaar wordt verwezen door p1 wordt gewijzigd, heeft dit geen enkele invloed op het object waarnaar wordt verwezen door p3. Dit blijkt uit de verkregen resultaten.
3.1.10. Tijdelijke objecten
In een uitdrukking kan expliciet een beroep worden gedaan op de constructor van een object: dit object wordt aangemaakt, maar we hebben er geen toegang toe (om het bijvoorbeeld te wijzigen). Dit tijdelijke object wordt aangemaakt om de uitdrukking te kunnen evalueren en wordt daarna vrijgegeven. De geheugenruimte die het in beslag nam, wordt later automatisch vrijgemaakt door een programma dat "garbage collector" wordt genoemd en dat tot taak heeft de geheugenruimte vrij te maken die wordt ingenomen door objecten waarnaar niet langer wordt verwezen door gegevens in het programma.
Laten we het volgende voorbeeld eens bekijken:
// import person;
public class test1{
public static void main(String arg[]){
new personne(new personne("Jean","Dupont",30)).identifie();
}
}
en passen we de constructors van de klasse personne aan zodat ze een bericht weergeven:
// fabrikanten
public personne(String P, String N, int age){
System.out.println("Constructeur personne(String, String, int)");
initialise(P,N,age);
}
public personne(personne P){
System.out.println("Constructeur personne(personne)");
initialise(P);
}
We krijgen de volgende resultaten:
waarin de opeenvolgende opbouw van de twee tijdelijke objecten te zien is.
3.1.11. Methoden voor het lezen en schrijven van privé-attributen
We voegen aan de klasse personne de methoden toe die nodig zijn om de status van de attributen van de objecten te lezen of te wijzigen:
public class personne{
private String prenom;
private String nom;
private int age;
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
public void identifie(){
System.out.println(prenom+","+nom+","+age);
}
// toeleveranciers
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//bewerkers
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
We testen de nieuwe klasse met het volgende programma:
// persoon importeren;
public class test1{
public static void main(String[] arg){
personne P=new personne("Jean","Michelin",34);
System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
P.setAge(56);
System.out.println("P=("+P.getPrenom()+","+P.getNom()+","+P.getAge()+")");
}
}
en we krijgen de volgende resultaten:
3.1.12. De methoden en klasse-attributen
Stel dat we het aantal objecten personne willen tellen dat in een applicatie is aangemaakt. We kunnen zelf een teller bijhouden, maar dan lopen we het risico dat we de tijdelijke objecten vergeten die hier en daar worden aangemaakt. Het lijkt veiliger om in de constructors van de klasse personne een instructie op te nemen die een teller verhoogt. Het probleem is dat er een verwijzing naar deze teller moet worden doorgegeven, zodat de constructor deze kan verhogen: er moet een nieuwe parameter aan worden doorgegeven. Men kan de teller ook opnemen in de definitie van de klasse. Aangezien het een attribuut is van de klasse zelf en niet van een specifiek object van deze klasse, wordt het anders gedeclareerd met het sleutelwoord static:
Om ernaar te verwijzen, schrijven we personne.nbPersonnes om aan te geven dat het een attribuut is van de klasse personne zelf. Hier hebben we een privé-attribuut aangemaakt waartoe we buiten de klasse geen directe toegang hebben. We maken daarom een openbare methode om toegang te geven tot het klasseattribuut nbPersonnes. Om de waarde van nbPersonnes op te halen, heeft de methode geen specifiek object nodig: nbPersonnes is immers geen attribuut van een specifiek object, maar van de hele klasse. Daarom is er een klassemethode nodig die eveneens is gedeclareerd als static:
die van buitenaf wordt aangeroepen met de syntaxis personne.getNbPersonnes(). Hier volgt een voorbeeld.
De klasse personne ziet er als volgt uit:
public class personne{
// klasse-attribuut
private static long nbPersonnes=0;
// objectattributen
…
// constructors
public personne(String P, String N, int age){
initialise(P,N,age);
nbPersonnes++;
}
public personne(personne P){
initialise(P);
nbPersonnes++;
}
// methode
…
// klassenmethode
public static long getNbPersonnes(){
return nbPersonnes;
}
}// klasse
Met het volgende programma:
// import persoon;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
personne p2=new personne(p1);
new personne(p1);
System.out.println("Nombre de personnes créées : "+personne.getNbPersonnes());
}// main
}//test1
dit levert de volgende resultaten op:
3.1.13. Overdracht van een object aan een functie
We hebben al gezegd dat Java de werkelijke parameters van een functie per waarde doorgeeft: de waarden van de werkelijke parameters worden gekopieerd naar de formele parameters. Een functie kan de werkelijke parameters dus niet wijzigen.
In het geval van een object moet men zich niet laten misleiden door het systematische taalgebruik waarbij men spreekt van een ‘object’ in plaats van een ‘objectreferentie’. Een object wordt uitsluitend gemanipuleerd via een verwijzing (een pointer) ernaar. Wat dus aan een functie wordt doorgegeven, is niet het object zelf, maar een verwijzing naar dat object. Het is dus de waarde van de verwijzing en niet de waarde van het object zelf die in de formele parameter wordt gekopieerd: er wordt geen nieuw object aangemaakt.
Als een objectreferentie R1 aan een functie wordt doorgegeven, wordt deze gekopieerd naar de bijbehorende formele parameter R2. De verwijzingen R2 en R1 verwijzen dus naar hetzelfde object. Als de functie het object wijzigt waarnaar R2 verwijst, wijzigt zij uiteraard ook het object waarnaar R1 verwijst, aangezien het om hetzelfde object gaat.

Dit wordt geïllustreerd in het volgende voorbeeld:
// import persoon;
public class test1{
public static void main(String arg[]){
personne p1=new personne("Jean","Dupont",30);
System.out.print("Paramètre effectif avant modification : ");
p1.identifie();
modifie(p1);
System.out.print("Paramètre effectif après modification : ");
p1.identifie();
}// main
private static void modifie(personne P){
System.out.print("Paramètre formel avant modification : ");
P.identifie();
P.initialise("Sylvie","Vartan",52);
System.out.print("Paramètre formel après modification : ");
P.identifie();
}// wijzig
}// klasse
De methode modifie wordt aangeduid als static omdat het een klassemethode is: er hoeft geen object voor te worden geplaatst om deze aan te roepen. De verkregen resultaten zijn als volgt:
Constructeur personne(String, String, int)
Paramètre effectif avant modification : Jean,Dupont,30
Paramètre formel avant modification : Jean,Dupont,30
Paramètre formel après modification : Sylvie,Vartan,52
Paramètre effectif après modification : Sylvie,Vartan,52
We zien dat er slechts één object wordt aangemaakt: dat van de persoon p1 van de functie main, en dat het object inderdaad is gewijzigd door de functie modifie.
3.1.14. De uitvoerparameters van een functie in een object inkapselen
Omdat parameters als waarde worden doorgegeven, is het in Java niet mogelijk om een functie te schrijven met uitgangsparameters van het type int, bijvoorbeeld, omdat het niet mogelijk is om de referentie van een type int door te geven, aangezien dit geen object is. We kunnen dan een klasse maken die het type int inkapselen:
public class entieres{
private int valeur;
public entieres(int valeur){
this.valeur=valeur;
}
public void setValue(int valeur){
this.valeur=valeur;
}
public int getValue(){
return valeur;
}
}
De vorige klasse heeft een constructor waarmee een geheel getal kan worden geïnitialiseerd en twee methoden waarmee de waarde van dit geheel getal kan worden gelezen en gewijzigd. We testen deze klasse met het volgende programma:
// import integers;
public class test2{
public static void main(String[] arg){
entieres I=new entieres(12);
System.out.println("I="+I.getValue());
change(I);
System.out.println("I="+I.getValue());
}
private static void change(entieres entier){
entier.setValue(15);
}
}
en we krijgen de volgende resultaten:
3.1.15. Een tabel met personen
Een object is een gegeven zoals elk ander en daarom kunnen meerdere objecten in een array worden verzameld:
// import persoon;
public class test1{
public static void main(String arg[]){
personne[] amis=new personne[3];
System.out.println("----------------");
amis[0]=new personne("Jean","Dupont",30);
amis[1]=new personne("Sylvie","Vartan",52);
amis[2]=new personne("Neil","Armstrong",66);
int i;
for(i=0;i<amis.length;i++)
amis[i].identifie();
}
}
De instructie persoon[] vrienden = new persoon[3]; maakt een array aan met 3 elementen van het type personne. Deze 3 elementen worden hier geïnitialiseerd met de waarden null en c.a.d, die naar geen enkel object verwijzen. Ook hier wordt, in de volksmond, gesproken van een array van objecten, terwijl het slechts een array van verwijzingen naar objecten is. Het aanmaken van de objectenarray – een array die zelf een object is (aanwezigheid van new) – creëert dus op zichzelf geen enkel object van het type van de elementen ervan: dat moet daarna gebeuren.
Dit levert de volgende resultaten op:
----------------
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Constructeur personne(String, String, int)
Jean,Dupont,30
Sylvie,Vartan,52
Neil,Armstrong,66
3.2. Het voorbeeld als erfenis
3.2.1. Algemeen
Hier bespreken we het begrip overerving. Het doel van overerving is om een bestaande klasse te 'aanpassen' zodat deze aan onze behoeften voldoet. Stel dat we een klasse enseignant willen maken: een docent is een bijzonder persoon. Hij heeft eigenschappen die een ander niet heeft: het vak dat hij onderwijst bijvoorbeeld. Maar hij heeft ook de eigenschappen die iedereen heeft: voornaam, achternaam en leeftijd. Een leraar maakt dus volledig deel uit van de klasse personne, maar heeft extra eigenschappen. In plaats van een klasse enseignant helemaal vanaf nul te schrijven, geven we er de voorkeur aan om voort te bouwen op de bestaande klasse personne en deze aan te passen aan het specifieke karakter van leraren. Het concept van overerving maakt dit mogelijk.
Om aan te geven dat de klasse enseignant de eigenschappen van de klasse personne erft, schrijven we:
public class enseignant extends personne
personne wordt de bovenliggende (of moeder)klasse genoemd en enseignant de afgeleide (of dochter)klasse. Een object enseignant heeft alle eigenschappen van een object personne: het heeft dezelfde attributen en dezelfde methoden. Deze attributen en methoden van de bovenliggende klasse worden niet herhaald in de definitie van de afgeleide klasse: we vermelden alleen de attributen en methoden die door de afgeleide klasse zijn toegevoegd:
class enseignant extends personne{
// attributen
private int section;
// constructor
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
}
We gaan ervan uit dat de klasse personne als volgt is gedefinieerd:
public class personne{
private String prenom;
private String nom;
private int age;
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
public personne(personne P){
this.prenom=P.prenom;
this.nom=P.nom;
this.age=P.age;
}
public String identite(){
return "personne("+prenom+","+nom+","+age+")";
}
// accessors
public String getPrenom(){
return prenom;
}
public String getNom(){
return nom;
}
public int getAge(){
return age;
}
//modifiers
public void setPrenom(String P){
this.prenom=P;
}
public void setNom(String N){
this.nom=N;
}
public void setAge(int age){
this.age=age;
}
}
De methode identifie is licht gewijzigd om een tekenreeks te genereren die de persoon identificeert en heet nu identite. Hier voegt de klasse enseignant aan de methoden en attributen van de klasse personne toe:
- een attribuut section, dat het sectienummer aangeeft waartoe de docent behoort binnen het docentencorps (grofweg één sectie per vak)
- een nieuwe constructor waarmee alle attributen van een docent kunnen worden geïnitialiseerd
3.2.2. Aanmaken van een docentobject
De constructor van de klasse enseignant is als volgt:
// constructor
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
De instructie super(P,N,age) is een aanroep van de constructor van de bovenliggende klasse, in dit geval de klasse personne. We weten dat deze constructor de velden ‘voornaam’, ‘achternaam’ en ‘age’ initialiseert van het object personne dat zich binnen het object étudiant bevindt. Dit lijkt erg ingewikkeld en men zou misschien liever het volgende schrijven:
// constructor
public enseignant(String P, String N, int age,int section){
this.prenom=P;
this.nom=N
this.age=age
this.section=section;
}
Dat is onmogelijk. De klasse personne heeft haar drie velden prenom, nom en age als privé (private) gedeclareerd. Alleen objecten van dezelfde klasse hebben directe toegang tot deze velden. Alle andere objecten, inclusief afgeleide objecten zoals hier, moeten via openbare methoden werken om er toegang toe te krijgen. Dit zou anders zijn geweest als de klasse personne de drie velden als beschermd (protected) had gedeclareerd: dan zou zij afgeleide klassen toestaan om directe toegang te hebben tot de drie velden. In ons voorbeeld was het gebruik van de constructor van de bovenliggende klasse dus de juiste oplossing en dat is ook de gebruikelijke werkwijze: bij het aanmaken van een onderliggend object roepen we eerst de constructor van het bovenliggende object aan en vullen we vervolgens de initialisaties aan die specifiek zijn voor het onderliggende object (section in ons voorbeeld).
Laten we een eerste programma proberen:
// persoon importeren;
// import docent;
public class test1{
public static void main(String arg[]){
System.out.println(new enseignant("Jean","Dupont",30,27).identite());
}
}
Dit programma beperkt zich tot het aanmaken van een object enseignant (new) en het identificeren ervan. De klasse enseignant heeft geen methode identité, maar de bovenliggende klasse heeft er wel een, die bovendien openbaar is: door overerving wordt dit een openbare methode van de klasse enseignant.
De bronbestanden van de klassen worden in één map verzameld en vervolgens gecompileerd:
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00 765 personne.java
10/06/2002 10:00 212 enseignant.java
10/06/2002 10:01 192 test1.java
E:\data\serge\JAVA\BASES\OBJETS\4>javac *.java
E:\data\serge\JAVA\BASES\OBJETS\4>dir
10/06/2002 10:00 765 personne.java
10/06/2002 10:00 212 enseignant.java
10/06/2002 10:01 192 test1.java
10/06/2002 10:02 316 enseignant.class
10/06/2002 10:02 1 146 personne.class
10/06/2002 10:02 550 test1.class
Het bestand test1.class wordt uitgevoerd:
3.2.3. Een methode overschrijven
In het vorige voorbeeld hadden we de identiteit van het onderdeel personne van de docent, maar er ontbreekt bepaalde informatie die specifiek is voor de klasse enseignant (de sectie). We moeten dus een methode schrijven waarmee de docent kan worden geïdentificeerd:
class enseignant extends personne{
int section;
public enseignant(String P, String N, int age,int section){
super(P,N,age);
this.section=section;
}
public String identite(){
return "enseignant("+super.identite()+","+section+")";
}
}
De methode identite van de klasse enseignant is gebaseerd op de methode identite van de bovenliggende klasse (super.identite) om het onderdeel „personne” weer te geven en vult dit vervolgens aan met het veld section, dat specifiek is voor de klasse enseignant.
De klasse enseignant beschikt nu over twee methoden identite:
- de methode die is overgenomen van de bovenliggende klasse personne
- en een eigen methode
Als E een object van de klasse enseignant is, verwijst E.identite naar de methode identite van de klasse enseignant. Men zegt dat de methode identite van de bovenliggende klasse wordt „overschreven” door de methode identite van de onderliggende klasse. In het algemeen geldt: als O een object is en M een methode, dan zoekt het systeem, om de methode O.M uit te voeren, naar een methode M in de volgende volgorde:
- in de klasse van het object O
- in de bovenliggende klasse, indien deze bestaat
- in de bovenliggende klasse van de bovenliggende klasse, indien deze bestaat
- enzovoort…
Erfenis maakt het dus mogelijk om in de dochterklasse methoden met dezelfde naam als in de bovenliggende klasse te overschrijven. Hierdoor kan de dochterklasse aan haar eigen behoeften worden aangepast. In combinatie met polymorfisme, dat we straks zullen bespreken, is het overschrijven van methoden het belangrijkste voordeel van erfenis.
Laten we hetzelfde voorbeeld als eerder bekijken:
// import persoon;
// import docent;
public class test1{
public static void main(String arg[]){
System.out.println(new enseignant("Jean","Dupont",30,27).identite());
}
}
De verkregen resultaten zijn deze keer als volgt:
3.2.4. Polymorfisme
Laten we eens kijken naar een reeks klassen: C0 C1 C2 … Cn
waarbij Ci Cj aangeeft dat de klasse Cj is afgeleid van de klasse Ci. Dit houdt in dat de klasse Cj alle kenmerken van de klasse Ci heeft, plus nog enkele andere. Stel dat er objecten Oi van het type Ci zijn. Het is toegestaan om het volgende te schrijven:
Door overerving heeft de klasse Cj namelijk alle kenmerken van de klasse Ci plus nog andere. Een object Oj van het type Cj bevat dus een object van het type Ci. De bewerking
zorgt ervoor dat Oi een verwijzing is naar het object van het type Ci dat in het object Oj is opgenomen.
Het feit dateen variabele Oi van de klasse Ci in feite niet alleen naar een object van de klasse Ci kan verwijzen, maar naar elk object dat is afgeleid van de klasse Ci, wordt polymorfisme genoemd: het vermogen van een variabele om naar verschillende soorten objecten te verwijzen.
Laten we een voorbeeld nemen en de volgende functie bekijken, die onafhankelijk is van welke klasse dan ook:
De klasse Object is de „moeder“ van alle Java-klassen. Dus wanneer we schrijven:
schrijft men impliciet:
Elk Java-object bevat dus een onderdeel van het type Object. We kunnen dus schrijven:
De formele parameter van het type Object van de functie affiche krijgt een waarde van het type enseignant. Aangezien enseignant afgeleid is van *Object*, is dit toegestaan.
3.2.5. Overbelasting en polymorfisme
Laten we onze functie affiche aanvullen:
De methode obj.toString() retourneert een tekenreeks die het object obj identificeert in de vorm nom_de_la_classe@adresse_de_l'object. Wat gebeurt er in het geval van ons vorige voorbeeld:
Het systeem moet de instructie System.out.println(e.toString()) uitvoeren, waarbij e een lerarobject is. Het zoekt naar een methode toString in de klassenhiërarchie die leidt naar de klasse enseignant, te beginnen bij de laatste:
- in de klasse enseignant vindt hij geen methode toString()
- in de bovenliggende klasse personne vindt hij geen methode toString()
- in de bovenliggende klasse Object vindt hij de methode toString() en voert deze uit
Dit wordt geïllustreerd door het volgende programma:
// persoon importeren;
// leraar importeren;
public class test1{
public static void main(String arg[]){
enseignant e=new enseignant("Lucile","Dumas",56,61);
affiche(e);
personne p=new personne("Jean","Dupont",30);
affiche(p);
}
public static void affiche(Object obj){
System.out.println(obj.toString());
}
}
De verkregen resultaten zijn als volgt:
Dat wil zeggen het object nom_de_la_classe@adresse_de_l. Aangezien dit niet erg duidelijk is, is men geneigd om een methode toString te definiëren voor de klassen personne en etudiant, die de methode toString van de bovenliggende klasse Object zou overschrijven. In plaats van methoden te schrijven die sterk lijken op de reeds bestaande methoden identite in de klassen personne en enseignant, beperken we ons ertoe om deze methoden identite te hernoemen naar toString:
public class personne{
...
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
...
}
class enseignant extends personne{
int section;
…
public String toString(){
return "enseignant("+super.toString()+","+section+")";
}
}
Met hetzelfde testprogramma als eerder zijn de volgende resultaten verkregen:
3.3. Interne klassen
Een klasse kan de definitie van een andere klasse bevatten. Laten we het volgende voorbeeld eens bekijken:
// geïmporteerde klassen
import java.io.*;
public class test1{
// interne klasse
private class article{
// de structuur wordt gedefinieerd
private String code;
private String nom;
private double prix;
private int stockActuel;
private int stockMinimum;
// constructor
public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
// initialisatie van de attributen
this.code=code;
this.nom=nom;
this.prix=prix;
this.stockActuel=stockActuel;
this.stockMinimum=stockMinimum;
}//constructor
//toString
public String toString(){
return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
}//toString
}//artikelklasse
// lokale gegevens
private article art=null;
// fabrikant
public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
// attribuutdefinitie
art=new article(code, nom, prix, stockActuel,stockMinimum);
}//test1
// accessor
public article getArticle(){
return art;
}//getArticle
public static void main(String arg[]){
// aanmaken van een instantie test1
test1 t1=new test1("a100","velo",1000,10,5);
// weergave test1.art
System.out.println("art="+t1.getArticle());
}//hoofdprogramma
}// einde class
De klasse test1 bevat de definitie van een andere klasse, namelijk de klasse article. Men zegt dat article een interne klasse is van de klasse test1. Dit kan handig zijn wanneer de interne klasse alleen nut heeft binnen de klasse die deze bevat. Bij het compileren van de bovenstaande broncode test1.java worden twee .class-bestanden gegenereerd:
E:\data\serge\JAVA\classes\interne>dir
05/06/2002 17:26 1 362 test1.java
05/06/2002 17:26 941 test1$article.class
05/06/2002 17:26 1 020 test1.class
Er is een bestand test1$article.class gegenereerd voor de klasse article, die een onderdeel is van de klasse test1. Als we het bovenstaande programma uitvoeren, krijgen we de volgende resultaten:
3.4. Interfaces
Een interface is een verzameling prototypes van methoden of eigenschappen die samen een contract vormen. Een klasse die besluit een interface te implementeren, verbindt zich ertoe een implementatie te leveren van alle methoden die in de interface zijn gedefinieerd. De compiler controleert deze implementatie.
Hier volgt bijvoorbeeld de definitie van de interface java.util.Enumeration:
Methodenoverzicht | ||
boolean | hasMoreElements() Controleert of deze opsomming meer elementen bevat. | |
Object | nextElement() Geeft het volgende element van deze opsomming terug als dit opsommingsobject nog ten minste één element te bieden heeft. | |
Elke klasse die deze interface implementeert, wordt gedeclareerd als
De methoden hasMoreElements() en nextElement() moeten in klasse C worden gedefinieerd.
Laten we eens kijken naar de volgende code, waarin een klasse élève wordt gedefinieerd die de naam van een leerling en zijn cijfer voor een bepaald vak vastlegt:
// een leerlingklasse
public class élève{
// openbare attributen
public String nom;
public double note;
// constructor
public élève(String NOM, double NOTE){
nom=NOM;
note=NOTE;
}//constructor
}//leerling
We definiëren een klasse notes waarin de cijfers van alle leerlingen voor een bepaald vak worden verzameld:
// geïmporteerde klassen
// import leerling
// cijferklasse
public class notes{
// attributen
protected String matière;
protected élève[] élèves;
// constructor
public notes (String MATIERE, élève[] ELEVES){
// opslag leerlingen & vak
matière=MATIERE;
élèves=ELEVES;
}//cijfers
// toString
public String toString(){
String valeur="matière="+matière +", notes=(";
int i;
// alle cijfers worden samengevoegd
for (i=0;i<élèves.length-1;i++){
valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
};
//laatste cijfer
if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
valeur+=")";
// einde
return valeur;
}//toString
}//klasse
De attributen matière en élèves worden gedeclareerd als protected om toegankelijk te zijn vanuit een afgeleide klasse. We besluiten de klasse notes af te leiden naar een klasse notesStats die twee extra attributen zou hebben, namelijk het gemiddelde en de standaardafwijking van de cijfers:
public class notesStats extends notes implements Istats {
// attributen
private double _moyenne;
private double _écartType;
De klasse notesStats is afgeleid van de klasse notes en implementeert de volgende interface Istats:
// een interface
public interface Istats{
double moyenne();
double écartType();
}//
Dit betekent dat de klasse notesStats twee methoden moet hebben met de namen moyenne en écartType, met de handtekening die is aangegeven in de interface Istats. De klasse notesStats ziet er als volgt uit:
// geïmporteerde klassen
// import notes;
// import Istats;
// import leerling;
public class notesStats extends notes implements Istats {
// attributen
private double _moyenne;
private double _écartType;
// constructor
public notesStats (String MATIERE, élève[] ELEVES){
// aanmaken van de bovenliggende klasse
super(MATIERE,ELEVES);
// berekening van het gemiddelde van de cijfers
double somme=0;
for (int i=0;i<élèves.length;i++){
somme+=élèves[i].note;
}
if(élèves.length!=0) _moyenne=somme/élèves.length;
else _moyenne=-1;
// standaardafwijking
double carrés=0;
for (int i=0;i<élèves.length;i++){
carrés+=Math.pow((élèves[i].note-_moyenne),2);
}//for
if(élèves.length!=0) _écartType=Math.sqrt(carrés/élèves.length);
else _écartType=-1;
}//constructor
// ToString
public String toString(){
return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
}//ToString
// methoden van de Istats-interface
public double moyenne(){
// berekent het gemiddelde van de scores
return _moyenne;
}//gemiddelde
public double écartType(){
// geeft de standaardafwijking weer
return _écartType;
}//écartType
}//-klasse
Het gemiddelde _moyenne en de standaardafwijking _ecartType worden berekend zodra het object wordt aangemaakt. Daarom hoeven de methoden moyenne en écartType alleen de waarde van de attributen _moyenne en _ecartType terug te geven. Beide methoden geven -1 terug als de tabel met leerlingen leeg is.
De volgende testklasse:
// geïmporteerde klassen
// leerling importeren;
// Istats importeren;
// import cijfers;
// import notesStats;
// testklas
public class test{
public static void main(String[] args){
// enkele leerlingen & cijfers
élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
// die we opslaan in een notenobject
notes anglais=new notes("anglais",ELEVES);
// en die worden weergegeven
System.out.println(""+anglais);
// idem met gemiddelde en standaardafwijking
anglais=new notesStats("anglais",ELEVES);
System.out.println(""+anglais);
}//hoofd
}//klas
levert de volgende resultaten op:
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0])
matière=anglais, notes=([paul,14.0],[nicole,16.0],[jacques,18.0]),moyenne=16.0,écart-type=1.632993161855452
De verschillende klassen in dit voorbeeld zijn allemaal ondergebracht in een apart bronbestand:
E:\data\serge\JAVA\interfaces\notes>dir
06/06/2002 14:06 707 notes.java
06/06/2002 14:06 878 notes.class
06/06/2002 14:07 1 160 notesStats.java
06/06/2002 14:02 101 Istats.java
06/06/2002 14:02 138 Istats.class
06/06/2002 14:05 247 élève.java
06/06/2002 14:05 309 élève.class
06/06/2002 14:07 1 103 notesStats.class
06/06/2002 14:10 597 test.java
06/06/2002 14:10 931 test.class
De klasse notesStats had de methoden moyenne en écartType heel goed voor zichzelf kunnen implementeren zonder aan te geven dat ze de interface Istats implementeerde. Wat is dan het nut van interfaces? Het is het volgende: een functie kan als parameter een waarde van het type van een interface I accepteren. Elk object van een klasse C dat de interface I implementeert, kan dan als parameter van deze functie dienen. Laten we de volgende interface eens bekijken:
// een interface Ivoorbeeld
public interface Iexemple{
int ajouter(int i,int j);
int soustraire(int i,int j);
}//interface
De interface Iexemple definieert twee methoden: ajouter en soustraire. De volgende klassen, classe1 en classe2, implementeren deze interface.
// geïmporteerde klassen
// import Ivoorbeeld;
public class classe1 implements Iexemple{
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
}//klasse
// geïmporteerde klassen
// import Ivoorbeeld;
public class classe2 implements Iexemple{
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
}//klasse
Om het voorbeeld eenvoudig te houden, doen de klassen niets anders dan de interface Iexemple implementeren. Laten we nu eens kijken naar het volgende voorbeeld:
// geïmporteerde klassen
// import klasse1;
// import klasse2;
// testklasse
public class test{
// een statische functie
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//berekenen
// de functie main
public static void main(String[] arg){
// twee objecten van klasse1 en klasse2 aanmaken
classe1 c1=new classe1();
classe2 c2=new classe2();
// de statische functie berekenen aanroepen
calculer(4,3,c1);
calculer(14,13,c2);
}//main
}//testklasse
De statische functie calculer accepteert als parameter een element van het type Iexemple. Deze functie kan voor deze parameter dus zowel een object van het type classe1 als van het type classe2 ontvangen. Dit gebeurt in de functie main met de volgende resultaten:
We zien dus dat we hier te maken hebben met een eigenschap die lijkt op het polymorfisme dat we bij klassen hebben gezien. Als een reeks klassen Ci die niet via overerving met elkaar verbonden zijn (waardoor we geen gebruik kunnen maken van het polymorfisme van overerving) een reeks methoden met dezelfde signatuur bevatten, kan het interessant zijn om deze methoden te bundelen in een interface I waarvan alle betrokken klassen zouden erven. Instanties van deze klassen Ci kunnen dan worden gebruikt als parameters van functies die een parameter van het type I toelaten, c.a.d. Dit zijn functies die uitsluitend gebruikmaken van de methoden van de objecten Ci die in de interface I zijn gedefinieerd, en niet van de specifieke attributen en methoden van de verschillende klassen Ci.
In het vorige voorbeeld was elke klasse of interface het onderwerp van een afzonderlijk bronbestand:
E:\data\serge\JAVA\interfaces\opérations>dir
06/06/2002 14:33 128 Iexemple.java
06/06/2002 14:34 218 classe1.java
06/06/2002 14:32 220 classe2.java
06/06/2002 14:33 144 Iexemple.class
06/06/2002 14:34 325 classe1.class
06/06/2002 14:34 326 classe2.class
06/06/2002 14:36 583 test.java
06/06/2002 14:36 628 test.class
Tot slot merken we op dat interfaces meerdere keren kunnen worden geërfd, c.a.d. Dit kan als volgt worden geschreven
waarbij de ij interfaces zijn.
3.5. Anonieme klassen
In het vorige voorbeeld hadden de klassen classe1 en classe2 niet expliciet gedefinieerd hoeven te worden. Laten we eens kijken naar het volgende programma, dat in wezen hetzelfde doet als het vorige, maar zonder de expliciete definitie van de klassen classe1 en classe2:
// geïmporteerde klassen
// import Ivoorbeeld;
// testklasse
public class test2{
// een interne klasse
private static class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//definitie klasse3
// een statische functie
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//berekenen
// de functie main
public static void main(String[] arg){
// aanmaken van twee objecten die de interface Iexemple implementeren
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
};//definitie i1
Iexemple i2=new Iexemple(){
public int ajouter(int a, int b){
return a+b+100;
}
public int soustraire(int a, int b){
return a-b+200;
}
};//definitie i2
// een ander Iexemple-object
Iexemple i3=new classe3();
// aanroepen van de statische functie 'calculer'
calculer(4,3,i1);
calculer(14,13,i2);
calculer(24,23,i3);
}//main
}//testklasse
Het bijzondere zit in de code:
// aanmaken van twee objecten die de interface Ivoorbeeld implementeren
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
return a+b+10;
}
public int soustraire(int a, int b){
return a-b+20;
}
};//definitie i1
We maken een object i1 aan, dat als enige taak heeft de interface Iexemple te implementeren. Dit object is van het type Iexemple. We kunnen dus objecten van het type interface aanmaken. Heel veel methoden van Java-klassen retourneren objecten van het type interface c.a.d: objecten die als enige taak hebben de methoden van een interface te implementeren. Om het object i1 aan te maken, zou men in de verleiding kunnen komen om het volgende te schrijven:
Iexemple i1=new Iexemple()
Maar een interface kan niet worden geïnstantieerd. Alleen een klasse die deze interface implementeert, kan worden geïnstantieerd. Hier definiëren we zo’n klasse ‘on-the-fly’ in de definitie van het object i1 zelf:
Iexemple i1=new Iexemple(){
public int ajouter(int a, int b){
// definitie van toevoegen
}
public int soustraire(int a, int b){
// definitie van 'aftrekken'
}
};//definitie i1
De betekenis van een dergelijke instructie is vergelijkbaar met de reeks:
public class test2{
................
// een interne klasse
private static class classe1 implements Iexemple{
public int ajouter(int a, int b){
// definitie van 'toevoegen'
}
public int soustraire(int a, int b){
// definitie van aftrekken
}
};//definitie klasse1
.................
public static void main(String[] arg){
...........
Iexemple i1=new classe1();
}//main
}//klasse
In het bovenstaande voorbeeld wordt inderdaad een klasse geïnstantieerd en geen interface. Een klasse die 'on-the-fly' wordt gedefinieerd, wordt een anonieme klasse genoemd. Dit is een methode die vaak wordt gebruikt om objecten te instantiëren die als enige taak hebben een interface te implementeren.
De uitvoering van het vorige programma levert de volgende resultaten op:
In het vorige voorbeeld werden anonieme klassen gebruikt om een interface te implementeren. Deze kunnen ook worden gebruikt om klassen af te leiden die geen constructors met parameters hebben. Laten we het volgende voorbeeld eens bekijken:
// geïmporteerde klassen
// import Ivoorbeeld;
class classe3 implements Iexemple{
public int ajouter(int a, int b){
return a+b+1000;
}
public int soustraire(int a, int b){
return a-b+2000;
}
};//definitie klasse3
public class test4{
// een statische functie
private static void calculer(int i, int j, Iexemple inter){
System.out.println(inter.ajouter(i,j));
System.out.println(inter.soustraire(i,j));
}//berekenen
// methode main
public static void main(String args[]){
// definitie van een anonieme klasse die afgeleid is van klasse3
// om aftrekken opnieuw te definiëren
classe3 i1=new classe3(){
public int ajouter(int a, int b){
return a+b+10000;
}//aftrekken
};//i1
// aanroepen van de statische functie berekenen
calculer(4,3,i1);
}//main
}//klasse
Hier vinden we een klasse classe3 die de interface Iexemple implementeert. In de functie main definiëren we een variabele i1 met als type een klasse die is afgeleid van classe3. Deze afgeleide klasse wordt ‘on-the-fly’ gedefinieerd in een anonieme klasse en herdefinieert de methode ajouter van de klasse classe3. De syntaxis is identiek aan die van de anonieme klasse die een interface implementeert. Alleen hier detecteert de compiler dat classe3 geen interface is, maar een klasse. Voor de compiler gaat het dan om een klasse-afleiding. Alle methoden die hij in de body van de anonieme klasse aantreft, zullen de methoden met dezelfde naam in de basisklasse vervangen.
De uitvoering van het vorige programma levert de volgende resultaten op:
3.6. De pakketten
3.6.1. Klassen aanmaken in een pakket
Om een regel op het scherm weer te geven, gebruiken we de instructie
Als we de definitie van de klasse System bekijken, zien we dat deze in feite java.lang.System heet:

Laten we dit aan de hand van een voorbeeld bekijken:
public class test1{
public static void main(String[] args){
java.lang.System.out.println("Coucou");
}//main
}//klasse
Laten we dit programma compileren en uitvoeren:
E:\data\serge\JAVA\classes\paquetages>javac test1.java
E:\data\serge\JAVA\classes\paquetages>dir
06/06/2002 15:40 127 test1.java
06/06/2002 15:40 410 test1.class
E:\data\serge\JAVA\classes\paquetages>java test1
Coucou
Waarom kunnen we dan schrijven
System.out.println("Coucou");
in plaats van
java.lang.System.out.println("Coucou");
Omdat er voor elk Java-programma impliciet een systematische import plaatsvindt van het "pakket" java.lang. Het is dus alsof aan het begin van elk programma de instructie staat:
Wat betekent deze instructie? Ze geeft toegang tot alle klassen van het pakket java.lang. De compiler zal daar het bestand System.class vinden, waarin de klasse System wordt gedefinieerd. We weten nog niet waar de compiler het pakket java.lang zal vinden, noch hoe een pakket eruitziet. Daar komen we later op terug. Om een klasse in een pakket aan te maken, schrijf je:
Laten we voor het voorbeeld onze eerder besproken klasse personne in een pakket aanmaken. We kiezen istia.st als naam voor het pakket. De klasse personne wordt dan:
// naam van het pakket waarin de klasse 'persoon' zal worden aangemaakt
package istia.st;
// klasse persoon
public class personne{
// achternaam, voornaam, leeftijd
private String prenom;
private String nom;
private int age;
// constructor 1
public personne(String P, String N, int age){
this.prenom=P;
this.nom=N;
this.age=age;
}
// toString
public String toString(){
return "personne("+prenom+","+nom+","+age+")";
}
}//klasse
Deze klasse wordt gecompileerd en vervolgens in de map istia\st van de huidige map geplaatst. Waarom istia\st? Omdat het pakket istia.st heet.
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:04 <DIR> istia
E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002 16:04 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002 16:28 675 personne.class
Laten we nu de klasse personne gebruiken in een eerste testklasse:
public class test{
public static void main(String[] args){
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
System.out.println("p1="+p1);
}//hand
}//testklasse
Merk op dat de klasse personne nu wordt voorafgegaan door de naam van het bijbehorende pakket: istia.st. Waar zal de compiler de klasse istia.st.personne vinden? De compiler zoekt de klassen die hij nodig heeft in een vooraf gedefinieerde lijst met mappen en in een boomstructuur die begint bij de huidige map. In dit geval zal hij de klasse istia.st.personne zoeken in het bestand istia\st\personne.class. Daarom hebben we het bestand personne.class in de map istia\st geplaatst. Laten we het testprogramma compileren en vervolgens uitvoeren:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
E:\data\serge\JAVA\classes\paquetages\personne>java test
p1=personne(Jean,Dupont,20)
Om te voorkomen dat je
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
De klasse istia.st.personne kan worden geïmporteerd met een clausule import:
import istia.st.personne;
We kunnen dan schrijven
personne p1=new personne("Jean","Dupont",20);
en de compiler zal dit vertalen naar
istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);
Het testprogramma ziet er dan als volgt uit:
// geïmporteerde naamruimten
import istia.st.personne;
public class test2{
public static void main(String[] args){
personne p1=new personne("Jean","Dupont",20);
System.out.println("p1="+p1);
}//hoofdprogramma
}//klasse test2
Laten we dit nieuwe programma compileren en uitvoeren:
E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 16:50 740 test2.class
E:\data\serge\JAVA\classes\paquetages\personne>java test2
p1=personne(Jean,Dupont,20)
We hebben het pakket istia.st in de huidige map geplaatst. Dit is niet verplicht. Laten we het in een map met de naam mesClasses plaatsen, nog steeds in de huidige map. Ter herinnering: de klassen van het pakket istia.st staan in een map met de naam istia\st. De mapstructuur van de huidige map is als volgt:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 16:06 246 test.java
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 16:50 740 test2.class
06/06/2002 16:21 <DIR> mesClasses
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses
06/06/2002 16:22 <DIR> istia
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia
06/06/2002 16:22 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir mesClasses\istia\st
06/06/2002 16:01 1 153 personne.class
Laten we nu het programma test2.java opnieuw compileren:
E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
test2.java:2: package istia.st does not exist
import istia.st.personne;
De compiler kan het pakket istia.st niet meer vinden sinds we het hebben verplaatst. Merk op dat de compiler ernaar zoekt vanwege de instructie import. Standaard zoekt hij het vanuit de huidige map in een map met de naam istia\st, die niet meer bestaat. Laten we de opties van de compiler eens bekijken:
E:\data\serge\JAVA\classes\paquetages\personne>javac
Usage: javac <options> <source files>
where possible options include:
-g Generate all debugging info
-g:none Generate no debugging info
-g:{lines,vars,source} Generate only some debugging info
-O Optimize; may hinder debugging or enlarge class file
-nowarn Generate no warnings
-verbose Output messages about what the compiler is doing
-deprecation Output source locations where deprecated APIs are used
-classpath <path> Specify where to find user class files
-sourcepath <path> Specify where to find input source files
-bootclasspath <path> Override location of bootstrap class files
-extdirs <dirs> Override location of installed extensions
-d <directory> Specify where to place generated class files
-encoding <encoding> Specify character encoding used by source files
-source <release> Provide source compatibility with specified release
-target <release> Generate class files for specific VM version
-help Print a synopsis of standard options
Hier kan de optie -classpath van pas komen. Hiermee kunnen we de compiler aangeven waar hij zijn klassen en pakketten moet zoeken. Laten we het eens proberen. Laten we compileren en de compiler vertellen dat het pakket istia.st zich nu in de map mesClasses bevindt:
E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath mesClasses test2.java
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:47 236 test2.java
06/06/2002 17:03 740 test2.class
06/06/2002 16:21 <DIR> mesClasses
Het compileren verloopt deze keer zonder problemen. Laten we het programma test2.class uitvoeren:
E:\data\serge\JAVA\classes\paquetages\personne>java test2
Exception in thread "main" java.lang.NoClassDefFoundError: istia/st/personne
at test2.main(test2.java:6)
Nu is het de beurt aan de Java-virtuele machine om de klasse istia/st/personne niet te vinden. Ze zoekt deze in de huidige map, terwijl deze zich nu in de map mesClasses bevindt. Laten we eens kijken naar de opties van de Java-virtuele machine:
E:\data\serge\JAVA\classes\paquetages\personne>java
Usage: java [-options] class [args...]
(to execute a class)
or java -jar [-options] jarfile [args...]
(to execute a jar file)
where options include:
-client to select the "client" VM
-server to select the "server" VM
-hotspot is a synonym for the "client" VM [deprecated]
The default VM is client.
-cp -classpath <directories and zip/jar files separated by ;>
set search path for application classes and resources
-D<name>=<value>
set a system property
-verbose[:class|gc|jni]
enable verbose output
-version print product version and exit
-showversion print product version and continue
-? -help print this help message
-X print help on non-standard options
-ea[:<packagename>...|:<classname>]
-enableassertions[:<packagename>...|:<classname>]
enable assertions
-da[:<packagename>...|:<classname>]
-disableassertions[:<packagename>...|:<classname>]
disable assertions
-esa | -enablesystemassertions
enable system assertions
-dsa | -disablesystemassertions
disable system assertions
We zien dat JVM net als de compiler ook een optie classpath heeft. Laten we die gebruiken om aan te geven waar het pakket istia.st zich bevindt:
E:\data\serge\JAVA\classes\paquetages\personne>java.bat -classpath mesClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2
We zijn nog niet veel verder gekomen. Nu wordt de klasse test2 zelf niet gevonden. De reden hiervoor is dat, bij afwezigheid van het trefwoord classpath, de huidige map systematisch wordt doorzocht bij het zoeken naar klassen, maar niet wanneer het trefwoord wel aanwezig is. Daardoor wordt de klasse test2.class, die zich in de huidige map bevindt, niet gevonden. De oplossing? Voeg de huidige map toe aan classpath. De huidige map wordt aangeduid met het symbool .
E:\data\serge\JAVA\classes\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(Jean,Dupont,20)
Waarom al deze complicaties? Het doel van de pakketten is om naamconflicten tussen klassen te voorkomen. Laten we eens kijken naar twee bedrijven, E1 en E2, die respectievelijk geklasseerde klassen distribueren in de pakketten com.e1 en com.e2. Stel dat een klant C deze twee sets klassen koopt, waarin beide bedrijven een klasse personne hebben gedefinieerd. Klant C zal verwijzen naar de klasse personne van hetbedrijf E1 met com.e1.personne en die van het bedrijf E2 met com.e2.personne, waardoor een naamconflict wordt voorkomen.
3.6.2. Zoeken naar pakketten
Wanneer we in een programma schrijven
om toegang te krijgen tot alle klassen van het pakket java.util, waar wordt dit dan gevonden? We hebben gezegd dat pakketten standaard worden gezocht in de huidige map of in de lijst met mappen die zijn opgegeven in de optie classpath van de compiler of de JVM, indien deze optie aanwezig is. Ze worden ook gezocht in de mappen lib van de installatiemap van de JDK. Laten we deze map eens bekijken:

In dit voorbeeld worden de mappen jdk14\lib en jdk14\jre\lib doorzocht op .class-bestanden, of op .jar- of .zip-bestanden die archieven van klassen bevatten. Laten we bijvoorbeeld zoeken naar de bestanden in .jar die zich in de bovenliggende map jdk14 bevinden:

Er zijn er tientallen. Een bestand .jar kan worden geopend met het hulpprogramma winzip. Laten we het bovenstaande bestand rt.jar openen (rt=RunTime). Daarin bevinden zich enkele honderden .class-bestanden, waaronder die welke behoren tot het pakket java.util:

Een eenvoudige manier om de pakketten te beheren is ze vervolgens in de map <jdk>\jre\lib te plaatsen, waarbij <jdk> de installatiemap van JDK is. Over het algemeen bevat een pakket meerdere klassen en is het handig om deze samen te voegen in één enkel .jar-bestand (JAR = Java ARchive-bestand). Het uitvoerbare bestand jar.exe bevindt zich in de map <jdk>\bin:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\bin\jar.exe"
07/02/2002 12:52 28 752 jar.exe
Hulp bij het gebruik van het programma jar kunt u krijgen door het zonder parameters op te roepen:
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar.exe"
Syntaxe : jar {ctxu}[vfm0M] [fichier-jar] [fichier-manifest] [rÚp -C] fichiers ...
Options :
-c crÚer un nouveau fichier d''archives
-t gÚnÚrer la table des matiÞres du fichier d''archives
-x extraire les fichiers nommÚs (ou tous les fichiers) du fichier d''archives
-u mettre Ó jour le fichier d''archives existant
-v gÚnÚrer des informations verbeuses sur la sortie standard
-f spÚcifier le nom du fichier d''archives
-m inclure les informations manifest provenant du fichier manifest spÚcifiÚ
-0 stocker seulement ; ne pas utiliser la compression ZIP
-M ne pas crÚer de fichier manifest pour les entrÚes
-i gÚnÚrer l''index pour les fichiers jar spÚcifiÚs
-C passer au rÚpertoire spÚcifiÚ et inclure le fichier suivant
Si un rÚpertoire est spÚcifiÚ, il est traitÚ rÚcursivement.
Les noms des fichiers manifest et d''archives doivent Ûtre spÚcifiÚs
dans l''ordre des indicateurs ''m'' et ''f''.
Exemple 1 : pour archiver deux fichiers de classe dans le fichier d''archives classes.jar :
jar cvf classes.jar Foo.class Bar.class
Exemple 2 : utilisez le fichier manifest existant ''monmanifest'' pour archiver tous les fichiers du
rÚpertoire foo/ dans ''classes.jar'':
jar cvfm classes.jar monmanifest -C foo/ .
Laten we teruggaan naar de klasse personne.class die eerder is aangemaakt in een pakket istia.st:
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 17:36 195 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 18:15 740 test2.class
E:\data\serge\JAVA\classes\paquetages\personne>dir istia
06/06/2002 16:04 <DIR> st
E:\data\serge\JAVA\classes\paquetages\personne>dir istia\st
06/06/2002 16:28 675 personne.class
Laten we een bestand istia.st.jar aanmaken waarin alle klassen van het pakket istia.st worden gearchiveerd, dus alle klassen van de bovenstaande boomstructuur istia\st:
E:\data\serge\JAVA\classes\paquetages\personne>"e:\program files\jdk14\bin\jar" cvf istia.st.jar istia\st\*
E:\data\serge\JAVA\classes\paquetages\personne>dir
06/06/2002 16:28 467 personne.java
06/06/2002 17:36 195 test.java
06/06/2002 16:04 <DIR> istia
06/06/2002 16:06 738 test.class
06/06/2002 16:47 236 test2.java
06/06/2002 18:15 740 test2.class
06/06/2002 18:08 874 istia.st.jar
Laten we met winzip de inhoud van het bestand istia.st.jar bekijken:

Laten we het bestand istia.st.jar in de map <jdk>\jre\lib\perso plaatsen:
E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\jre\lib\perso"
06/06/2002 18:08 874 istia.st.jar
Laten we nu het programma test2.java compileren en vervolgens uitvoeren:
E:\data\serge\JAVA\classes\paquetages\personne>javac -classpath istia.st.jar test2.java
E:\data\serge\JAVA\classes\paquetages\personne>java -classpath istia.st.jar;. test2
p1=personne(Jean,Dupont,20)
We zien dat we alleen de naam van het te doorzoeken archief hoefden op te geven, zonder expliciet aan te geven waar het zich bevond. Alle mappen in de boomstructuur <jdk>\jre\lib worden doorzocht om het gevraagde bestand .jar te vinden.
3.7. Het voorbeeld IMPÔTS
We nemen de belastingberekening die we in het vorige hoofdstuk al hebben besproken en verwerken deze met behulp van een klasse. Laten we het probleem nog eens op een rijtje zetten:
We gaan uit van het vereenvoudigde geval van een belastingplichtige die alleen zijn salaris hoeft aan te geven:
- we berekenen het aantal aandelen van de werknemer nbParts = nbEnfants/2 + 1 als hij ongehuwd is, nbEnfants/2 + 2 als hij getrouwd is, waarbij nbEnfants het aantal kinderen is.
- als hij ten minste drie kinderen heeft, krijgt hij een half deel extra
- zijn belastbaar inkomen wordt berekend als R = 0,72 * S, waarbij S zijn jaarsalaris is
- we berekenen zijn gezinscoëfficiënt QF = R / nbParts
- zijn belasting I wordt berekend. Laten we de volgende tabel bekijken:
12620,0 | 0 | 0 |
13190 | 0,05 | 631 |
15640 | 0,1 | 1290,5 |
24.740 | 0,15 | 2072,5 |
31810 | 0,2 | 3309,5 |
39.970 | 0,25 | 4900 |
48360 | 0,3 | 6898,5 |
55790 | 0,35 | 9316,5 |
92970 | 0,4 | 12106 |
127860 | 0,45 | 16754,5 |
151250 | 0,50 | 23147,5 |
172.040 | 0,55 | 30710 |
195.000 | 0,60 | 39312 |
0 | 0,65 | 49062 |
Elke regel heeft 3 velden. Om belasting I te berekenen, zoeken we de eerste regel waar QF <= veld1. Als bijvoorbeeld QF = 23000 is, vinden we de regel
Belasting I is dan gelijk aan 0,15*R - 2072,5*nbParts. Als QF zodanig is dat de relatie QF<=veld1 nooit geldt, dan worden de coëfficiënten van de laatste regel gebruikt. In dit geval:
wat de belasting I = 0,65*R - 49062*nbParts oplevert.
De klasse impots wordt als volgt gedefinieerd:
// aanmaken van een klasse 'belastingen'
public class impots{
// de gegevens die nodig zijn voor de berekening van de belasting
// zijn afkomstig uit een externe bron
private double[] limites, coeffR, coeffN;
// fabrikant
public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
// we controleren of de 3 tabellen dezelfde grootte hebben
boolean OK=LIMITES.length==COEFFR.length && LIMITES.length==COEFFN.length;
if (! OK) throw new Exception ("Les 3 tableaux fournis n'ont pas la même taille("+
LIMITES.length+","+COEFFR.length+","+COEFFN.length+")");
// het klopt
this.limites=LIMITES;
this.coeffR=COEFFR;
this.coeffN=COEFFN;
}//fabrikant
// berekening van de belasting
public long calculer(boolean marié, int nbEnfants, int salaire){
// berekening van het aantal aandelen
double nbParts;
if (marié) nbParts=(double)nbEnfants/2+2;
else nbParts=(double)nbEnfants/2+1;
if (nbEnfants>=3) nbParts+=0.5;
// berekening belastbaar inkomen & gezinsquotiënt
double revenu=0.72*salaire;
double QF=revenu/nbParts;
// belastingberekening
limites[limites.length-1]=QF+1;
int i=0;
while(QF>limites[i]) i++;
// resultaat weergeven
return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
}//berekenen
}//klasse
Er wordt een 'impots'-object aangemaakt met de gegevens die nodig zijn om de belasting van een belastingplichtige te berekenen. Dit is het vaste deel van het object. Zodra dit object is aangemaakt, kan de methode 'calculer' herhaaldelijk worden aangeroepen om de belasting van de belastingplichtige te berekenen op basis van zijn burgerlijke staat (gehuwd of ongehuwd), het aantal kinderen en zijn jaarsalaris.
Een testprogramma zou er als volgt uit kunnen zien:
//geïmporteerde klassen
// belastingen importeren;
import java.io.*;
public class test
{
public static void main(String[] arg) throws IOException
{
// interactief programma voor belastingberekening
// de gebruiker voert drie gegevens in via het toetsenbord: gehuwd nbEnfants salaris
// het programma geeft vervolgens de te betalen belasting weer
final String syntaxe="syntaxe : marié nbEnfants salaire\n"
+"marié : o pour marié, n pour non marié\n"
+"nbEnfants : nombre d'enfants\n"
+"salaire : salaire annuel en F";
// tabellen met gegevens die nodig zijn voor de berekening van de belasting
double[] limites=new double[] {12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0};
double[] coeffR=new double[] {0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65};
double[] coeffN=new double[] {0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062};
// aanmaken van een leesstroom
BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
// aanmaken van een belastingobject
impots objImpôt=null;
try{
objImpôt=new impots(limites,coeffR,coeffN);
}catch (Exception ex){
System.err.println("L'erreur suivante s'est produite : " + ex.getMessage());
System.exit(1);
}//try-catch
// oneindige lus
while(true){
// de parameters voor de belastingberekening worden opgevraagd
System.out.print("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :");
String paramètres=IN.readLine().trim();
// moet er iets gebeuren?
if(paramètres==null || paramètres.equals("")) break;
// controle van het aantal argumenten in de ingevoerde regel
String[] args=paramètres.split("\\s+");
int nbParamètres=args.length;
if (nbParamètres!=3){
System.err.println(syntaxe);
continue;
}//if
// controle van de geldigheid van de parameters
// getrouwd
String marié=args[0].toLowerCase();
if (! marié.equals("o") && ! marié.equals("n")){
System.err.println(syntaxe+"\nArgument marié incorrect : tapez o ou n");
continue;
}//if
// nbEnfants
int nbEnfants=0;
try{
nbEnfants=Integer.parseInt(args[1]);
if(nbEnfants<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument nbEnfants incorrect : tapez un entier positif ou nul");
continue;
}//if
// salaris
int salaire=0;
try{
salaire=Integer.parseInt(args[2]);
if(salaire<0) throw new Exception();
}catch (Exception ex){
System.err.println(syntaxe+"\nArgument salaire incorrect : tapez un entier positif ou nul");
continue;
}//if
// de parameters zijn correct – de belasting wordt berekend
System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
// volgende belastingplichtige
}//while
}//hoofdprogramma
}//klasse
Hier volgt een voorbeeld van de uitvoering van het vorige programma:
E:\data\serge\MSNET\c#\impots\3>java test
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument marié incorrect : tapez o ou n
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o s d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument nbEnfants incorrect : tapez un entier positif ou nul
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 d
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Argument salaire incorrect : tapez un entier positif ou nul
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :q s d f
syntaxe : marié nbEnfants salaire
marié : o pour marié, n pour non marié
nbEnfants : nombre d'enfants
salaire : salaire annuel en F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22504 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :