6. Referenzen, strukturierte Datentypen

6. Referenzen, strukturierte Datentypen

Objektvariablen enthalten Referenzen auf Objekte. Sie unterscheiden sich von Variablen primitiver Datentypen in einer Reihe von Eigenschaften:

  Variablen primitiver Datentypen Objektvariablen
Initialisierung Standardwert des Typs; direkt benutzbar Null Zeiger, Objekt muss separat erzeugt werden
Exklusivität Variable gehört exklusiv zum Objekt referenziertes Objekt gehört nicht exklusiv zum umgebenden Objekt
Lebensdauer die des umgebenden Objekts referenziertes Objekt existiert so lange es von irgendeiner Referenz referenziert wird
Ausnahmebehandlung nicht relevant Der Versuch eine Methode oder Objektvariable mit einer Referenz auf null zu benutzen führt zu einer Ausnahme
Parameterübergabe bei Methoden Kopie wird angelegt Objekt wird nicht kopiert. Nur Referenz wird kopiert

 

Neue Objekte werden im Adressraum nur mit dem new() Operator angelegt. Referenzen speichern nur die eindeutige Identität unter der das Objekt zu erreichen ist.

Die in der Referenz verwaltete eindeutige Objektidentität bleibt für die gesamte Lebensdauer des Objekts konstant. Sie ist eine logische Kennung. Das unten aufgeführte Programmbeispiel mit Diagramm ist jedoch nur als Gedankenmodell zu verstehen. Die konkrete Implementierung in einer virtuellen Java Maschine kann durchaus anders aussehen. Sie ist für den Javaentwickler transparent.

Kraftwagen wagen1 = new Kraftwagen ("0-1");
Kraftwagen wagen2 = wagen1;
Kraftwagen wagen3 = new Kraftwagen("0-2");

Das Referenzkonzept von Java ist die einzige Möglichkeit auf Objekte zuzugreifen.

Das Referenzenkonzept von Java ist syntaktisch sehr ähnlich zu dem Pointerkonzept von C und C++. Es gibt jedoch wichtige Unterschiede mit signifikanten Auswirkungen auf die Stabilität der Anwendungen:

  Java C/C++
Begriff Referenz (Reference) Zeiger (Pointer)
Implementierung abstrakter Datentyp (Klasse) Speicherbereich
direkter Speicherzugriff nein ja
Typcheck zum Übersetzungszeitpunkt ja ja (Normalerweise)
Zugriffskontrolle auf eine nicht initialisierte Referenz Check mit eventuellem Wurf einer behandelbaren Ausnahme kein Check auf Existenz oder Gültigkeit des Objekts

 

Bemerkung: Der neue C++11 Standard bietet viele Verbesserungen dieser historisch bedingten Defizite von C/C++. Da C++ oft für Anwendungen deren Leistung sehr wichtig ist (Systemprogrammierung) benutzt wird, sind alle zusätzlichen Checks von C++11 immer nur optional.

Stefan Schneider Mon, 08/23/2010 - 19:46

6.1 Die "null" und "this" Referenz

6.1 Die "null" und "this" Referenz

6.1.1 Die "null" Referenz

Java verfügt über eine "null" Konstante mit deren Wert man Referenzen belegen kann um:

  • ein Objekt zu dereferenzieren weil die Anwendungslogik dies erfordert. Das dereferenzierte Objekt wird (irgendwann) gelöscht nachdem niemand mehr auf es zeigt
  • Referenzen zu initialisieren.

Im folgenden Beispiel ist zu sehen was geschieht wenn wagen2 und wagen3 mit der null Referenz belegt werden:

package s1.block6.reference;

public class Main {

public static void main(String[] args) {
    Kraftwagen wagen1 = new Kraftwagen("0-1");
    Kraftwagen wagen2 = wagen1;
    Kraftwagen wagen3 = new Kraftwagen("0-2");
    wagen2 = null;
    wagen3 = null;
   }
}

Der Wagen"0-2" wird ein Kandidat zum Löschen. Der Wagen "0-1" wird noch über die Referenz wagen1 referenziert.

Enthält eine Referenz eine Null Variable so zeigt sie nicht mehr auf ein Objekt und man kann nicht mehr die Methoden und Datenfelder einer Instanz aufrufen. Mit static deklarierte Methoden und Attribute sind hiervon ausgenommen.

Das Javalaufzeitsystem wird in diesem Fall eine NullPointerException werfen und das Programm beenden. Das folgende Beispiel zeigt den Fall einer unbehandelten Ausnahme sowie die Behandlung einer Ausnahme:

package s1.block6.reference;

public class Main {

public static void main(String[] args) {
    Kraftwagen wagen1 = new Kraftwagen("O-1");
    Kraftwagen wagen2 = wagen1;
    Kraftwagen wagen3 = new Kraftwagen("O-2");
    wagen2 = null;
    wagen3 = null;
        
    try{
        System.out.println(wagen2.getKennzeichen()); // Erste Ausnahme
        }
    catch (NullPointerException e)
        {
            System.out.println("Variable wagen2 ist eine Null Referenz");
        }
    System.out.println(wagen3.getKennzeichen()); // Zweite Ausnahme
    //Das Programm wurde wegen der unbehandelten Ausnahme beendet
    // Die folgende Zeile nicht nicht mehr erreicht
    System.out.println("Diese Zeile des Programms wird nicht erreicht..");
    }
}

Das Programm wird bei der Ausführung die folgenden Ausgaben auf der Konsole erzeugen;

Variable wagen2 ist eine Null Referenz
Exception in thread "main" java.lang.NullPointerException
	at s1.block6.reference.Main.main(Main.java:20)

Die erste Ausnahme, der Aufruf von wagen2.getKennzeichen() wird mit mit einem try-catch Block aufgefangen. Nach der Behandlung im catch-Block wird die Methode weiter ausgeführt.

Der Aufruf von wagen3.getKennzeichen() wird nicht aufgefangen und führt zu einer Ausnahme in Zeile 20 die das Programm beendet. Ausnahmen und Ausnahmebehandlungen werden in einem späteren Abschnitt, im Kurs 2 behandelt.

6.1.2 Die Eigenreferenz mit dem Schlüsselwort this

Es gibt Fälle in denen man als Parameter einer aufzurufenden Methode einer anderen Klasse einen Zeiger auf die aktuelle Instanz, in deren Methoden man gerade arbeitet, mitgeben möchte. Hierzu dient das Schlüsselwort this. Es kann benutzt werden um auf die aktuelle Instanz zu zeigen, solange man sich nicht in einer static Methode befindet. static Methoden können auch ohne eine existierende Instanz benutzt werden. Eine Objektinstanz existiert daher nicht unbedingt.

Beispiel:

class Person {
... 
   public void sitzPlatzZuweisen (Reservierung r) {
      ...
      r.buchung(this);
      ...
   }
}
...
// Beliebige andere Klasse
Person p = new Person();
Reservierung buchung;
buchung = new Reservierung("LH454","17B"); 
// Zeitpunkt 1
p.sitzPlatzZuweisen(buchung);
// Zeitpunkt 2
...

 

Stefan Schneider Sat, 09/11/2010 - 14:44

Anonymous (not verified)

Tue, 12/18/2018 - 19:11

Müsste es ganz oben über dem ersten grauen Kasten bei der null- Referenz nicht heißen "...wenn wagen2 und wagen3 mit der null- referenz belegt werden"?

Danke, stimmt, wurde verbessert.

6.2 Heap-Größe und Freigabe von Speicher (Garbage Collection)

6.2 Heap-Größe und Freigabe von Speicher (Garbage Collection)

Die Java Objekte werden innerhalb des Javaprozesses in einem Speicherbereich mit dem Namen "Java Heap" verwaltet. In diesem Speicherbereich werden alle Datenstrukturen mit einer nicht festen Größe verwaltet. Diese Datenstrukturen sind:

  • Objekte (Instanzen von Klassen)
  • Felder von Basistypen oder Objekten
  • Zeichenketten (Sonderfall da Zeichenketten nicht mit dem new Operator angelegt werden müssen)

