Skip to content

3. Klassen und Schnittstellen

3.1. Das Objekt anhand von Beispielen erklärt

3.1.1. Übersicht

Wir werden nun die objektorientierte Programmierung anhand von Beispielen erkunden. Ein Objekt ist eine Entität, die Daten enthält, die ihren Zustand definieren (sogenannte Attribute oder Eigenschaften), sowie Funktionen (sogenannte Methoden). Ein Objekt wird auf der Grundlage einer Vorlage erstellt, die als Klasse bezeichnet wird:

public class C1{
    type1 p1;            // property p1
    type2 p2;            // property p2
    
    type3 m3(){        // m3 method
        
    }
    type4 m4(){        // m4 method
        
    }
    
}

Aus der vorherigen Klasse C1 können wir viele Objekte O1, O2, … erstellen. Alle werden die Eigenschaften p1, p2, … und die Methoden m3, m4, … haben. Sie werden unterschiedliche Werte für ihre Eigenschaften pi haben und somit jeweils einen eigenen Zustand besitzen.

Wenn O1 ein Objekt vom Typ C1 ist, dann bezieht sich O1.p1 auf die Eigenschaft p1 von O1 und O1.m1 auf die Methode m1 von O1.

Betrachten wir ein erstes Objektmodell: die Klasse Person.

3.1.2. Definition der Klasse Person

Die Definition der Klasse „Person“ lautet wie folgt:


import java.io.*;
 
public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Hier sehen wir die Definition einer Klasse, die einen Datentyp darstellt. Wenn wir Variablen dieses Typs erstellen, nennen wir sie Objekte. Eine Klasse ist also eine Vorlage, nach der Objekte erstellt werden.

Die Mitglieder oder Felder einer Klasse können Daten oder Methoden (Funktionen) sein. Diese Felder können eines der folgenden drei Attribute haben:

private: Ein privates Feld ist nur für die internen Methoden der Klasse zugänglich

public: Ein öffentliches Feld ist für jede Funktion zugänglich, unabhängig davon, ob sie innerhalb der Klasse definiert ist oder nicht

protected: Ein geschütztes Feld ist nur für die internen Methoden der Klasse oder für ein abgeleitetes Objekt zugänglich (siehe das Konzept der Vererbung weiter unten).

Im Allgemeinen werden die Daten einer Klasse als privat deklariert, während ihre Methoden als öffentlich deklariert werden. Das bedeutet, dass der Benutzer eines Objekts (der Programmierer)

a: keinen direkten Zugriff auf die privaten Daten des Objekts hat

b: die öffentlichen Methoden des Objekts aufrufen kann, einschließlich derer, die Zugriff auf dessen private Daten gewähren.

Die Syntax für die Deklaration eines Objekts lautet wie folgt:


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
}

Anmerkungen

  • Die Reihenfolge, in der private, geschützte und öffentliche Attribute deklariert werden, ist beliebig.

3.1.3. Die initialize-Methode

Kehren wir zu unserer Person-Klasse zurück, die wie folgt deklariert ist:


import java.io.*;
 
public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Welche Rolle spielt die initialize-Methode? Da lastName, firstName und age private Mitglieder der Person-Klasse sind, sind die Anweisungen

personne p1;
p1.prenom="Jean";
p1.nom="Dupont";
p1.age=30;

sind ungültig. Wir müssen ein Objekt vom Typ Person mithilfe einer öffentlichen Methode initialisieren. Dies ist die Aufgabe der Methode initialize. Wir schreiben:

personne p1;
p1.initialise("Jean","Dupont",30);

Die Syntax p1.initialize ist gültig, da initialize öffentlich ist.

3.1.4. Der new-Operator

Die Anweisungssequenz

personne p1;
p1.initialise("Jean","Dupont",30);

ist falsch. Die Aussage

    personne p1;

erklärt p1 als Referenz auf ein Objekt vom Typ person. Dieses Objekt existiert noch nicht, daher wird p1 nicht initialisiert. Es ist, als hätten wir geschrieben:

personne p1=null;

wobei wir mit dem Schlüsselwort null explizit angeben, dass die Variable p1 noch auf kein Objekt verweist.

Wenn wir dann schreiben

p1.initialise("Jean","Dupont",30);

rufen wir die initialize-Methode des Objekts auf, auf das p1 verweist. Dieses Objekt existiert jedoch noch nicht, und der Compiler meldet einen Fehler. Damit p1 auf ein Objekt verweisen kann, müssen wir schreiben:

personne p1=new personne();

Dadurch wird ein nicht initialisiertes Person-Objekt erstellt: Die Attribute name und first_name, die Verweise auf String-Objekte sind, haben den Wert null, und age hat den Wert 0. Es findet also eine Standardinitialisierung statt. Da p1 nun auf ein Objekt verweist, wird die Initialisierungsanweisung für dieses Objekt

p1.initialise("Jean","Dupont",30);

gültig.

3.1.5. Das Schlüsselwort this

Sehen wir uns den Code für die initialize-Methode an:


public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }

Die Anweisung this.firstName = P bedeutet, dass dem Attribut firstName des aktuellen Objekts (this) der Wert P zugewiesen wird. Das Schlüsselwort this bezieht sich auf das aktuelle Objekt: dasjenige, in dem sich die ausgeführte Methode befindet. Woher wissen wir das? Schauen wir uns an, wie das von p1 referenzierte Objekt im aufrufenden Programm initialisiert wird:

p1.initialise("Jean","Dupont",30);

Es ist die Methode initialise des Objekts p1, die aufgerufen wird. Wenn wir innerhalb dieser Methode auf das Objekt this verweisen, verweisen wir tatsächlich auf das Objekt p1. Die Methode initialise hätte auch wie folgt geschrieben werden können:


public void initialise(String P, String N, int age){
   prenom=P;
   nom=N;
   this.age=age;
  }

Wenn eine Methode eines Objekts auf ein Attribut A dieses Objekts verweist, ist die Notation this.A impliziert. Sie muss explizit verwendet werden, wenn ein Konflikt zwischen Bezeichnern vorliegt. Dies ist bei der folgenden Anweisung der Fall:


this.age=age;

wobei „age“ sowohl auf ein Attribut des aktuellen Objekts als auch auf den von der Methode empfangenen Parameter „age“ verweist. Die Mehrdeutigkeit muss dann durch die Bezugnahme auf das Attribut „age“ als „this.age“ aufgelöst werden.

3.1.6. Ein Testprogramm

Hier ist ein Testprogramm:


public class test1{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

Die Klasse „Person“ ist in der Quelldatei person.java definiert und wird kompiliert:

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

Das Gleiche machen wir für das Testprogramm:

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

Es mag überraschend erscheinen, dass das Programm test1.java die Klasse person nicht mit einer Anweisung importiert:

import personne;

Wenn der Compiler im Quellcode auf eine Klassenreferenz stößt, die nicht in derselben Quelldatei definiert ist, sucht er an verschiedenen Stellen nach der Klasse:

  • in den Paketen, die durch die Import-Anweisungen importiert wurden
  • in dem Verzeichnis, aus dem der Compiler gestartet wurde

In unserem Beispiel wurde der Compiler aus dem Verzeichnis gestartet, das die Datei personne.class enthält, was erklärt, warum er die Definition der Klasse personne gefunden hat. In diesem Szenario führt das Hinzufügen einer Importanweisung zu einem Kompilierungsfehler:

E:\data\serge\JAVA\BASES\OBJETS\2>javac test1.java
test1.java:1: '.' expected
import personne;
               ^
1 error

Um diesen Fehler zu vermeiden und gleichzeitig sicherzustellen, dass die Klasse Person importiert wird, werden wir künftig am Anfang des Programms Folgendes schreiben:

// classes importées
// import personne;

Wir können nun die Datei test1.class ausführen:

E:\data\serge\JAVA\BASES\OBJETS\2>java test1
Jean,Dupont,30

Es ist möglich, mehrere Klassen in einer einzigen Quelldatei zusammenzufassen. Fassen wir die Klassen person* und test1 in der Quelldatei test2.java* zusammen. Die Klasse test1 wird in test2 umbenannt, um der Änderung des Quelldateinamens Rechnung zu tragen:

// imported packages
import java.io.*;

class personne{
   // attributes
  private String prenom;    // first name
  private String nom;            // its name
  private int age;                // his age