Dieser Bereich

  • hat eine initiale Größe
  • kann bis zu einem gegeben Limit wachsen
  • zwingt die Java Laufzeitumgebung eine OutOfMemory Ausnahme zu werfen wenn der Speicherbereich voll gelaufen ist.

Java VM Optionen

Optionen zum Heapmanagement(siehe auch Java Optionen (Referenzdokumentation)

  • initiale Heapgröße -Xms
  • maximale Heapgröße -Xmx
  • Protokollieren von Garbagekollektorläufen:-Xlog:gc 

Beispiel: Starten einer Anwendung Main.class mit 500 Megabytes initialem Heap (Freispeicher), 800 Megabytes maximalem Heap und Protokollierung von Garbagekollektorläufen:

java -Xms500m -Xmx800m -Xlog:gc Main

Nicht mehr referenzierte Objekte werden von der JavaVM automatisch, im Hintergrund von einem "Garbage Collector" gelöscht.

Dieser "Garbage Collector" (GC) kann manuell getriggert werden. Dies sollte man aber nicht in einem produktiven Programm durchführen. Der explizit angestossene GC bringt die Anwendung während seiner Arbeit zum Stehen! Automatisch ausgeführte GCs bewirken dies (normalerweise) nicht. Das explizite Anstoßen geschieht mit der statischem Methode System.gc(). Man kann sie wie folgt aufrufen:

System.gc();

Die Konsolenausgabe eines Programms welches mit der Option -Xlog:gc sieht wie folgt aus:

[GC 38835K->38906K(63936K), 0.1601889 secs]
[GC 39175K(63936K), 0.0050223 secs]
[GC 52090K->52122K(65856K), 0.1452102 secs]
[GC 65306K->65266K(79040K), 0.1433074 secs]

Sie zeigt an wieviel Speicher die Objekte von der "Garbage Collection" vorher und nachher belegen. Die benötigte Zeit wird ebenfalls angezeigt.

Verwalten von Objekten im Freispeicher (Heap)

Der Freispeicher (Heap) kann je nach Konfiguration eine konstante Größe haben oder er kann dynamisch bis zu einer vorgegebenen maximalen Größe wachsen. Der Entwickler ist daran interessiert, dass seine Anwendung alle benötigten Objekte im Freispeicher (Heap) verwalten kann. Kann das Javalaufzeitsystem keine neuen Objekte mehr anlegen wird es die Anwendung mit einer OutOfMemoryError Ausnahme beenden.

Dies wird normalerweise durch den Garbage-Collector der automatisch alle Objekte löscht die nicht mehr referenziert werden vermieden.

Nicht referenzierte Objekte

Nicht referenzierte Objekte können weder von der Anwendung noch vom Javalaufzeitsystem mit Hilfe von Referenzen erreicht werden. Dies bedeutet es gibt keine Kette von Referenzen zu einem Objekt die an den folgenden Orten beginnt:

  • von einer lokalen Referenzvariable im Systemstack
  • keine static Referenzvariable

Nicht referenzierte Objekte werden vom Javalaufzeitsystem bei Bedarf zu einem beliebigen Zeitpunkt gelöscht. Der genaue Zeitpunkt des Löschens ist nicht für den Entwickler vorhersehbar.

 

Beispiel

Im folgenden Programm werden eine Reihe von Personen erzeugt, die über eine Vater und Mutterbeziehung aufeinander referenzieren können.

 
package s1.block6.dereferenzieren;
 
public class Person {
 
    public Person vater;
    public Person mutter;
 
    public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
// Zeitpunkt 1
        p1.vater = p2;
        p2 = null;
// Zeitpunkt 2
        aufruf(p1);
        //Zeitpunkt 5
    }
 
    public static void aufruf(Person p) {
        Person[] persFeld = new Person[2];
// Zeitpunkt 3
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 4
    }
 
}

Das Hauptprogramm main() erzeugt zwei Instanzen der Klasse Person und ruft dann die Methode aufruf() auf die ein kleines Feld und einige weitere Instanzen erzeugt. 

Hinweis: Im folgenden Beispiel wird ein Feld verwendet. Javafelder werden im Detail im folgenden Kapitel erklärt.

Zum Zeitpunkt 1 sind in der main() Methode die folgenden Objekte erzeugt:

Zum Zeitpunkt 2 wurde in der main() Methode bereits die Vater-Referenz und der ursprüngliche Zeiger p2 mit null dereferenziert.

Zum Zeitpunkt 3 wurde die Methode aufruf() aufgerufen und das Feld persFeld mit zwei Feldern auf dem Heap angelegt.

Zum Zeitpunkt 4 sind drei weitere Personen angelegt worden. Die drei neuen Personen sind über den Index 0 von der Variablen persFeld erreichbar.
Zu diesem Zeitpunkt können alle Objekte auf dem Heap direkt, oder indirekt von Datenstrukturen auf dem Stapel (stack) erreicht werden.

Zum Zeitpunkt 5 wurde die Methode aufruf() bereits verlassen. Die lokalen Variablen der Methode aufruf() wurden vom Stack gelöscht und stehen nicht mehr zur Verfügung.

Hierdurch können eine Reihe von Instanzen der Klasse Person und das Feld auf dem Heap (Freispeicher) nicht mehr erreicht werden:

Die nicht mehr erreichbaren Objekte sind Müll (Garbarge) geworden und belegen verfügbaren Speicherplatz. Sie werden bei Bedarf vom Garbage-Collector (GC) gelöscht. Der Garbage-Collector wird alle Objekte löschen die vom Stack und statischen Variablen nicht mehr erreichbar sind. Dies hat für die Anwendung keine Implikationen da die Objekte auch von der Anwendung nicht mehr erreichbar sind. 

"Memory Leak"(engl. wikipedia)

Objekte die versehentlich bzw. ungewollt referenziert werden können nicht gelöscht werden. Diese Objekte können nach und nach den Heap füllen und zu einem Programmabbruch mangels Hauptspeicher (OutOfMemoryError Ausnahme) führen. Diesen Zustand der früher oder später zum ungewollten Abbruch eines Programms führen kann, nennt man im englischen "Memory Leak" (Speicherleck). Da man über die Zeit nutzbaren Speicher verliert wie ein Tank Wasser durch ein Leck verlieren kann.

Modifiziertes Beispiel (Memory Leak)

Ein Speicherleck kann durch eine minimale Änderung im vorhergehenden Beispiel entstehen. Gibt die Methode aufruf() als Ergebnis einen Zeiger auf das Feld von Personen zurück werden die drei Personen und das Feld nicht dereferenziert.

 
package s1.block6.dereferenzieren;
 
public class Person {
 
    public Person vater;
    public Person mutter;
 
    public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.vater = p2;
        p2 = null;
        Person[] f = aufruf(p1);
        //Zeitpunkt 7
    }
 
    public static Person[] aufruf(Person p) {
        Person[] persFeld = new Person[2];
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 6
return persFeld;
    }
}

 

Zum Zeitpunkt 6 sieht Objektmodell noch aus wie im vorhergehenden Beispiel:

 

 

Durch die Rückgabe der Referenz auf das Feld beim Beenden der Methode aufruf(), ist das Feld und die drei Objekte noch vom Stack erreichbar:

Die Variable f in main() referenziert das Feld. Das Feld wiederum referenziert 3 weitere Objekte.

Hier liegt ein Speicherleck nur vor, wenn der Entwickler nicht davon ausgeht, dass das Feld und alle referenzierten Objekte noch erreichbar sind. Ein Speicherleck ist kein Problem des Laufzeitsystems, da das Laufzeitsystem nicht zwischen noch benötigten und nicht mehr benötigten Objekten unterscheiden kann.

Dieses Problem wird vom Javaentwickler durch das Dereferenzieren von Objekten vermieden.

Dereferenzieren von Objekten

Der Javaentwickler muss nicht (und kann nicht) wie in anderen Programmiersprachen nicht mehr benötigte Objekte selbst löschen. Es verbleibt jedoch die Aufgabe sicherzustellen, dass nicht mehr benötigte Objekte nicht mehr referenziert werden um ein vollaufen des Heap zu vermeiden.

Implizites Dereferenzieren