   // method
  public void initialise(String P, String N, int age){
    this.prenom=P;
    this.nom=N;
    this.age=age;
  }//initialize

   // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }//identifies
}//class
public class test2{
  public static void main(String arg[]){
    personne p1=new personne();
    p1.initialise("Jean","Dupont",30);
    p1.identifie();
  }
}

Beachten Sie, dass die Klasse „Person“ nicht mehr das Attribut „public“ aufweist. Tatsächlich darf in einer Java-Quelldatei nur eine Klasse das Attribut „public“ haben. Dies ist die Klasse, die die Methode „main“ enthält. Außerdem muss die Quelldatei nach dieser Klasse benannt sein. Kompilieren wir nun die Datei „test2.java“:

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

Beachten Sie, dass für jede der in der Quelldatei vorhandenen Klassen eine .class-Datei generiert wurde. Führen wir nun die Datei test2.class aus:

E:\data\serge\JAVA\BASES\OBJETS\2>java test2
Jean,Dupont,30

Von nun an werden wir beide Methoden abwechselnd verwenden:

  • Klassen, die in einer einzigen Quelldatei zusammengefasst sind
  • eine Klasse pro Quelldatei

3.1.7. Eine andere Methode initialisiert

Fahren wir mit der Klasse „Person“ fort und fügen wir ihr die folgende Methode hinzu:


public void initialise(personne P){
    prenom=P.prenom;
    nom=P.nom;
    this.age=P.age;
  }

Wir haben nun zwei Methoden namens *initialize*: Dies ist zulässig, solange sie unterschiedliche Parameter verwenden. Das ist hier der Fall. Der Parameter ist nun eine Referenz P auf eine Person. Die Attribute der Person P werden dann dem aktuellen Objekt (this) zugewiesen. Beachten Sie, dass die Methode initialize direkten Zugriff auf die Attribute des Objekts P hat, obwohl diese vom Typ private sind. Dies gilt immer: Methoden eines Objekts O1 einer Klasse C haben immer Zugriff auf die privaten Attribute anderer Objekte derselben Klasse C.

Hier ist ein Test der neuen Klasse „Person“:


// import nobody;
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();
  }
}

und das Ergebnis:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.8. Konstruktoren der Klasse „Person“

Ein Konstruktor ist eine Methode, die den Namen der Klasse trägt und beim Erstellen des Objekts aufgerufen wird. Er wird im Allgemeinen zur Initialisierung des Objekts verwendet. Es handelt sich um eine Methode, die Argumente annehmen kann, aber kein Ergebnis zurückgibt. Ihrem Prototyp oder ihrer Definition geht kein Typ voraus (nicht einmal void).

Wenn eine Klasse einen Konstruktor hat, der n Argumente args akzeptiert, können die Deklaration und Initialisierung eines Objekts dieser Klasse wie folgt erfolgen:


        classe objet =new classe(arg1,arg2, ... argn);

oder


        classe objet;

        objet=new classe(arg1,arg2, ... argn);

Wenn eine Klasse einen oder mehrere Konstruktoren hat, muss einer dieser Konstruktoren verwendet werden, um ein Objekt dieser Klasse zu erstellen. Wenn eine Klasse C keine Konstruktoren hat, verfügt sie über einen Standardkonstruktor, nämlich den Konstruktor ohne Parameter: public C(). Die Attribute des Objekts werden dann mit Standardwerten initialisiert. Genau das geschah, als wir in den vorherigen Programmen Folgendes geschrieben haben:

    personne p1;
    p1=new personne();

Erstellen wir zwei Konstruktoren für unsere Klasse Person:


public class personne{
  // attributes
  private String prenom;
  private String nom;
  private int age;
  
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
  }
  public personne(personne P){
    initialise(P);
  }
 
  // method
  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;
  }
 
  // method
  public void identifie(){
    System.out.println(prenom+","+nom+","+age);
  }
}

Unsere beiden Konstruktoren rufen einfach die entsprechenden initialize-Methoden auf. Erinnern Sie sich daran, dass, wenn wir in einem Konstruktor beispielsweise die Notation initialize(P) finden, der Compiler diese in this.initialize(P) übersetzt. Im Konstruktor wird die initialize-Methode daher aufgerufen, um auf das Objekt zu wirken, auf das this verweist, also das aktuelle Objekt, das gerade konstruiert wird.

Hier ist ein Testprogramm:


// import nobody;
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();
  }
}

und die erzielten Ergebnisse:

p1=Jean,Dupont,30
p2=Jean,Dupont,30

3.1.9. Objektreferenzen

Wir verwenden weiterhin dieselbe Person-Klasse. Das Testprogramm sieht nun wie folgt aus:


// import nobody;
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 references the same object as p1
    personne p2=p1;
    System.out.print("p2="); p2.identifie();
    // p3 references an object that will be a copy of the object referenced by p1
    personne p3=new personne(p1);
    System.out.print("p3=");  p3.identifie();
    // change the state of the object referenced by p1
    p1.initialise("Micheline","Benoît",67);
    System.out.print("p1=");  p1.identifie();
    // as p2=p1, the object referenced by p2 must have changed state
    System.out.print("p2=");  p2.identifie();
    // as p3 does not reference the same object as p1, the object referenced by p3 must not have changed
    System.out.print("p3=");  p3.identifie();
  }
}

Die Ergebnisse lauten wie folgt:

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

Bei der Deklaration der Variablen p1 mit

personne p1=new personne("Jean","Dupont",30);

verweist p1 auf das Objekt personne("Jean","Dupont",30), ist aber nicht das Objekt selbst. In C würde man sagen, dass es sich um einen Zeiger handelt, d. h. um die Adresse des erstellten Objekts. Wenn wir dann schreiben:

    p1=null

wird nicht das Objekt person("Jean","Dupont",30) verändert, sondern die Referenz p1 ändert ihren Wert. Das Objekt person("Jean","Dupont",30) geht „verloren“, wenn es von keiner anderen Variablen referenziert wird.

Wenn wir schreiben:

personne p2=p1;

initialisieren wir den Zeiger p2: Er „zeigt“ auf dasselbe Objekt (er verweist auf dasselbe Objekt) wie der Zeiger p1. Wenn wir also das Objekt ändern, auf das p1 „zeigt“ (oder auf das er verweist), ändern wir auch dasjenige, auf das p2 verweist.

Wenn wir schreiben:

personne p3=new personne(p1);

wird ein neues Objekt erstellt, das eine Kopie des Objekts ist, auf das p1 verweist. Auf dieses neue Objekt verweist p3. Wenn Sie das Objekt ändern, auf das p1 „zeigt“ (oder auf das es verweist), ändern Sie das von p3 referenzierte Objekt in keiner Weise. Das zeigen die Ergebnisse.

3.1.10. Temporäre Objekte

In einem Ausdruck können Sie den Konstruktor eines Objekts explizit aufrufen: Das Objekt wird erstellt, aber Sie können nicht darauf zugreifen (um es beispielsweise zu ändern). Dieses temporäre Objekt wird zum Auswerten des Ausdrucks erstellt und anschließend verworfen. Der von ihm belegte Speicherplatz wird später automatisch von einem Programm namens „Garbage Collector“ zurückgewonnen, dessen Aufgabe es ist, Speicherplatz zurückzugewinnen, der von Objekten belegt wird, auf die von Programmdaten nicht mehr verwiesen wird.

Betrachten Sie das folgende Beispiel:


// import nobody;
 
public class test1{
  public static void main(String arg[]){
    new personne(new personne("Jean","Dupont",30)).identifie();
  }
}

und ändern wir die Konstruktoren der Klasse „Person“ so, dass sie eine Meldung anzeigen:


// manufacturers
  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);
  }

Wir erhalten folgende Ergebnisse:

Constructeur personne(String, String, int)
Constructeur personne(personne)
Jean,Dupont,30

zeigt die aufeinanderfolgende Erstellung der beiden temporären Objekte.

3.1.11. Methoden zum Lesen und Schreiben privater Attribute

Wir fügen der Klasse „Person“ die erforderlichen Methoden hinzu, um den Zustand der Attribute der Objekte zu lesen oder zu ändern:


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);
  }
 
  // 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;
  }
}

Wir testen die neue Klasse mit dem folgenden Programm:


// import nobody;
 
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()+")");
  }
}

und wir erhalten folgende Ergebnisse:

P=(Jean,Michelin,34)
P=(Jean,Michelin,56)

3.1.12. Klassenmethoden und Attribute

Angenommen, wir möchten die Anzahl der in einer Anwendung erstellten Person-Objekte zählen. Wir könnten selbst einen Zähler verwalten, aber dabei besteht die Gefahr, dass wir temporäre Objekte vergessen, die hier und da erstellt werden. Es erscheint sicherer, in die Konstruktoren der Person-Klasse eine Anweisung einzufügen, die einen Zähler inkrementiert. Das Problem besteht darin, eine Referenz auf diesen Zähler zu übergeben, damit der Konstruktor ihn inkrementieren kann: Wir müssen ihnen einen neuen Parameter übergeben. Wir können den Zähler auch in die Klassendefinition aufnehmen. Da es sich um ein Attribut der Klasse selbst und nicht um ein bestimmtes Objekt dieser Klasse handelt, deklarieren wir es anders, indem wir das Schlüsselwort static verwenden:

    private static long nbPersonnes;        // number of people created

Um darauf zu verweisen, schreiben wir person.nbPeople, um zu zeigen, dass es sich um ein Attribut der Klasse *Person* selbst handelt. Hier haben wir ein privates Attribut erstellt, auf das von außerhalb der Klasse nicht direkt zugegriffen werden kann. Wir erstellen daher eine öffentliche Methode, um Zugriff auf das Klassenattribut „nbPersonnes“ zu gewähren. Um den Wert von „nbPersonnes“ zurückzugeben, benötigt die Methode kein spezifisches Objekt: Tatsächlich ist „nbPersonnes“ nicht das Attribut eines bestimmten Objekts, sondern das Attribut der gesamten Klasse. Daher benötigen wir eine Klassenmethode, die ebenfalls als statisch deklariert ist:

public static long getNbPersonnes(){
    return nbPersonnes;
}

die von außen mit der Syntax person.getNbPeople() aufgerufen wird. Hier ist ein Beispiel.

Die Klasse Person sieht nun wie folgt aus:


public class personne{
  
  // class attribute
  private static long nbPersonnes=0;
 
  // object attributes

 
  // manufacturers
  public personne(String P, String N, int age){
    initialise(P,N,age);
    nbPersonnes++;
  }
  public personne(personne P){
    initialise(P);
    nbPersonnes++;
  }
 
  // method

 
  // class method
  public static long getNbPersonnes(){
    return nbPersonnes;
  }
 
}// class

Mit dem folgenden Programm:


// import nobody;
 
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());
  }// hand
}//test1

Wir erhalten folgende Ergebnisse:

    Nombre de personnes créées : 3

3.1.13. Ein Objekt an eine Funktion übergeben

Wir haben bereits erwähnt, dass Java die tatsächlichen Funktionsparameter per Wert übergibt: Die Werte der tatsächlichen Parameter werden in die formalen Parameter kopiert. Eine Funktion kann daher die tatsächlichen Parameter nicht ändern.

Im Falle eines Objekts darf man sich nicht von dem häufigen Sprachmissbrauch täuschen lassen, der auftritt, wenn Menschen systematisch von einem „Objekt“ statt von einer „Objektreferenz“ sprechen. Ein Objekt wird nur über eine Referenz (einen Zeiger) darauf manipuliert. An eine Funktion übergeben wird daher nicht das Objekt selbst, sondern eine Referenz auf dieses Objekt. Es ist somit der Wert der Referenz – nicht der Wert des Objekts selbst –, der in den formalen Parameter kopiert wird: Es wird kein neues Objekt erstellt.

Wird eine Objektreferenz R1 an eine Funktion übergeben, wird sie in den entsprechenden formalen Parameter R2 kopiert. Somit verweisen die Referenzen R2 und R1 auf dasselbe Objekt. Wenn die Funktion das von R2 referenzierte Objekt verändert, verändert sie offensichtlich auch das von R1 referenzierte, da es sich um dasselbe Objekt handelt.

Image

Dies wird durch das folgende Beispiel veranschaulicht:


// import nobody;
 
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();
  }// hand
 
  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();
  }// modify
}// class

Die Methode „modify“ ist als statisch deklariert, da es sich um eine Klassenmethode handelt: Sie müssen ihr kein Objekt voranstellen, um sie aufzurufen. Die erhaltenen Ergebnisse lauten wie folgt:

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

Wir sehen, dass nur ein Objekt erstellt wird: das der Person p1 in der Hauptfunktion, und dass das Objekt tatsächlich durch die Funktion modify geändert wurde.

3.1.14. Kapselung der Ausgabeparameter einer Funktion in einem Objekt

Da Parameter als Wert übergeben werden, ist es nicht möglich, eine Java-Funktion mit Ausgabeparametern vom Typ int zu schreiben, da wir keine Referenz auf einen int-Typ übergeben können, der kein Objekt ist. Wir können daher eine Klasse erstellen, die den int-Typ kapseln:


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;
  }
}

Die vorstehende Klasse verfügt über einen Konstruktor zur Initialisierung einer Ganzzahl sowie zwei Methoden zum Auslesen und Ändern des Werts dieser Ganzzahl. Wir testen diese Klasse mit dem folgenden Programm:


// import integer;
 
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);
  }
}

und wir erhalten folgende Ergebnisse:

I=12
I=15

3.1.15. Eine Reihe von Menschen

Ein Objekt ist ein Datenelement wie jedes andere auch, und als solches können mehrere Objekte in einem Array zusammengefasst werden:


// import nobody;
 
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();
  }
}

Die Anweisung person[] friends = new person[3]; erstellt ein Array mit 3 Elementen vom Typ person. Diese 3 Elemente werden hier mit dem Wert null initialisiert, was bedeutet, dass sie auf keine Objekte verweisen. Auch hier sprechen wir im technischen Sinne von einem „Array von Objekten“, obwohl es sich eigentlich nur um ein Array von Objektreferenzen handelt. Die Erstellung des Objekt-Arrays – eines Arrays, das selbst ein Objekt ist (wie durch die Verwendung von new angedeutet) – erzeugt an sich noch keine Objekte des Typs seiner Elemente: Dies muss anschließend erfolgen.

Es ergeben sich folgende Ergebnisse:

----------------
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. Vererbung anhand von Beispielen

3.2.1. Übersicht

Hier besprechen wir das Konzept der Vererbung. Der Zweck der Vererbung besteht darin, eine bestehende Klasse so „anzupassen“, dass sie unseren Anforderungen entspricht. Angenommen, wir möchten eine Klasse „Lehrer“ erstellen: Ein Lehrer ist eine bestimmte Art von Person. Er verfügt über Attribute, die andere Personen nicht haben, beispielsweise das Fach, das er unterrichtet. Er hat aber auch die Attribute einer beliebigen Person: Vorname, Nachname und Alter. Ein Lehrer ist daher ein vollwertiges Mitglied der Klasse „Person“, verfügt jedoch über zusätzliche Attribute. Anstatt eine Klasse „Lehrer“ von Grund auf neu zu schreiben, würden wir es vorziehen, auf der bestehenden Klasse „Person“ aufzubauen und diese an die spezifischen Eigenschaften von Lehrern anzupassen. Das Konzept der Vererbung ermöglicht uns dies.