Zeigt eine lokale Variable auf ein Objekt, so verschwindet die Referenz auf das Objekt mit dem Verlassen des Blocks in dem die lokale Referenzvariable definiert war.

Im der unten aufgeführten Methode warePrüfen() gibt es zwei Referenzvariablen w und w1 die auf eine Instanz einer Ware zeigen

public void warePrüfen(Ware w) {
   Ware w1 = w;
   ....
}

Beim Aufruf dieser Methode erhöht sich die Anzahl der Referenzen auf eine bestimmte Instanz der Klasse Ware um zwei Referenzen. Da die beiden Variablen aber am Ende des Blocks beim Verlassen der Methode wieder gelöscht werden erniedrigt sich die Anzahl der Referenzen auf ein gegebenes Objekt um zwei.

"Memory Leaks" entstehen daher typischerweise nicht durch lokale Variablen. Werden Methoden jedoch sehr spät verlassen (main() Methode!) werden auch die entsprechenden lokalen Referenzvariablen erst sehr spät gelöscht.

Beispiel

Die erste Variante des Beispiels in dem das Feld auf Personen nur innerhalb des Methodenblocks verwendet wurde ist ein Fall von impliziten Dereferenzieren

Explizites Dereferenzieren

Entwickler sollten Referenzvariablen auf Objekte explizit mit dem null belegen wenn Sie wissen, dass ein Objekt sicher nicht mehr benötigt wird.

o.ref = a-reference;
...
o.ref = null;

Das Derefenzieren einer Referenzvariable und den null Wert ist insbesondere wichtig wenn die Variable ein Attribut einer Klasse ist. Die Lebensdauer des Objekts o ist nicht unbedingt ersichtlich für den Entwickler. Das Setzen der Referenzvariablen o.ref auf null gewährleistet, dass das Objekt auf das mit der Variable a-reference gezeigt wird bei Bedarf gelöscht werden kann.

Beispiel

Das Speicherleck im zweiten Beispiel kann durch zwei verschiedene Änderungen vermieden werden:

1. Möglichkeit: Dereferenzieren  der Variable persFeld in der Methode aufruf()

public static Person[] aufruf(Person p) {
        Person[] persFeld = new Person[2];
        persFeld[1] = p;
        persFeld[0] = new Person();
        persFeld[0].vater = new Person();
        persFeld[0].mutter = new Person();
        // Zeitpunkt 1
        persFeld = null;
    return persFeld;
    }

2. Möglichkeit: Dereferenzieren der Variable f in der main() Methode:

public static void main (String[] args ) {
        Person p1 = new Person();
        Person p2 = new Person();
        p1.vater = p2;
        p2 = null;
        Person[] f = aufruf(p1);
        //Zeitpunkt 2
        f = null;
    }

 

 

Stefan Schneider Sat, 09/25/2010 - 15:49

6.3 Kopieren von Objekten

6.3 Kopieren von Objekten

 Objekte kopieren ist nicht trivial. Die folgende Java-Anweisung

Person p1;
Person p2;
p1 = new Person("John", "Doe");
p2=p1;

dupliziert nicht das erzeugte Objekt, es dupliziert nur die Referenz auf das gleiche Objekt. Dies bedeutet, dass man das Objekt über p1 sowie über p2 erreichen und modifizieren kann.

Das duplizieren von Objekten muss vom Entwickler explizit implementiert werden. Dies geschieht typischer in einem eigen Konstruktor dem "Copy Construktor". Der typische "Copy Constructor" wird wie folgt implementiert:

  • Der Eingabeparameter für den Konstruktor ist das zu duplizierende Objekt
  • Alle Attribute des zu kopierenden Objekts werden dupliziert

Das Vorgehen beim Kopieren der Objektvariablen obliegt dem Implementierer. Er hat hier zwei Möglichkeiten mit verschiedener Semantik:

  • Die Referenz wird umkopiert: Das geklonte Objekt zeigt auf die gleichen Objekte wie das ursprüngliche Objekt. Die referenzierten Objekte werden geteilt.
  • Das referenzierte Objekt wird ebenfalls geklont. Jetzt hat das neue Objekt einen exklusiven Satz von Objekten. Hierbei ist zu beachten, dass man bei diesem Vorgang keinen endlos rekursiven Kopiervorgang anstößt.

Das rekursive Kopieren von Objekten wird im Englischen auch als "deep copy" bezeichnet. 
Das einfache wiederbenutzen der Objekte ist im folgenden Beispiel implementiert:

public class Person {
   Person vater;
   Person mutter;
   String name;
...
   public Person (Person orig) {
      vater  = orig.vater;
      mutter = orig.mutter;
      name   = orig.name;
    }
    public Person(String n){
       name = n;
    }
}
...
Person p1 = new Person("John Doe");
p1.mutter = new Person("mum");
p1.vater = new Person("dad");
Person p2 = new Person(p1);

Das Kopieren einer Person "John Doe" ohne das Duplizieren der referenzierten Objekte führt zum folgenden Objektmodell:

Das tiefe, rekursive Klonen (Kopieren) wird wie folgt implementiert:

public class Person {
   Person vater;
   Person mutter;
   String name;
...
   public Person (Person orig) {
      if (orig.vater != null)
         vater  = new Person(orig.vater);     
      if (orig.mutter != null)
         mutter = new Person(orig.mutter);
      name   = orig.name;
   }
   public Person(String n){
      name = n;
   }
}
...
Person p1 = new Person("John Doe");
p1.mutter = new Person("mum");
p1.vater  = new Person("dad");
Person p2 = new Person(p1);

Stefan Schneider Sat, 09/11/2010 - 14:46

6.4 Vergleiche zwischen Objektvariablen

6.4 Vergleiche zwischen Objektvariablen

 In Java muss man bei Vergleichen die folgenden, unterschiedlichen Fälle unterscheiden:

  • Vergleiche von primitiven Typen
  • Vergleiche von Objekten
    • Vergleich auf Identität (gleiche Objektinstanz)
    • Vergleich auf Inhalt (enthalten zwei Instanzen die gleichen Informationen)

Vergleiche mit den == und != Operatoren

Bei primitiven Typen ist der Vergleich recht einfach

int i = 1;
int j = 2;
if (i ==j) ...

Beim Vergleich von Objekten ist hier jedoch Vorsicht geboten. Beispiel:

class Person {
   String name;
   public Person (String nameparam) { ...}
}

   ...
   Person p1 = new Person ("John Doe");
   if (p1 == "John Doe") 
   ...

 p1 ist eine Referenz auf ein Objekt. p1 wird hier direkt mit dem Zeichenliteral "John Doe" verglichen. Dieser Vergleich ist erlaubt, er führt jedoch nicht zum gewünschten Ergebnis. Die Zeichenkette "John Doe" hat einen anderen Typ und ein anderes Objekt als p1.

Vergleiche mit der equals() Methode

In Java erben alle Klassen die Methoden der Basisklasse Object. Alle Klassen erben eine Implementierung der equals() Methode von der Klasse Object.

Diese erlaubt das vergleichen von Objekten wie im folgenden Beispiel

Person p1;
Person p2;
...
if (p1.equals(p2)) ...

Die Methode equals()

  • kann auf mit null belegte Referenzen aufgerufen werden (Das Ergebnis ist immer false)
  • vergleicht zuerst auf Identität
  • vergleicht in der vererbten Implementierung der Klasse Object nur auf Identität
  • kann für jede eigene Klasse selbst mit einer komplexen Logik implementiert werden

Vorsicht: Implementiert man diese Methode selbst, dann sollte auch die Methode hashCode() selbst implementiert (überschrieben) werden. Sie wird von vielen Hilfsklassen im Zusammenhang mit equals() gleichzeitig benutzt.

Beispiel:

class Person {
   String name;
...
     public boolean equals(Object obj) {
         boolean result;
         Person target = (Person) obj;
         result = (name.equals(target.name));
     return result;
     }
}
Stefan Schneider Sat, 09/11/2010 - 14:48

6.5 Das Schlüsselwort "final" und Objektvariablen