Um auszudrücken, dass die Klasse „Lehrer“ die Eigenschaften der Klasse „Person“ erbt, würden wir schreiben:


    public class enseignant extends personne

„Person“ wird als übergeordnete (oder Basis-)Klasse bezeichnet, und „Teacher“ ist die abgeleitete (oder untergeordnete) Klasse. Ein „Teacher“-Objekt verfügt über alle Eigenschaften eines „Person“-Objekts: Es hat dieselben Attribute und Methoden. Diese Attribute und Methoden der übergeordneten Klasse werden in der Definition der untergeordneten Klasse nicht wiederholt; wir geben lediglich die Attribute und Methoden an, die von der untergeordneten Klasse hinzugefügt wurden:


class enseignant extends personne{
// attributes
  private int section;
 
// manufacturer
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }
}

Wir nehmen an, dass die Klasse „Person“ wie folgt definiert ist:


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;
  }
}

Die Methode identifiant wurde leicht geändert, um eine Zeichenkette zurückzugeben, die die Person identifiziert, und heißt nun identite. Hier erweitert die Klasse Teacher die Methoden und Attribute der Klasse Person:

  • ein Attribut `section`, das die Sektionsnummer angibt, zu der der Lehrer innerhalb des Lehrerkollegiums gehört (im Grunde eine Sektion pro Fach)
  • einen neuen Konstruktor, der alle Attribute eines Lehrers initialisiert

3.2.2. Ein Lehrerobjekt erstellen

Der Konstruktor für die Klasse „Teacher“ lautet wie folgt:


// constructeur
  public enseignant(String P, String N, int age,int section){
    super(P,N,age);
    this.section=section;
  }

Die Anweisung super(P, N, age) ist ein Aufruf des Konstruktors der übergeordneten Klasse, in diesem Fall der Klasse Person. Wir wissen, dass dieser Konstruktor die Felder first_name, last_name und age des im Student-Objekt enthaltenen Person-Objekts initialisiert. Das erscheint recht kompliziert, und wir würden vielleicht lieber schreiben:


// constructeur
  public enseignant(String P, String N, int age,int section){
    this.prenom=P;
        this.nom=N
        this.age=age
      this.section=section;
  }

Das ist unmöglich. Die Klasse „Person“ hat ihre drei Felder – first_name, last_name und age – als privat deklariert. Nur Objekte derselben Klasse haben direkten Zugriff auf diese Felder. Alle anderen Objekte, einschließlich untergeordneter Objekte wie in diesem Fall, müssen öffentliche Methoden verwenden, um auf sie zuzugreifen. Dies wäre anders gewesen, wenn die Klasse „Person“ die drei Felder als geschützt deklariert hätte: Dann hätten abgeleitete Klassen direkten Zugriff auf die drei Felder gehabt. In unserem Beispiel war die Verwendung des Konstruktors der übergeordneten Klasse daher die richtige Lösung, und dies ist der Standardansatz: Beim Erstellen eines untergeordneten Objekts rufen wir zunächst den Konstruktor des übergeordneten Objekts auf und führen dann die für das untergeordnete Objekt spezifischen Initialisierungen durch (in unserem Beispiel section).

Probieren wir ein erstes Programm aus:


// import nobody;
// import teacher;
 
public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

Dieses Programm erstellt lediglich ein „Teacher“-Objekt (new) und identifiziert es. Die Klasse „Teacher“ verfügt zwar nicht über eine „identify“-Methode, ihre übergeordnete Klasse hat jedoch eine solche Methode, die zudem öffentlich ist: Durch Vererbung wird sie zu einer öffentlichen Methode der Klasse „Teacher“.

Die Quelldateien für die Klassen werden im selben Verzeichnis abgelegt und anschließend kompiliert:

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

Die Datei test1.class wird ausgeführt:

E:\data\serge\JAVA\BASES\OBJETS\4>java test1
personne(Jean,Dupont,30)

3.2.3. Methodenüberladung

Im vorherigen Beispiel war die Identität der Person Teil des Lehrers, aber es fehlen einige für die Klasse „Teacher“ spezifische Informationen (die Abteilung). Wir müssen daher eine Methode schreiben, um den Lehrer zu identifizieren:


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+")";    
  }    
}

Die Methode identity der Klasse Teacher stützt sich auf die Methode identity ihrer übergeordneten Klasse (super.identity), um den Teil „person“ anzuzeigen, und fügt anschließend das Feld section hinzu, das spezifisch für die Klasse Teacher ist.

Die Klasse „Teacher“ verfügt nun über zwei identity-Methoden:

  • die von der übergeordneten Klasse „Person“ geerbte
  • ihre eigene

Wenn E ein „Teacher“-Objekt ist, verweist E.identity auf die Identitätsmethode der „Teacher“-Klasse. Wir sagen, dass die Identitätsmethode der übergeordneten Klasse durch die Identitätsmethode der untergeordneten Klasse „überschrieben“ wird. Allgemein gilt: Wenn O ein Objekt und M eine Methode ist, sucht das System zur Ausführung der Methode O.M in der folgenden Reihenfolge nach einer Methode M:

  • in der Klasse des Objekts O
  • in seiner übergeordneten Klasse, falls vorhanden
  • in der übergeordneten Klasse seiner übergeordneten Klasse, falls diese existiert
  • und so weiter…

Die Vererbung ermöglicht es daher, Methoden mit demselben Namen in der übergeordneten Klasse in der untergeordneten Klasse zu überschreiben. Dadurch kann die untergeordnete Klasse an ihre eigenen Bedürfnisse angepasst werden. In Kombination mit dem Polymorphismus, auf den wir gleich noch eingehen werden, ist das Überschreiben von Methoden der Hauptvorteil der Vererbung.

Betrachten wir dasselbe Beispiel wie zuvor:


// import nobody;
// import teacher;
 
public class test1{
  public static void main(String arg[]){
    System.out.println(new enseignant("Jean","Dupont",30,27).identite());
  }
}

Die diesmal erzielten Ergebnisse lauten wie folgt:

    enseignant(personne(Jean,Dupont,30),27)

3.2.4. Polymorphismus

Betrachten wir eine Klassenhierarchie: C0 C1 C2 … Cn

wobei Ci Cj bedeutet, dass die Klasse Cj von der Klasse Ci abgeleitet ist. Dies bedeutet, dass die Klasse Cj alle Eigenschaften der Klasse Ci sowie weitere Eigenschaften besitzt. Seien Oi Objekte vom Typ Ci . Es ist zulässig zu schreiben:

    Oi=Oj avec j>i

Tatsächlich besitzt die Klasse Cj durch Vererbung alle Eigenschaften der Klasse Ci sowie zusätzliche Eigenschaften. Daher enthält ein Objekt Oj vom Typ Cj in sich ein Objekt vom Typ Ci. Die Operation

    Oi=Oj

bedeutet, dass Oi ein Verweis auf das Objekt vom Typ Ci ist, das im Objekt Oj enthalten ist.

Die Tatsache, dass eine Variable Oi der Klasse Ci nicht nur auf ein Objekt der Klasse Ci, sondern auf jedes von der Klasse Ci abgeleitete Objekt verweisen kann, wird als Polymorphismus bezeichnet: die Fähigkeit einer Variablen, auf verschiedene Objekttypen zu verweisen.

Betrachten wir ein Beispiel und nehmen wir die folgende Funktion, die von keiner Klasse abhängig ist:

    public static void affiche(Object obj){
        .
    }

Die Klasse „Object“ ist die „Überklasse“ aller Java-Klassen. Wenn wir also schreiben:

    public class personne

schreiben wir implizit:

    public class personne extends Object

Somit enthält jedes Java-Objekt eine *Object*-Komponente. Daher können wir schreiben:

    enseignant e;
    affiche(e);

Der formale Parameter vom Typ „Object“ in der display-Funktion erhält einen Wert vom Typ „Teacher“. Da „Teacher“ von „Object“ abgeleitet ist, ist dies zulässig.

3.2.5. Überladung und Polymorphismus

Vervollständigen wir unsere display-Funktion:

    public static void affiche(Object obj){
        System.out.println(obj.toString());
    }

Die Methode obj.toString() gibt eine Zeichenkette zurück, die das Objekt obj in der Form class_name@object_address identifiziert. Was passiert im Fall unseres vorherigen Beispiels:

    enseignant e=new enseignant(...);
    affiche(e);

Das System muss die Anweisung System.out.println(e.toString()) ausführen, wobei e ein Teacher-Objekt ist. Es sucht in der Klassenhierarchie, die zur Teacher-Klasse führt, nach einer toString-Methode, beginnend mit der untersten:

  • In der Klasse „Teacher“ findet es keine toString()-Methode
  • in der übergeordneten Klasse Person findet es keine toString()-Methode
  • in der übergeordneten Klasse „Object“ findet es die toString()-Methode und führt sie aus

Das zeigt das folgende Programm:


// import nobody;
// import teacher;
 
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());
  }
}

Die Ergebnisse lauten wie folgt:

enseignant@1ee789
personne@1ee770

Das heißt, class_name@object_address. Da dies nicht sehr klar ist, könnten wir versucht sein, für die Klassen Person und Student eine toString-Methode zu definieren, die die toString-Methode der übergeordneten Klasse Object überschreiben würde. Anstatt Methoden zu schreiben, die den bereits in den Klassen Person und Teacher vorhandenen Identitätsmethoden ähneln würden, benennen wir diese Identitätsmethoden einfach in toString um:


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+")";    
  }    
}

Mit demselben Testprogramm wie zuvor ergeben sich folgende Ergebnisse:

enseignant(personne(Lucile,Dumas,56),61)
personne(Jean,Dupont,30)

3.3. Interne Klassen

Eine Klasse kann die Definition einer anderen Klasse enthalten. Betrachten Sie das folgende Beispiel:

// imported classes
import java.io.*;

public class test1{

    // internal class
    private class article{
         // we define the structure
        private String code;
        private String nom;
        private double prix;
        private int stockActuel;
        private int stockMinimum;

     // manufacturer
    public article(String code, String nom, double prix, int stockActuel, int stockMinimum){
         // attribute initialization
      this.code=code;
      this.nom=nom;
      this.prix=prix;
      this.stockActuel=stockActuel;
      this.stockMinimum=stockMinimum;
    }//manufacturer

    //toString
    public String toString(){
        return "article("+code+","+nom+","+prix+","+stockActuel+","+stockMinimum+")";
    }//toString
  }//item class

   // local data
  private article art=null;

   // manufacturer
  public test1(String code, String nom, double prix, int stockActuel, int stockMinimum){
       // attribute definition
    art=new article(code, nom, prix, stockActuel,stockMinimum);
  }//test1

   // accessor
  public article getArticle(){
      return art;
  }//getArticle

    public static void main(String arg[]){
      // create a test1 instance
      test1 t1=new test1("a100","velo",1000,10,5);
    // display test1.art
    System.out.println("art="+t1.getArticle());
  }//hand

}// fin class

Die Klasse test1 enthält die Definition einer weiteren Klasse, der Klasse article. Wir sagen, dass article eine innere Klasse der Klasse test1 ist. Dies kann nützlich sein, wenn die innere Klasse nur innerhalb der Klasse benötigt wird, die sie enthält. Beim Kompilieren des obigen Quellcodes test1.java erhalten wir zwei .class-Dateien:

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

Für die Klasse „article“, die zur Klasse „test1“ gehört, wurde eine Datei „test1$article.class“ generiert. Wenn wir das obige Programm ausführen, erhalten wir folgende Ergebnisse:

E:\data\serge\JAVA\classes\interne>java test1
art=article(a100,velo,1000.0,10,5)

3.4. Schnittstellen

Eine Schnittstelle ist eine Sammlung von Methoden- oder Eigenschaftsprototypen, die einen Vertrag bilden. Eine Klasse, die sich entscheidet, eine Schnittstelle zu implementieren, verpflichtet sich, eine Implementierung aller in der Schnittstelle definierten Methoden bereitzustellen. Der Compiler überprüft diese Implementierung.

Hier ist ein Beispiel für die Definition der Schnittstelle *java.util.Enumeration:*

Methodenzusammenfassung
 
boolean
hasMoreElements()
          Prüft, ob diese Aufzählung weitere Elemente enthält.
 
Objekt
nextElement()
          Gibt das nächste Element dieser Aufzählung zurück, sofern dieses Aufzählungsobjekt mindestens ein weiteres Element bereithält.
 

Jede Klasse, die diese Schnittstelle implementiert, wird als

public class C : Enumeration{
    ...
    boolean hasMoreElements(){....}
    Object nextElement(){...}
}

Die Methoden hasMoreElements() und nextElement() müssen in der Klasse C definiert sein.

Betrachten Sie den folgenden Code, der eine Klasse „Student“ definiert, die den Namen eines Schülers und dessen Note in einem Fach angibt:


     // a student class
public class élève{
     // public attributes
    public String nom;
    public double note;
     // manufacturer
    public élève(String NOM, double NOTE){
        nom=NOM;
        note=NOTE;
    }//manufacturer
}//student  

Wir definieren eine Klasse „notes“, die die Noten aller Schüler in einem Fach sammelt:


// imported classes
// student import
 
// class notes
public class notes{
 
     // attributes
    protected String matière;
    protected élève[] élèves;
 
     // manufacturer
    public notes (String MATIERE, élève[] ELEVES){
         // student & subject memorization
        matière=MATIERE;
        élèves=ELEVES;
    }//notes
 
    // toString
    public String toString(){
        String valeur="matière="+matière +", notes=(";
        int i;
         // concatenate all the notes
        for (i=0;i<élèves.length-1;i++){
            valeur+="["+élèves[i].nom+","+élèves[i].note+"],";
        };
         //final note
        if(élèves.length!=0){ valeur+="["+élèves[i].nom+","+élèves[i].note+"]";}
        valeur+=")";
         // end
        return valeur;
    }//toString
}//class

Die Attribute „subject“ und „students“ werden als „protected“ deklariert, damit von einer abgeleiteten Klasse aus auf sie zugegriffen werden kann. Wir beschließen, die Klasse „notes“ in eine Klasse „notesStats“ abzuleiten, die zwei zusätzliche Attribute haben soll: den Durchschnitt und die Standardabweichung der Noten:


public class notesStats extends notes implements Istats {
     // attributes
    private double _moyenne;
    private double _écartType;

Die Klasse notesStats leitet sich von der Klasse notes ab und implementiert die folgende Istats-Schnittstelle:


// an interface
public interface Istats{
    double moyenne();
    double écartType();
}//

Das bedeutet, dass die Klasse notesStats zwei Methoden namens average und standardDeviation mit den in der Schnittstelle Istats angegebenen Signaturen haben muss. Die Klasse notesStats sieht wie folgt aus:


// imported classes
// import notes;
// import Istats;
// student import;
 
public class notesStats extends notes implements Istats {
     // attributes
    private double _moyenne;
    private double _écartType;
 
     // manufacturer
    public notesStats (String MATIERE, élève[] ELEVES){
         // parent class construction
        super(MATIERE,ELEVES);
         // average score calculation
        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;
         // standard deviation
        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;
    }//manufacturer
 
 
     // ToString
    public String toString(){
        return super.toString()+",moyenne="+_moyenne+",écart-type="+_écartType;
    }//ToString
 