6.5 Das Schlüsselwort "final" und Objektvariablen

 Das Schlüsselwort final erzwingt man, dass Variablen nur genau einmal belegt werden dürfen (Siehe Javaspezifikation).

Variablen dürfen dann später nicht mehr modifiziert werden.

Siehe Beispiel:

public class Person {
    ...
    final String name;
    ...
    final Adresse geburtsort;

Das Attribut name ist eine Zeichenkette. Man kann genau einmal eine Zeichenkette zuweisen. Eine spätere Änderung ist nicht mehr möglich.

Der Modifizierer final (englisch modifier) hat bei Referenztypen (hier die Klasse Adresse) eine besondere Wirkung:

  • Man kann auf genau ein Objekt referenzieren
  • Man kann nicht später auf ein anderes Objekt referenzieren
  • Man kann jedoch die Attribute des referenzierten Objekts gemäß seiner Implementierung verändern!
Stefan Schneider Sat, 09/11/2010 - 14:50

6.6 Zeichenketten (Strings)

6.6 Zeichenketten (Strings)

 Zeichenketten werden in Java mit der Klasse String verwaltet. Sie sind Objekte.

Sie wurden bisher wie die Basistypen Character, Integer oder Float behandelt. Zeichenketten werden jedoch ähnlich wie Referenzen behandelt da Zeichenketten unterschiedlich lang sein können. Man kann Zeichenketten daher nicht die Zahlentypen mit fester Größe einfach in den Speicherbereich eines Objekts einbetten.

Zeichenketten werden bei Bedarf im Hauptspeicher (Heap) angelegt und referenziert. Die Referenz auf die Zeichenkette selbst hat eine konstante Größe.

Zeichenketten können mit einer direkten Zuweisung erzeugt werden. Sie können jedoch auch gleichwertig mit dem new() Operator und dem Konstruktor der Klasse String angelegt werden. Beide Varianten sind nur in der Syntax unterschiedlich. Der einzige Unterschied besteht darin, dass das Objekt "John" schon beim Laden der Klasse angelegt wird. Das Objekt "Doe" wird erst beim Aufruf des Befehls angelegt.

String a = "John";
String b = new String("Doe");
Drei Referenzen auf zwei Zeichenketten

 a sowie b sind Objektreferenzen nicht aber der direkte Datenbehälter!

Zeichenketten(Strings) können einfach mit dem plus "+" Operator verkettet werden. Der plus Operator konvertiert automatisch andere Basistypen in Zeichenketten bevor die neue verkettete Zeichenkette zugewiesen wird:

String a = "Das Ergebnis ist ";
double pi = 3.1415;
String b = "***";
String c = a + pi + b;

Tipp: Die Klasse String verfügt über eine ganze Reihe von hilfreichen Methoden. Hier eine Auswahl:

Zeichenketten werden von der virtuellen Maschine als unveränderbare ("immutable") Objekte gehandhabt. Sie können daher nicht überschrieben oder verändert werden. Die Implikationen können am folgenden Beispiel gezeigt werden.

Schritt 1: Anlegen der Zeichenketten

String a = "John";
String b = a;
String c = " Doe";
Drei Referenzen auf zwei Zeichenketten

Schritt 2: Verkettung und Objektallokation

Eine Verkettung der Zeichenketten mit dem plus Operator ergibt das folgende Speicherabbild

c = a + c;

Die Variable c zeigt jetzt auf ein neues Objekt. Die Zeichenkette " Doe" ist eventuell nicht mehr referenziert und wird dann vielleicht irgendwann gelöscht. Bei der oben gezeigten Verkettung entstehen typischerweise sehr viele neue Objekte und es werden sehr viele Stringobjekte zum Löschen freigesetzt! 

Drei Referenzen auf zwei Zeichenketten

Vergleich

Da ein Objekt vom Typ String eine Referenz auf eine Zeichenkette ist, muss man beim Vergleich von Zeichenketten unterscheiden, ob man die Referenz oder den Inhalt der Zeichenkette vergleichen möchte um Fehler zu vermeiden.

Der Javaübersetzer und das Javalaufzeitsystem speichern Literale wenn möglich nur einmal im Hauptspeicher. Konstante Literale die man zur Übersetzungszeit erkennen kann werden in Referenzen auf ein einziges Objekt zusammengefasst. Dynamisch erzeugte Zeichenketten werden in eigenen Objekten verwaltet. Die genaue Semantik ist in der Java Sprachspezifikation (3.te Version von 2005, Paragraph 3.10.5 String Literals) beschrieben.

Wichtig: Identische Zeichenketten haben nicht unbedingt die gleiche Objektidentität. Ein Vergleich mit Hilfe der Objektidentität ist daher im Normalfall nicht ratsam.

String a = "John";
String b = a;
String c = "John";
String d = "Jo"+"hn"; // Der konstante Ausdruck 
      // wird schon bei der Übersetzung aufgelöst!
String e = new String("John");

das Speicherabbild zum Quellcode links:

4 Referenzen auf zwei Zeichenketten

Ein Vergleich mit dem == Operator vergleicht nur die Adressen der referenzierten Objekte nicht aber deren Inhalt. Zum Vergleich der Inhalte wird die .equals() Methode benötigt. Siehe

if (a == b) System.out.println(" a und b zeigen auf das gleiche Objekt");
    else System.out.println(" a und b zeigen nicht auf das gleiche Objekt");
if (a == c) System.out.println(" a und c zeigen auf das gleiche Objekt");
    else System.out.println(" a und c zeigen nicht auf das gleiche Objekt");
if (a == d) System.out.println(" a und d zeigen auf das gleiche Objekt");
    else System.out.println(" a und d zeigen nicht auf das gleiche Objekt");
if (a == e) System.out.println(" a und e zeigen auf das gleiche Objekt");
    else System.out.println(" a und e zeigen nicht auf das gleiche Objekt");
if (a.equals(b)) System.out.println(" a und b sind gleiche Zeichenketten");
    else System.out.println(" a und b sind nicht gleiche Zeichenketten");
if (a.equals(c)) System.out.println(" a und c sind gleiche Zeichenketten");
    else System.out.println(" a und c sind nicht gleiche Zeichenketten");
if (a.equals(d)) System.out.println(" a und d sind gleiche Zeichenketten");
   else System.out.println(" a und d sind nicht gleiche Zeichenketten");
if (a.equals(e)) System.out.println(" a und e sind gleiche Zeichenketten");
   else System.out.println(" a und e sind nicht gleiche Zeichenketten");

Führt zum Ergebnis:

 a und b zeigen auf das gleiche Objekt
 a und c zeigen auf das gleiche Objekt
 a und d zeigen auf das gleiche Objekt
 a und e zeigen nicht auf das gleiche Objekt
 a und b sind gleiche Zeichenketten
 a und c sind gleiche Zeichenketten
 a und d sind gleiche Zeichenketten
 a und e sind gleiche Zeichenketten

Zeichenkettenverwaltung im "Stringpool"

Der javac Übersetzer erkennt Literale schon beim Übersetzen einer Klasse und speichert sie nur einmal in der .class Datei mit dem Binärcode. Zur Laufzeit eines Javaprogramms wird die entsprechende Klasse bei der ersten Benutzung dynamisch geladen und die konstanten Zeichenketten (Literale) zu einem "Stringpool" hinzugefügt, wenn sie vorher noch nicht im Stringpool existierten. Dieses Verfahren minimiert den Speicherverbrauch und steigert den Durchsatz da Zeichenketten, in der Regel, kleine Objekte sind, die sonst den Freispeicher (Heap) dauerhaft belegen und fragmentieren.

Der Stringpool ist ein spezialisierter Bereich des Freispeichers. Objekte im allgemeinen Freispeicher und im Stringpool verhalten sich gleich. Der einzige Unterschied besteht darin, dass alle Literale im Stringpool nur genau einmal vorkommen.

Wird eine Zeichenkette dynamisch mit new String() angelegt so wird sie wie ein normales Objekt auf dem Freispeicher (Heap) angelegt und nicht im Stringpool. Die Klasse String hat jedoch eine Methode String.intern() die immer auf das kanonisierte Literal im Stringpool zeigt.

Der Zusammenhang zwischen Stringobjekten im Freispeicher wird durch das folgende Beispielprogramm deutlich:

String a = new String("Test");
String b = a.intern();
String c = new String("Test");
String d = c.intern();
String e = "Test";
if (a == c) System.out.println
   ("a,c sind die gleichen Objekte");
if (b == d) System.out.println
   ("b,d sind die gleichen Objekte"+
    " im Stringpool");
if (e == d) System.out.println
   ("e,d sind die gleichen Objekte"+
     " im Stringpool");

Ausgabe:

b,d sind die gleichen Objekte im Stringpool
d,e sind die gleichen Objekte im Stringpool
Stringobjekte und Stringpoolbeispiel

 

 

Effiziente Zeichenverwaltung mit der Klasse Stringbuffer

Die Klasse StringBuffer dient dem effizienten Handhaben von Zeichenketten. Ihre wesentlichen Eigenschaften sind:

  • "Threadsave" beim parallelen Ausführen von Threads in einem Javaprogramm werden Zeichenketten nicht versehentlich zerstört. Die Klasse ist hiermit sicher jedoch langsamer als eine nicht threadsichere Klasse (aber immer noch schneller als die Klasse String!)
  • Die Zeichenketten sind veränderbar

Wichtige Methoden Methoden der Klasse Stringbuffer sind:

Die Klasse StringBuffer verfügt über viele weitere Methoden zur Manipulation von Zeichenketten die in der API Dokumentation beschrieben werden.

Tipp: Seit JDK 5.0 steht die Klasse StringBuilder für Anwendungen die extreme Leistung benötigen zur Verfügung. Sie bietet jedoch keine Synchronisation zwischen Threads.

Stefan Schneider Mon, 10/11/2010 - 09:07

Anonymous (not verified)

Wed, 12/03/2014 - 23:53

Bitte erläutern Sie die Stelle mit "[...]dynamisch geladen[...]" sowie die Stelle mit [...]auf das kanonisierte Literal [...].

Was bedeutet dynamisch geladen? Was hat es mit dem kanonisierten Literal auf sich, also was heißt kanonisiert?

Können Sie die Methode String.intern() genauer erklären bitte?

Danke im Voraus und lG

Motivation für diese Optimierung

  • Zeichenketten wind wahrscheinlich die häufigsten Objekte auf dem Heap.
  • Sie werden wahrscheinlich oft dereferenziert.
  • Sie sind in der Regel kleine Objekte
  • Sie müssen vom Garbagekollektor identifiziert und geköscht werden

Die Kosten zum Löschen sind also in Relation zum gewonnenen Speicherplatz hoch. Man versucht ausserdem viele dieser konstanten Objekte in einem gemeinsamen Speicherbereich eng zu packen. Dadurch wird das cachen von Hauptspeicherseiten im Prozessor effizienter.

Erste Überlegung:

  • Strings sind immer nicht veränderliche Javaobjekte. Das bedeutet: Es kann mehrere Objekte geben, die den gleichen Inhalt besitzen. Java arbeitet immer mit Zeigern auf diese Objekte. Da die Objekte alle unveränderbar sind, könnten auch alle Referenzen auf das gleiche Objekt zeigen, das würde Platz sparen und Verwaltungsaufwand (gc!) sparen.

Die zweite Überlegung: Wann werden dem Laufzeitsystem Zeichenketten bekannt gemacht:

  1. Beim Programmieren im Quellcode: Hier werden viele Zeichenketten "als Konstanten" (das war eine konstantes Javaliteral) deklariert. Alle diese Zeichenketten können schon vom Übersetzer (javac) so zusammengefasst werden, dass in jeder *.class Datei jede Zeichenkette die in der Klasse verwendet wird nur noch einmal vorkommen muß. Alle Benutzer in der Klasse können auf die gleiche Zeichenkette zeigen. Beim Starten eines Javaprogramms werden alle *.class Dateien gelesen. Jetzt werden nochmals alle statischen und schon bekannten Zeichenketten in einem Stringpool konsolidert. Alle Zeichenketten die schon im Quellcode bekannt waren werden jetzt genau einmal (kanonisch) gespeichert und als Objekte angelegt.
  2. Es gibt aber auch Zeichenketten die zur Laufzeit des Programms dynamisch angelegt werden müssen. Ein Webserver muss zum Beispiel die URL "http://www.scalingbits.com/about" als Zeichenkette dynamisch anlegen können um eine entrsprechende Seite auszuliefern. Diese Zeichenketten werden (nicht unbedingt) im Stringpool verwaltet. Sie werden wie normale Objekte verwaltet. Man kann eine Kopie eines solchen Objekts aber mit der Methode .intern() in den Stringpool befördern und sich die Referenz zurückgeben lassen.

6.7 Übungen

6.7 Übungen

Duke als Boxer

6.7.1 Referenzieren, Dereferenzieren und GC

Implementieren Sie die Klasse Person mit folgenden Eigenschaften:

  • Attribute für Vater und Mutter vom Typ Person
  • Statisches Attribut zum Zählen aller erzeugten Instanzen
  • Konstruktor
    • Eingabeparameter Anzahl der Vorfahrengenerationen
    • Erzeugen der Vorfahren durch Belegen der Attribute Vater, Mutter
    • Erhöhen Sie den Personenzähler
    • Drucken Sie einen Punkt pro tausend erzeugter Objekte
    • Tipp: Der Konstruktor ist rekursiv!

Hauptprogramm mit folgenden Eigenschaften:

  • Eingabe von n Generationen
  • Eingabe ob explizite GC erfolgen soll
  • Anlegen einer Person p1 mit n Generationen
  • Anstoßen einer Garbage Collection falls gewünscht
  • Anlegen einer neuen Person auf p1 mit n Generationen
Person mit Vorfahren

 

Testen des Programms mit den Optionen -Xms -Xmx -Xlog:gc

Beispiel:

java -Xms64m -Xmx256m -Xlog:gc block7.gc.Main

Dies bewirkt das Starten einer Javaanwendung mit

  • 64 Megabyte initialem Freispeicher (-Xms64m)
  • 256 Megabyte maximalem Freispeicher (-Xmx256m)
  • Protokollieren auftretender Garbagecollections (-Xlog:gc)

Allgemeine Überlegungen:

  • Wie kann man erzwingen das die erste Person und Ihre Vorfahren zum Löschen freigegeben werden?
  • Fügen die unten aufgeführte Kontrollmethode zum Auslesen der Speicherdaten an interessanten Stellen aus.

Tipp: Benutzen die folgende Beispielmethode zum Auslesen des Speicherverbrauchs.

Aufgaben

Variieren Sie den initialen, maximalen Speicher sowie die Anzahl der Generationen um

  • Implizite GCs zu provozieren, zu vermeiden
  • Eine OutOfMemory Ausnahme zu provozieren
  • Erhöhen Sie Ihren Speicherverbrauch bis Sie an die Grenzen Ihres Systems kommen. Vorsicht! Nähern Sie sich Ihren Systemgrenzen in kleinen Schritten. Ihr System kann für eine gewisse Zeit durch Überlastung unbenutzbar werden!

Ein Testprogramm

Anbei ein einfaches Testprogramm welches eine Person ohne Vorfahren erzeugt und dann in einer Schleife neue Personen mit immer mehr Vorfahren erzeugt.

Die Konstante mit den Generationen kann bis zu einem Wert von etwa 20 erhöht werden.

package s1.block6;
public class Test {
    /**
     * Zu erzeugende Generationen
     */
    public static int generationen = 3; // Initial testen
    //public static int generationen = 19; // mit -Xlog:gc
    //public static int generationen = 22; // mit -Xmx500m -Xlog:gc
    //public static int generationen = 23; // mit -Xms500m -Xmx1024m -Xlog:gc
    public static void main(String[] args) { 
        Person p;       
        systemStatus();
        for (int i=0; i<= generationen; i++) {
            System.out.println("*** Erzeuge " + i + " Generationen ***");
            // Der alte Personenbaum wird implizit dereferenziert
            p = new Person(i);
            // Verlängern der Laufzeit. Dies erlaubt eine bessere Beobachtung mit jonsole
            // Der alte Vorfahrenbaum wird durch die Zuweisung dereferenziert
            //p = new Person(i);
            //p = new Person(i);
            systemStatus();
            System.out.println("*** Ende. Erzeuge " + i + " Generationen ***");       
        }
    }
    public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** Systemstatus ***");
        System.out.println("* Prozessoren :       " + r.availableProcessors());
        System.out.println("* Freier Speicher:    " + r.freeMemory());
        System.out.println("* Maximaler Speicher: " + r.maxMemory());
        System.out.println("* Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  Ende Systemstatus ***");
    }
}