     // istats interface methods
    public double moyenne(){
         // makes the average score
        return _moyenne;
    }//average
    public double écartType(){
         // makes the standard deviation
        return _écartType;
    }//écartType
}//class

Der Mittelwert _mean und die Standardabweichung _standardDev werden bei der Erstellung des Objekts berechnet. Daher geben die Methoden mean und standardDev einfach die Werte der Attribute _mean und _standardDev zurück. Beide Methoden geben -1 zurück, wenn das Array student leer ist.

Die folgende Testklasse:


// imported classes
// student import;
// import Istats;
// import notes;
// import notesStats;
 
// test class
public class test{
    public static void main(String[] args){
        // some students & notes
        élève[] ELEVES=new élève[] { new élève("paul",14),new élève("nicole",16), new élève("jacques",18)};
         // recorded in a notes object
        notes anglais=new notes("anglais",ELEVES);
         // and display
        System.out.println(""+anglais);
         // idem with mean and standard deviation
        anglais=new notesStats("anglais",ELEVES);
        System.out.println(""+anglais);
    }//hand
}//class
 

gibt folgende Ergebnisse zurück:


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

Die verschiedenen Klassen in diesem Beispiel sind alle in separaten Quelldateien enthalten:

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

Die Klasse notesStats hätte die Methoden average und standardDev durchaus selbst implementieren können, ohne anzugeben, dass sie die Schnittstelle IStats implementiert. Was ist also der Sinn von Schnittstellen? Der Punkt ist folgender: Eine Funktion kann einen Wert vom Typ I als Parameter annehmen. Jedes Objekt der Klasse C, das die Schnittstelle I implementiert, kann dann als Parameter dieser Funktion dienen. Betrachten wir die folgende Schnittstelle:


// an Iexample interface
public interface Iexemple{
    int ajouter(int i,int j);
    int soustraire(int i,int j);
}//interface

Die Schnittstelle Iexemple definiert zwei Methoden: add und subtract. Die folgenden Klassen, class1 und class2, implementieren diese Schnittstelle.


// imported classes
// import Iexample;
 
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;
    }
}//class

// imported classes
// import Iexample;
 
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;
    }
}//class

Um das Beispiel zu vereinfachen, tun die Klassen nichts anderes, als die Schnittstelle Iexample zu implementieren. Betrachten Sie nun das folgende Beispiel:


// imported classes
// import class1;
// import class2;
 
// test class
public class test{
    // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
     // the main function
    public static void main(String[] arg){
        // creation of two objects class1 and class2
        classe1 c1=new classe1();
        classe2 c2=new classe2();
        // static function calls calculate
        calculer(4,3,c1);
        calculer(14,13,c2);
    }//hand
}//test class

Die statische Funktion calculate akzeptiert ein Element vom Typ Iexample als Parameter. Sie kann daher für diesen Parameter entweder ein Objekt vom Typ class1 oder vom Typ class2 entgegennehmen. Dies geschieht in der Hauptfunktion mit folgenden Ergebnissen:

17
21
127
201

Wir sehen also, dass diese Eigenschaft dem bei Klassen beobachteten Polymorphismus ähnelt. Wenn also eine Gruppe von Klassen Ci, die nicht durch Vererbung miteinander verbunden sind (und daher keinen auf Vererbung basierenden Polymorphismus nutzen können), eine Reihe von Methoden mit derselben Signatur aufweist, kann es sinnvoll sein, diese Methoden in einer Schnittstelle I zusammenzufassen, von der alle relevanten Klassen erben würden. Instanzen dieser Klassen Ci können dann als Parameter für Funktionen verwendet werden, die einen Parameter vom Typ I akzeptieren, d. h. für Funktionen, die nur die in der Schnittstelle I definierten Methoden der Ci-Objekte nutzen und nicht die spezifischen Attribute und Methoden der verschiedenen Ci-Klassen.

Im vorherigen Beispiel war jede Klasse oder Schnittstelle Gegenstand einer separaten Quelldatei:

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

Beachten Sie schließlich, dass die Vererbung von Schnittstellen mehrfach erfolgen kann, d. h., man kann schreiben

public class classeDérivée extends classeDeBase implements i1,i2,..,in{
...
}

wobei die ij Schnittstellen sind.

3.5. Anonyme Klassen

Im vorherigen Beispiel hätten die Klassen class1 und class2 weggelassen werden können. Betrachten Sie das folgende Programm, das im Wesentlichen dasselbe tut wie das vorherige, jedoch ohne die Klassen class1 und class2 explizit zu definieren:


// imported classes
// import Iexample;
 
// test class
public class test2{
 
  // an internal class
  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;
        }
    };//definition class3
 
     // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate
 
     // the main function
    public static void main(String[] arg){
        // creation of two objects implementing the Iexemple interface
        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;
        }
    };//definition 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;
        }
    };//definition i2
        // another object Iexample
    Iexemple i3=new classe3();
 
        // static function calls calculate
        calculer(4,3,i1);
        calculer(14,13,i2);
    calculer(24,23,i3);
    }//hand
}//test class
 

Das Hauptmerkmal liegt im Code:


         // creation of two objects implementing the Iexemple interface
        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;
        }
    };//definition i1

Wir erstellen ein Objekt i1, dessen einziger Zweck darin besteht, die Schnittstelle Iexample zu implementieren. Dieses Objekt ist vom Typ Iexample. Wir können also Objekte vom Schnittstellentyp erstellen. Viele Java-Klassenmethoden geben Objekte vom Schnittstellentyp zurück, d. h. Objekte, deren einziger Zweck darin besteht, die Methoden einer Schnittstelle zu implementieren. Um das Objekt i1 zu erstellen, könnte man versucht sein, Folgendes zu schreiben:


        Iexemple i1=new Iexemple()

Eine Schnittstelle kann jedoch nicht instanziiert werden. Nur eine Klasse, die diese Schnittstelle implementiert, kann instanziiert werden. Hier definieren wir eine solche Klasse „on the fly“ innerhalb des Definitionskörpers des Objekts i1:


        Iexemple i1=new Iexemple(){
        public int ajouter(int a, int b){
            // définition de ajouter
        }
        public int soustraire(int a, int b){
            // définition de soustraire
        }
    };//définition i1

Die Bedeutung einer solchen Anweisung entspricht der folgenden Abfolge:

public class test2{
................
// an internal class
private static class classe1 implements Iexemple{
        public int ajouter(int a, int b){
            // definition of add
        }
        public int soustraire(int a, int b){
            // definition of subtract
        }
};//definition class1
.................
    public static void main(String[] arg){
...........
        Iexemple i1=new classe1();
}//hand
}//class

Im obigen Beispiel instanziieren wir tatsächlich eine Klasse und keine Schnittstelle. Eine „on the fly“ definierte Klasse wird als anonyme Klasse bezeichnet. Diese Methode wird häufig verwendet, um Objekte zu instanziieren, deren einziger Zweck darin besteht, eine Schnittstelle zu implementieren.

Die Ausführung des vorherigen Programms liefert folgende Ergebnisse:

17
21
127
201
1047
2001

Im vorherigen Beispiel wurden anonyme Klassen zur Implementierung einer Schnittstelle verwendet. Diese können auch verwendet werden, um Klassen abzuleiten, die keine parametrisierten Konstruktoren haben. Betrachten Sie das folgende Beispiel:

// imported classes
// import Iexample;

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;
    }
};//definition class3

public class test4{

     // a static function
    private static void calculer(int i, int j, Iexemple inter){
        System.out.println(inter.ajouter(i,j));
        System.out.println(inter.soustraire(i,j));
    }//calculate

   // hand method
    public static void main(String args[]){
       // definition of an anonymized class deriving from class3
     // to redefine subtract
    classe3 i1=new classe3(){
        public int ajouter(int a, int b){
          return a+b+10000;
      }//subtract
    };//i1
          // static function calls calculate
        calculer(4,3,i1);
  }//hand
}//class 