6.7.2 Tiefes Kopieren

Benutzen Sie den Quellcode der vorhergehenden Übung.

Schreiben Sie eine Klasse mir dem Namen Adresse und den folgenden Eigenschaften

  • öffentliche Attribute ort, plz, strasse, hausnummer
  • einen öffentlichen Konstruktor der alle Attribute erfasst
  • einen "Copy Constructor" für die Klasse

Erweitern Sie die Klasse Person wie folgt:

  • Attribut wohnort vom Typ Adresse
  • "Copy Constructor" für die Klasse

Wenden Sie den "Copy Constructor" im Hauptprogramm an.

Optional

Schreiben Sie einen "Copy Constructor" der maximal n Generationen kopiert.

6.7.3 Tiefes Vergleichen

Erweitern Sie die Klassen aus den vorgehenden Beispielen wie folgt:

Klasse Person

  • Fügen Sie einen Konstruktor hinzu der Name und alle vier Parameter für einen Geburtsort erfasst
  • Implementieren eine equals() Methode die den Vergleich basierend auf Namen und den Inhalten des Geburtsort ausführt

Klasse Adresse

  • Implementieren Sie eine  equals() Methode die auf alle Attribute der Klasse prüft

Hauptprogramm

  • Entwickeln Sie Testroutinen die die unterschiedlichen möglichen Fälle abprüfen

Tipp Die equals() Methode muss aus Typisierungsgründen ein Objekt vom Typ Object übernehmen. Mit dem unten aufgeführten Codestück kann man testen ob der EIngabeparameter vom gleichen Typ wie das aktuelle Objekt ist. Ist dies nicht der Fall so können die Objekte nicht gleich sein.