Hier haben wir eine Klasse classe3, die die Schnittstelle Iexemple implementiert. In der Funktion main definieren wir eine Variable i1 eines Typs, der von classe3 abgeleitet ist. Diese abgeleitete Klasse wird „on the fly“ in einer anonymen Klasse definiert und überschreibt die Methode ajouter der Klasse classe3. Die Syntax ist identisch mit der der anonymen Klasse, die eine Schnittstelle implementiert. Hier erkennt der Compiler jedoch, dass classe3 keine Schnittstelle, sondern eine Klasse ist. Für den Compiler handelt es sich daher um eine Klassenableitung. Alle Methoden, die im Körper der anonymen Klasse gefunden werden, überschreiben die gleichnamigen Methoden in der Basisklasse.

Die Ausführung des vorherigen Programms liefert folgende Ergebnisse:

E:\data\serge\JAVA\classes\anonyme>java test4
10007
2001

3.6. P -Pakete

3.6.1. Erstellen von Klassen in einem Paket

Um eine Zeile auf dem Bildschirm auszugeben, verwenden wir die Anweisung

System.out.println(...)

Wenn wir uns die Definition der System-Klasse ansehen, stellen wir fest, dass sie eigentlich java.lang.System heißt:

Image

Schauen wir uns das anhand eines Beispiels an:


public class test1{
    public static void main(String[] args){
        java.lang.System.out.println("Coucou");
    }//hand
}//class

Kompilieren und führen wir dieses Programm aus:

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

Warum können wir dann schreiben


        System.out.println("Coucou");

anstelle von


        java.lang.System.out.println("Coucou");

Denn standardmäßig importiert jedes Java-Programm automatisch das Paket java.lang. Es ist also so, als hätten wir am Anfang jedes Programms die folgende Anweisung:

import java.lang.*;

Was bedeutet diese Anweisung? Sie ermöglicht den Zugriff auf alle Klassen im Paket „java.lang“. Der Compiler findet dort die Datei „System.class“, die die Klasse „System“ definiert. Wir wissen noch nicht, wo der Compiler das Paket „java.lang“ findet oder wie ein Paket aussieht. Darauf kommen wir später zurück. Um eine Klasse in einem Paket zu erstellen, schreiben wir:

package paquetage;
// class definition
...

Erstellen wir für dieses Beispiel unsere zuvor behandelte Person-Klasse innerhalb eines Pakets. Wir wählen istia.st als Paketnamen. Die Person-Klasse sieht dann wie folgt aus:


// name of the package in which the person class will be created
package istia.st;
 
// class person
public class personne{
    // last name, first name, age
    private String prenom;
    private String nom;
    private int age;
  
    // builder 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+")";
    }
 
}//class
 

Diese Klasse wird kompiliert und anschließend im Verzeichnis istia\st des aktuellen Verzeichnisses abgelegt. Warum istia\st? Weil das Paket den Namen istia.st trägt.

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

Verwenden wir nun die Klasse „person“ in einer ersten 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
    }//test class

Beachten Sie, dass der Klasse „Person“ nun der Name ihres Pakets, „istia.st.“, vorangestellt ist. Wo findet der Compiler die Klasse „istia.st.Person“? Der Compiler sucht nach den benötigten Klassen in einer vordefinierten Liste von Verzeichnissen und in einer Verzeichnisstruktur, die vom aktuellen Verzeichnis ausgeht. Hier sucht er nach der Klasse istia.st.personne in einer Datei namens istia\st\personne.class. Deshalb haben wir die Datei personne.class im Verzeichnis istia\st abgelegt. Kompilieren wir das Testprogramm und führen wir es anschließend aus:

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)

Um das Schreiben zu vermeiden


            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);

können Sie die Klasse istia.st.personne mit einer Import-Anweisung importieren:


import istia.st.personne;

Wir können dann schreiben


            personne p1=new personne("Jean","Dupont",20);

und der Compiler übersetzt dies in


            istia.st.personne p1=new istia.st.personne("Jean","Dupont",20);

Das Testprogramm sieht dann wie folgt aus:


// imported namespaces
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);
    }//hand
}//test2 class

Kompilieren und führen wir dieses neue Programm aus:

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)

Wir haben das Paket „istia.st“ im aktuellen Verzeichnis abgelegt. Dies ist nicht zwingend erforderlich. Verschieben wir es in einen Ordner namens „mesClasses“, der sich ebenfalls im aktuellen Verzeichnis befindet. Zur Erinnerung: Die Klassen im Paket „istia.st“ befinden sich in einem Ordner namens „istia\st“. Die Verzeichnisstruktur des aktuellen Verzeichnisses sieht wie folgt aus:

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

Nun kompilieren wir das Programm test2.java erneut:

E:\data\serge\JAVA\classes\paquetages\personne>javac test2.java
test2.java:2: package istia.st does not exist
import istia.st.personne;

Der Compiler kann das Paket istia.st nicht mehr finden, da wir es verschoben haben. Beachten Sie, dass er aufgrund der import-Anweisung danach sucht. Standardmäßig sucht er im aktuellen Verzeichnis in einem Ordner namens istia\st, der nicht mehr existiert. Sehen wir uns die Compiler-Optionen an:

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 kann die Option -classpath nützlich sein. Damit können Sie dem Compiler mitteilen, wo er nach seinen Klassen und Paketen suchen soll. Probieren wir es aus. Kompilieren wir, indem wir dem Compiler mitteilen, dass sich das Paket istia.st nun im Ordner mesClasses befindet:

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

Diesmal verläuft die Kompilierung ohne Probleme. Führen wir das Programm test2.class aus:

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)

Nun ist es an der Java Virtual Machine, die Klasse istia/st/personne nicht zu finden. Sie sucht im aktuellen Verzeichnis danach, während sich diese nun im Verzeichnis mesClasses befindet. Sehen wir uns die Optionen der Java Virtual Machine an:

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

Wir sehen, dass die JVM ebenso wie der Compiler über eine Classpath-Option verfügt. Nutzen wir diese, um der JVM mitzuteilen, wo sich das Paket „istia.st“ befindet:

E:\data\serge\JAVA\classes\paquetages\personne>java.bat -classpath mesClasses test2
Exception in thread "main" java.lang.NoClassDefFoundError: test2

Wir sind noch nicht weit gekommen. Nun kann die Klasse test2 selbst nicht gefunden werden. Der Grund dafür ist folgender: Wenn das Schlüsselwort classpath fehlt, wird das aktuelle Verzeichnis systematisch nach Klassen durchsucht, nicht jedoch, wenn es vorhanden ist. Infolgedessen wird die Datei test2.class, die sich im aktuellen Verzeichnis befindet, nicht gefunden. Die Lösung? Fügen Sie das aktuelle Verzeichnis zum Classpath hinzu. Das aktuelle Verzeichnis wird durch das Symbol . dargestellt.

E:\data\serge\JAVA\classes\paquetages\personne>java -classpath mesClasses;. test2
p1=personne(Jean,Dupont,20)

Warum diese ganze Komplexität? Der Zweck von Paketen besteht darin, Namenskonflikte zwischen Klassen zu vermeiden. Betrachten wir zwei Unternehmen, E1 und E2, die gepackte Klassen in den Paketen com.e1 bzw. com.e2 vertreiben. Angenommen, ein Kunde C erwirbt beide Klassensätze, in denen beide Unternehmen eine Klasse namens Person definiert haben. Kunde C wird die Person-Klasse von Unternehmen E1 als com.e1.Person und die von Unternehmen E2 als com.e2.Person referenzieren und so einen Namenskonflikt vermeiden.

3.6.2. Suche nach Paketen

Wenn wir in einem Programm schreiben

import java.util.*;

um auf alle Klassen im Paket java.util zuzugreifen, wo wird dieses dann gefunden? Wir haben bereits erwähnt, dass Pakete standardmäßig im aktuellen Verzeichnis oder in der Liste der Verzeichnisse gesucht werden, die in der Option „classpath“ des Compilers oder der JVM angegeben sind, sofern diese Option vorhanden ist. Sie werden auch in den lib-Verzeichnissen des JDK-Installationsverzeichnisses gesucht. Betrachten Sie dieses Verzeichnis:

Image

In diesem Beispiel werden die Verzeichnisse jdk14\lib und jdk14\jre\lib nach .class-Dateien oder .jar- bzw. .zip-Dateien durchsucht, bei denen es sich um Klassenarchive handelt. Suchen wir beispielsweise nach .jar-Dateien, die sich im oben genannten Verzeichnis jdk14 befinden:

Image

Es gibt mehrere Dutzend davon. Eine .jar-Datei kann mit dem Dienstprogramm WinZip geöffnet werden. Öffnen wir die oben genannte Datei rt.jar (rt = RunTime). Sie enthält mehrere hundert .class-Dateien, darunter auch solche, die zum Paket java.util gehören:

Image

Eine einfache Möglichkeit, Pakete zu verwalten, besteht darin, sie im Verzeichnis <jdk>\jre\lib abzulegen, wobei <jdk> das Installationsverzeichnis des JDK ist. In der Regel enthält ein Paket mehrere Klassen, und es ist praktisch, diese in einer einzigen .jar-Datei (JAR = Java ARchive-Datei) zusammenzufassen. Die ausführbare Datei jar.exe befindet sich im Ordner <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

Hilfe zur Verwendung des Programms „jar“ erhalten Sie, indem Sie es ohne Parameter aufrufen:

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'existing archives
    -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 for jar files 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 must be spÚcifiÚs
dans l'order of ''m'' and ''f'' indicators.

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 'mymanifest'' to archive all files in the

           rÚpertoire foo/ dans 'classes.jar'':
       jar cvfm classes.jar monmanifest -C foo/ .

Kehren wir nun zur zuvor im Paket *istia.st erstellten Klasse *person.class zurück:

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

Erstellen wir eine Datei namens istia.st.jar, die alle Klassen im Paket istia.st archiviert, d. h. alle Klassen im oben gezeigten Verzeichnisbaum 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

Sehen wir uns den Inhalt der Datei „istia.st.jar“ mit WinZip an:

Image

Legen wir die Datei „istia.st.jar“ im Verzeichnis <jdk>\jre\lib\perso ab:

E:\data\serge\JAVA\classes\paquetages\personne>dir "e:\program files\jdk14\jre\lib\perso"
06/06/2002  18:08                  874 istia.st.jar

Nun kompilieren wir das Programm test2.java und führen es anschließend aus:

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)

Beachten Sie, dass wir nur den Namen des zu durchsuchenden Archivs angeben mussten, ohne dessen Speicherort explizit anzugeben. Alle Verzeichnisse im Verzeichnisbaum <jdk>\jre\lib werden durchsucht, um die angeforderte .jar-Datei zu finden.

3.7. Das Beispiel zur Steuerberechnung

Wir greifen die bereits im vorigen Kapitel behandelte Steuerberechnung wieder auf und bearbeiten sie mithilfe einer Klasse. Erinnern wir uns an die Aufgabe:

Wir betrachten den vereinfachten Fall eines Steuerpflichtigen, der nur sein Gehalt angeben muss:

  • Wir berechnen die Anzahl der Steuerklassen des Arbeitnehmers nbParts = nbEnfants/2 + 1, wenn er unverheiratet ist, und nbEnfants/2 + 2, wenn er verheiratet ist, wobei nbEnfants die Anzahl der Kinder ist.
  • Wenn er mindestens drei Kinder hat, erhält er einen zusätzlichen halben Anteil
  • Wir berechnen sein zu versteuerndes Einkommen R = 0,72 * S, wobei S sein Jahresgehalt ist
  • Wir berechnen den Familienkoeffizienten QF = R / nbParts
  • Wir berechnen seine Steuer I. Betrachten Sie die folgende Tabelle:
12620,0
0
0
13.190
0,05
631
15.640
0,1
1.290,5
24.740
0,15
2.072,5
31.810
0,2
3.309,5
39.970
0,25
4.900
48.360
0,3
6.898,5
55.790
0,35
9.316,5
92.970
0,4
12.106
127.860
0,45
16.754,5
151.250
0,50
23.147,5
172.040
0,55
30.710
195.000
0,60
39.312
0
0,65
49.062

Jede Zeile enthält 3 Felder. Um die Steuer I zu berechnen, suchen Sie die erste Zeile, in der QF <= Feld1 ist. Wenn beispielsweise QF = 23.000 ist, lautet die gefundene Zeile

    24740        0.15        2072.5

Steuer I ist dann gleich 0,15*R – 2072,5*nbParts. Wenn QF so ist, dass die Bedingung QF<=field1 nie erfüllt ist, werden die Koeffizienten aus der letzten Zeile verwendet. Hier:

    0                0.65        49062

was die Steuer I = 0,65*R - 49062*nbParts ergibt.

Die Klasse „impots“ wird wie folgt definiert:


// creation of an impots class
 
public class impots{
 
    // data required for tax calculation
     // come from an external source
 
    private double[] limites, coeffR, coeffN;
 
     // manufacturer
    public impots(double[] LIMITES, double[] COEFFR, double[] COEFFN) throws Exception{
         // check that the 3 arrays have the same size
        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+")");
        // it's good
        this.limites=LIMITES;
        this.coeffR=COEFFR;
        this.coeffN=COEFFN;
    }//manufacturer
 
     // tAX CALCULATION
    public long calculer(boolean marié, int nbEnfants, int salaire){
         // calculating the number of shares
        double nbParts;
        if (marié) nbParts=(double)nbEnfants/2+2;
        else nbParts=(double)nbEnfants/2+1;
        if (nbEnfants>=3) nbParts+=0.5;
         // calculation of taxable income & family quota
        double revenu=0.72*salaire;
        double QF=revenu/nbParts;
         // tAX CALCULATION
        limites[limites.length-1]=QF+1;
        int i=0;
        while(QF>limites[i]) i++;
        // return result
        return (long)(revenu*coeffR[i]-nbParts*coeffN[i]);
    }//calculate
}//class

Ein Steuerobjekt wird mit den Daten erstellt, die zur Berechnung der Steuer eines Steuerpflichtigen benötigt werden. Dies ist der stabile Teil des Objekts. Sobald dieses Objekt erstellt ist, kann seine Berechnungsmethode wiederholt aufgerufen werden, um die Steuer des Steuerpflichtigen auf der Grundlage seines Familienstands (verheiratet oder nicht), der Anzahl der Kinder und des Jahresgehalts zu berechnen.

Ein Testprogramm könnte wie folgt aussehen:


//imported classes
// import impots;
import java.io.*;
 
    public class test
    {
        public static void main(String[] arg) throws IOException
        {
             // interactive tax calculator
             // the user enters three data points on the keyboard: married nbEnfants salary
             // the program then displays the tax payable
 
            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";
 
             // data tables required for tax calculation
            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};
 
       // create a reading flow
      BufferedReader IN=new BufferedReader(new InputStreamReader(System.in));
            // tax object creation
            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
 
            // infinite loop
            while(true){
                 // tax calculation parameters are requested
                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();
                // anything to do?
                if(paramètres==null || paramètres.equals("")) break;
                // check the number of arguments in the input line
                String[] args=paramètres.split("\\s+");
                int nbParamètres=args.length;
                if (nbParamètres!=3){
                    System.err.println(syntaxe);
                    continue;
                }//if
                 // checking the validity of parameters
                // married
                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
                 // salary
                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
                 // parameters are correct - tax is calculated
                System.out.println("impôt="+objImpôt.calculer(marié.equals("o"),nbEnfants,salaire)+" F");
                 // next taxpayer
            }//while
        }//hand
    }//class

Hier ist ein Beispiel für das vorherige Programm in Aktion:

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 :