public boolean equals(Object obj) {
         ...
         if (this.getClass() == obj.getClass() )

6.7.4 "Walkthrough" Beispiel

Die hier gezeigte Übung wird interaktiv in der Vorlesung entwickelt. Die Arbeitsanweisungen sind im Quellcode enthalten.

Der Ausgangspunkt ist die Klasse Ware und die Klasse Lager die als Hauptprogramm dient:

KLasse Ware mit den erzeugten Instanzen

Ziel ist es eine Lösung zu entwickeln in der die Klasse Ware mit einem Lager verwendet werden kann:

Lager und Waren in UML

Klasse Ware

package s1.block6;
/**
 * Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
 * mit Preisen und Namen in einem Lager. 
 * @author  Stefan Schneider 
 * @version 1.1
 * @see     Lager
 */
public class Ware {
    /*
     * 7. Anlegen eine Copy Constructor
     * 7.1 Alle Werte des Objekts werden kopiert
     * 7.2 Es wird bei Bedarf eine neue Empfehlung angelegt
     */
    /**
     * Der aktuelle Mehrwertsteuersatz 2010.
     * Er liegt zur Zeit bei {@value}.
     * @since 1.0
     * @version 1.0
     */
    public static final double mws = 0.19;
    private double nettoPreis; //Deklaration
    public boolean halbeMws;
    private String name;
    public Ware empfehlung;
    /**
     * Konstruktor fuer die Klasse Ware
     * @param n der Name der Ware
     * @param np der Nettorpreis
     * @param hmws halber Mehrwertsteuersatz für Ware gueltig
     */
    public Ware(String n, double np, boolean hmws) {
        name = n;
        nettoPreis = np;
        halbeMws = hmws;
    }
    /**
     * Liefert den Namen einer Ware zurueck.
     * @return    Name der Ware
     */
    public String get_name() {
        return name;    }
    /**     * Setzen eines neuen Nettopreis
     * @param npr   der neue Nettopreis
     */
    public void set_nettoPreis(double npr) {
        nettoPreis = npr;
    }
    /**
     * Ausdrucken aller Werte auf der Konsole
     */
    public void drucken() { drucken(0); }
    /**
     * Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
     * für Empfehlungen
     * @param einruecken eingerueckte Stellen für Empfehlungen
     */    
    private void drucken(int einruecken) {
        String leerStellen = "";
        for (int i = 0; i < einruecken; i++) {
            leerStellen = leerStellen + " ";
        }
        System.out.println(leerStellen + "Name: " + name);
        System.out.println(leerStellen + "netto: " + nettoPreis);
        System.out.println(leerStellen + "Brutto: " + bruttoPreis());
        System.out.println(leerStellen + "Halbe Mws:" + halbeMws);
        if (empfehlung != null) { // Empfohlene Bücher werden eingerückt
            empfehlung.drucken(einruecken + 2);
        }
    }
    /**
     * Ausgabe des Nettopreis
     * @return der Nettopreis
     */
    public double nettoPreis() {
        return nettoPreis;    }
    /**
     * Ausgabe des Bruttopreis
     * @return der Bruttopreis
     */
    public double bruttoPreis() {
        double bp; //temporaere Variable; keine Klassenvariable
        if (halbeMws) {
            bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
        } else {
            bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
        }
        return bp;
    }
}

Klasse Lager 

package s1.block6;
/** * Die Klasse Lager verwaltet eine bestimmte Anzahl von Waren 
 * @author s@scalingbits.com
 */
public class Lager {
    /*     * Aufgaben
     * 1. Verwalten von n Waren in einem Feld
     * 1.1 Deklarieren eines privaten Feldes
     * 1.2 Zugriffsmethoden zum Setzen und Auslesen des Feldes
     * 2. Implementieren eines Konstruktors der das Lager
     *           für n Waren initialisiert
     * 3. Methode zum Ausräumen des Lagers
     * 4. Erzeugung eines Singletons zum Erzeugen genau eines Lagers
     * 5. Anlegen einer neuen Klasse MainLager
     * 5.1 Umkopieren der main() Methode aus der Klasse Lager in die Klasse
     *      MainLager.main()
     * 8. Testen des Copy Constructors
     * 8.1 Belegen Sie die ersten 500 Lagerpositionen mit Waren
     * 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
     *     folgenden 500 Lagerpositionen mit Ihnen
     * 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
     * 8.4 Implementieren Sie eine Schleife die einige Minuten läuft
     *      und testen Sie den Speicherverbrauch mit jconsole oder
     */
     public static void main(String[] args) {
        Ware ware1 = new Ware("Zeitung",12.12,true);
        System.out.println("Ware1 ohne Empfehlung:");
        ware1.drucken();
        double p = ware1.nettoPreis();
        Ware ware2 = new Ware("Potter Bd1",31.12,false);
        Ware ersterBand = ware2;
        ware1.empfehlung= ware2;
        System.out.println("Ware1 mit Empfehlung:");
        ware1.drucken();
        // Erzeugen einer Ware mit 7 verketteten Empfehlungen
        Ware ware3;
        Ware hp1=ware2; 
        for (int i=2; i<= 7; i++) {
            ware3 = new Ware("Potter Bd" + i,31.25+i,false);
            ware2.empfehlung = ware3;
            ware2 = ware3;
        }
        System.out.println("Alle Harry Potter Baende drucken");
        hp1.drucken();
 }
}

Klasse MainLager

package s1.block6;
/**
 * * Eine Hilfsklasse zur Implementierung eines Hauptprogramms
 *
 * @author sschneid
 * @version 1.0
 */
public class MainLager {
    /*
     * 6. Testen der Klasse Main 
     * 6.1 Aufruf des Singleton der Klasse Lager 
     * 6.2 Einfügen zweier Waren 
     * 6.3 Drucken zweier Waren
     */
    public static void main(String[] args) {
        Ware ware1 = new Ware("Zeitung", 12.12, true);
        ware1.drucken();
        double p = ware1.nettoPreis();
        Ware ware2 = new Ware("Potter Band 1", 31.12, false);
        Ware ersterBand = ware2;
        Ware ware3;
        for (int i = 2; i <= 7; i++) {
            ware3 = new Ware("Potter Band" + i, 31.12, false);
            ware2.empfehlung = ware3;
            ware2 = ware3;
        }
    }
}

 

Stefan Schneider Sat, 09/25/2010 - 18:17

6.8 Lösungen

6.8 Lösungen

6.8.1 Referenzieren, Derefenzieren und GC

Klasse Main

Das folgende Programm wird wie folgt gestartet

java s1.block6.Main 3 gc

Es erwartet zwei Kommandozeilenargumente. Das erste Argument beschreibt die Anzahl der zu erzeugenden Generationen.

Das zweite Argument is optional. Steht hier GC wird eine System Garbarge Collection angestoßen

package block6.gc;

public class Main {

    /**
    * Auslesen von Systemparametern
    */
    public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** System Status ***");
        System.out.println("Prozessoren :       " + r.availableProcessors());
        System.out.println("Freier Speicher:    " + r.freeMemory());
        System.out.println("Maximaler Speicher: " + r.maxMemory());
        System.out.println("Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  ***");
    }

    public static void main(String[] args) {
        int vorfahren = 0;
        boolean systemCollection=false;
        Person p1;

        systemStatus();
        // Parsen des ersten Arguments
        // Diese Zeichenkette enthält die ANzahl der Generationen
        if (args.length > 0 ) { // Gibt es ein erstes Argument?
        try { vorfahren = Integer.parseInt(args[0]);}
        catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
        // Auslesen des zweiten Arguments
        // Steht hier die Zeichenkette GC in Klein- oder Grossschreibung
        if (args.length > 1 ) // Gibt es ein zweites Argument
            systemCollection = ((args[1].equalsIgnoreCase("gc"));

        p1 = new Person(vorfahren);
        //p1 = null;
        System.out.println();
        System.out.println("Erster Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
        if (systemCollection)  {
            System.out.println("Starte System GC");
            System.gc();
        }
        p1 = new Person(vorfahren);
        System.out.println();
        System.out.println("Zweiter Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
    }

}

Klasse Person

package s1.block6;
public class Person {
    Person vater;
    Person mutter;
    static int zaehler=0;

    public Person (int vorfahren)
    {
        if ( vorfahren>0) {
            vater = new Person(vorfahren-1);
            mutter = new Person(vorfahren-1);
        }
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");

    }
}

6.8.2 "Copy Constructor" Klasse

Klasse Main

package s1.block6;

public class Main {

    public static void systemStatus(){
        Runtime r = Runtime.getRuntime();
        System.out.println("*** System Status ***");
        System.out.println("Prozessoren :       " + r.availableProcessors());
        System.out.println("Freier Speicher:    " + r.freeMemory());
        System.out.println("Maximaler Speicher: " + r.maxMemory());
        System.out.println("Gesamter Speicher:  " + r.totalMemory());
        System.out.println("***  ***");
    }

    public static void main(String[] args) {
        int vorfahren = 0;
        boolean systemCollection=false;
        Person p1;

        systemStatus();
        if (args.length > 0 ) {
        try { vorfahren   = Integer.parseInt(args[0]);}
        catch (NumberFormatException e) {
            System.err.println("Argument muss Ganzzahl sein");
            System.exit(1);
            }
        }
        if (args.length > 1 )
            systemCollection = (args[1].equalsIgnoreCase("gc"));

        p1 = new Person(vorfahren);
        //p1 = null;
        System.out.println();
        System.out.println("Erster Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
        if (systemCollection)  {
            System.out.println("Starte System GC");
            System.gc();
        }
        p1 = new Person(p1);
        System.out.println();
        System.out.println("Zweiter Schritt erledigt: " + Person.zaehler +
                " Instanzen erzeugt");
        systemStatus();
    }

}

Klasse Adresse

package s1.block6;

public class Adresse {
    public String ort;
    public String plz;
    public String strasse;
    public String hausnr;

    public Adresse (String o, String p, String s, String h){
        ort     = o;
        plz     = p;
        strasse = s;
        hausnr  = h;
    }

    public Adresse (Adresse orig) {
        ort     = orig.ort;
        plz     = orig.plz;
        strasse = orig.strasse;
        hausnr  = orig.hausnr;
    }
}

Klasse Person

package s1.block6;
public class Person {
    Person vater;
    Person mutter;
    String name;
    Adresse wohnort;
    static int zaehler=0;

    public Person (int vorfahren)
    {
        if ( vorfahren>0) {
            vater  = new Person(vorfahren-1);
            mutter = new Person(vorfahren-1);
        }
        wohnort = new Adresse("Berlin", "10117","Platz der Republik","1");
        name = "John Doe";
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
    }

    public Person (Person orig) {
         if (orig.vater != null) vater  = new Person(orig.vater);
         if (orig.mutter != null) mutter = new Person(orig.mutter);
         if (orig.wohnort != null) wohnort = new Adresse(orig.wohnort);
         name   = orig.name;
         zaehler++;
         if (zaehler%1000 == 0) System.out.print(".");
    }
}

Optional: "Copy Constructor mit begrenzter Kopiertiefe

    public Person (Person orig, int vorfahren) {
        if (vorfahren > 0) {
            if (orig.vater != null)
                vater  = new Person(orig.vater,  vorfahren-1);
            if (orig.mutter != null)
                mutter = new Person(orig.mutter, vorfahren-1);
            }
        if (orig.wohnort != null) wohnort = new Adresse(orig.wohnort);
        name   = orig.name;
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
    }

6.8.3 Tiefes Vergleichen Klasse Person

Klasse Person

class Person {
...
    public Person (String name1, String gort, String gplz,
            String gstrasse, String gnr){
        name = name1;
        geburtsort = new Adresse(gort,gplz,gstrasse,gnr);
        zaehler++;
        if (zaehler%1000 == 0) System.out.print(".");
   }
...
 public boolean equals(Object obj) {
         Person target;
         // Type checking is optional for this level of skills
         if (this.getClass() == obj.getClass() ) {
            target = (Person) obj;                
            return (name.equals(target.name) && 
                      (geburtsort.equals(target.geburtsort)));
            }
         else return false;
     }
...
}

Klasse Adresse

public boolean equals(Object obj) {
         Adresse target;
         // Type checking is optional for this level of skills
         if (this.getClass() == obj.getClass() ) {
            target = (Adresse) obj;                
            return (ort.equals(target.ort) && 
                plz.equals(target.plz) &&
                strasse.equals(target.strasse) &&
                hausnr.equals(target.hausnr));
            }
         else return false;
}

6.8.4 Ware/Lager Beispiel

Klasse MainLager

package s1.block6;
/**
* * Eine Hilfsklasse zur Implementierung eines Hauptprogramms
*
* @author s@scalingbits.com
* @version 1.1
*/
public class MainLager {
/*
* 5. Anlegen einer neuen Klasse MainLager * 5.1 Umkopieren der main() Methode
* aus der Klasse Lager in die Klasse MainLager.main()
*/
/*
* 8. Testen des Copy Constructors
* 8.1 Belegen Sie die ersten 500
* Lagerpositionen mit Waren
* 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
* folgenden 500 Lagerpositionen mit Ihnen
* 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
* 8.4 Implementieren Sie eine Schleife die einige Minuten läuft und testen Sie
* den Speicherverbrauch mit jconsole und jps
*/

public static void main(String[] args) {
Lager lager1 = Lager.dasLager();
int position;
for (int j = 0; j < 100000; j++) {
for (int i = 0; i < 500; i++) {
position = lager1.einlagern(new Ware("Buch der Zahl " + i, 2 * i, false));
//System.out.println("Ware auf Position" + position + " eingelagert");
}
for (int i = 0; i < 500; i++) {
position = lager1.einlagern(new Ware(lager1.holen(i)));
//System.out.println("Ware auf Position" + position + " eingelagert");
}
lager1.ausraeumen();

System.out.println("Schleife " + j + " von 100000");
}
}
}

Klasse Ware

package s1.block6.loesung;
/**
* Dies ist die Dokumentation der Klasse Ware. Ware dient zum Verwalten von Gütern
* mit Preisen und Namen in einem Lager.
* @author Stefan Schneider
* @version 1.1
* @see Lager
*/
public class Ware {
/**
* Der aktuelle Mehrwertsteuersatz 2010.
* Er liegt zur Zeit bei {@value}.
*/
public static final double mws = 0.19;
private double nettoPreis; //Deklaration
public boolean halbeMws;
private String name;
public Ware empfehlung;
/**
* Konstruktor fuer die Klasse Ware
* @param n der Name der Ware
* @param np der Nettorpreis
* @param hmws halber Mehrwertsteuersatz für Ware gueltig
*/
public Ware(String n, double np, boolean hmws) {
name = n;
nettoPreis = np;
halbeMws = hmws;
}
/*
* 7. Anlegen eine Copy Constructor
* 7.1 Alle Werte des Objekts werden kopiert
* 7.2 Es wird bei Bedarf eine neue Empfehlung angelegt
*/
/**
* Copy-Konstruktor fuer die Klasse Ware
* @param w die zu klonende Ware
*/
public Ware(Ware w) {
name = w.name;
nettoPreis = w.nettoPreis;
halbeMws = w.halbeMws;
if (w.empfehlung != null)
empfehlung = new Ware(w.empfehlung);
}
/**
* Liefert den Namen einer Ware zurueck.
* @return Name der Ware
*/ public String get_name() {
return name; }
/** * Setzen eines neuen Nettopreis
* @param npr der neue Nettopreis
*/
public void set_nettoPreis(double npr) {
nettoPreis = npr;
}
/**
* Ausdrucken aller Werte auf der Konsole
*/
public void drucken() {
System.out.println("Name: " + name);
System.out.println("netto: " + nettoPreis);
System.out.println("Brutto: " + bruttoPreis());
System.out.println("Halbe Mws:" + halbeMws);
if (empfehlung != null) {
empfehlung.drucken(2);
}
}
/**
* Ausdrucken aller Werte auf der Konsole mit vorgebener Einrueckung
* für Empfehlungen
* @param einruecken eingerueckte Stellen für Empfehlungen
*/ public void drucken(int einruecken) {
String leerStellen = "";
for (int i = 0; i < einruecken; i++) {
leerStellen = leerStellen + " ";
}
System.out.println(leerStellen + "Name: " + name);
System.out.println(leerStellen + "netto: " + nettoPreis);
System.out.println(leerStellen + "Brutto: " + bruttoPreis());
System.out.println(leerStellen + "Halbe Mws:" + halbeMws);
if (empfehlung != null) {
empfehlung.drucken(einruecken + 2);
}
}
/**
* Ausgabe des Nettopreis
* @return der Nettopreis
*/ public double nettoPreis() {
return nettoPreis; }
/**
* Ausgabe des Bruttopreis
* @return der Bruttopreis
*/ public double bruttoPreis() {
double bp; //temporaere Variable; keine Klassenvariable
if (halbeMws) {
bp = Math.round(nettoPreis * (mws / 2 + 1) * 100) / 100;
} else {
bp = Math.round(nettoPreis * (mws + 1) * 100) / 100;
}
return bp;
}
}

Klasse Lager

package s1.block6;
/** * Die Klasse Lager verwaltet eine bestimmte Anzahl von Waren
* @author s@scalingbits.com
* @version 1.1
*/
public class Lager {
/*
* Aufgaben
* 1. Verwalten von n Waren in einem Feld
* 1.1 Deklarieren eines privaten Feldes
*/
private int lagerGroesse = 1000;
private Ware[] warenLager;

// 1.2 Zugriffsmethoden zum Setzen und Auslesen des Feldes
public Ware holen(int i) {
if ((i<0) || (i>=lagerGroesse)) return null;
else return warenLager[i];
} public int einlagern(Ware w) { int i=0; while ((i<lagerGroesse) && (warenLager[i] != null)) i++; if (i<lagerGroesse) { warenLager[i] = w; return i; } else return -1; } // 3. Methode zum Ausräumen des Lagers public void ausraeumen() { for (int i=0; i<lagerGroesse; i++) warenLager[i]=null; }

/*
* 2. Implementieren eines Konstruktors der das Lager
* für n Waren initialisiert
*/
private Lager() {
// Es wird ein Feld von Referenzen auf Waren angelegt
// Es werden keine Waren angelegt!
warenLager = new Ware[lagerGroesse];
}
/*
* 4. Erzeugung eines Singletons zum Erzeugen genau eines Lagers
*/
private static Lager meinLager; // Eine Objektvariable die auf das Lager zeigt
static public Lager dasLager() {
if (meinLager== null) meinLager= new Lager();
return meinLager;
}
/*
* 5. Anlegen einer neuen Klasse MainLager
* 5.1 Umkopieren der main() Methode aus der Klasse Lager in die Klasse
* MainLager.main()
*/
// 6. Testen der Factory
/*Lager lager1 = Lager.dasLager();
int position;
for (int i=0; i<500; i++)
position = lager1.einlagern(new Ware("Buch " +i, 2*i, false));
for (int i=0; i<500; i++) {
Ware w = lager1.holen(i);
System.out.println("Position " + i);
w.drucken();
}
*/
/*
* 8. Testen des Copy Constructors
* 8.1 Belegen Sie die ersten 500 Lagerpositionen mit Waren
* 8.2 Klonen Sie die ersten 500 Lagerpositionen und belegen Sie die
* folgenden 500 Lagerpositionen mit Ihnen
* 8.3 Löschen Sie Ihr Lager indem Sie alle Postionen mit null belegen
* 8.4 Implementieren Sie eine Schleife die einige Minuten läuft
* und testen Sie den Speicherverbrauch mit jconsole oder
*/
public static void main(String[] args) {
Ware ware1 = new Ware("Zeitung",12.12,true);
System.out.println("Ware1 ohne Empfehlung:");
ware1.drucken();
double p = ware1.nettoPreis();
Ware ware2 = new Ware("Potter Bd1",31.12,false);
Ware ersterBand = ware2;
ware1.empfehlung= ware2;
System.out.println("Ware1 mit Empfehlung:");
ware1.drucken();
// Erzeugen einer Ware mit 10 verketteten Empfehlungen
Ware ware3;
Ware hp1=ware2;
for (int i=2; i<= 7; i++) {
ware3 = new Ware("Potter Bd" + i,31.25+i,false);
ware2.empfehlung = ware3;
ware2 = ware3;
}
System.out.println("Alle Harry Potter Baende drucken");
hp1.drucken();
// ersterBand.drucken();
}
}

 

Stefan Schneider Sat, 09/25/2010 - 18:22

6.9 Lernziele

6.9 Lernziele

Am Ende dieses Blocks können Sie:

  • .... den Unterschied zwischen Variablen primitive Datentypen und Objektvariablen erkennen, erklären und anwenden
  • ... die "null" Referenz anwenden um den Hauptspeicherverbrauch (Java-Heap) zu managen
  • ... Beispiele für die Verwendung von Eigenreferenzen (this Referenz) nennen
  • ... die this Referenz nutzen und erklären
  • ... die wichtigsten Optionen des Kommando java zur Konfiguration des Java-Heap nennen und anwenden
  • ... Objekte identifizieren die in einer Anwendung nicht mehr referenziert werden
  • ... erkennen ob Objekte oder nur die Referenzen auf die Objekte kopiert werden
  • ... den Unterschied zwischen dem == bzw. != Operator und der equals() Methode beim Vergleichen von Objekten erklären und anwenden
  • ...die Besonderheiten des final Modifizierer bei Objektvariablen erklären
  • ...mit der Javaklasse String umgehen, kennen die wichtigsten Methoden und können deren Verwaltung im Speicher erklären

Lernzielkontrolle

Sie sind in der Lage die folgenden Fragen zu Referenzen zu beantworten.

Feedback

Zur Umfrage

QR Code für Umfrage

Stefan Schneider Thu, 09/27/2012 - 10:11