Zurück   Weiter

6 Objekte und ihre Instanzen

In Java wird nicht zwischen dem Objektinhalt und dem Zeiger auf die Speicheraddresse unterschieden. Es gibt nur Verweise auf Objekte. Verweise besitzen als Datentyp ein Objekt und zeigen auf einen Speicherbreich, in dem ein solches Objekt abgelegt werden kann.

  objektDatenTyp verweisName;

6.1 null

Mit der Anweisung

  Point punkt1;

wird erst der Objekttyp von punkt1 festgelegt. Er legt fest, dass an punkt1 nur Objekte des Typs Point zugewiesen werden können. Es wird noch keine Instanz der Klasse Point angelegt. Zu diesem Zeitpunkt wird für punkt1 noch kein Speicherplatz reserviert. Die Speicheradresse ist noch unbestimmt. Eine Variable, deren Speicheradresse unbestimmt ist, erhält den Wert null. null ist ein vordefinierter Wert, den man in bedingten Anweisungen auch abfragen kann.

  package packageName;

  class KlassenName extends KlassenName_der_SuperKlasse {
    ...

    KlassenName(Parameterliste) {
      ...
    }

    private returnTyp methodenName(ParameterListe) {
      Objekt variablenName = getObject();
      if(variablenName == null) {
        ...
      }
      ...
    }
    ...
  }

6.2 Instantiierung von Variablen

Mit dem Aufruf

  punkt1 = new Point(1, 1);

wird eine neue Instanz der Klasse Point erzeugt. Diese Instantiierung von punkt1 bewirkt, dass im Speicher des Computers der für dieses Objekt erforderliche Speicherbereich reserviert wird. punkt1 verweist auf den Anfang dieses Speicherbereichs. Ich kann diese Adresse nicht, wie in anderen Programmiersprachen zum Teil möglich, beeinflussen. Die Startadresse und die Grösse des Speichers werden von der JVM festgelegt.

6.3 Mehrere Instanzen eines Objekts

Was passiert wenn wir mehrere Instanzen der gleichen Klasse erzeugt haben und diese Instanzen einander zuweisen. Hierzu erzeugen wir eine weitere Instanz der Klasse Point. Wir verwenden hierzu die Klasse Point aus dem package java.awt. In dieser Klasse sind die Membervariablen x und y als public deklariert.

  Point punkt2 = new Point(2, 2);

nach dieser Anweisung haben wir folgendes Bild:


instances1

Jetzt wollen wir versuchen punkt2 auf die gleiche Stelle wie punkt1 zu verschieben. Hierzu weisen wir der Variablen punkt2 punkt1 zu.

  punkt2 = punkt1;

In einem kleinen Beispielprogramm geben wir nun das Ergebnis auf der Console aus.

import java.awt.Point;

public class PointProgram1 {
  public static void main(String[] args) {
    Point punkt1 = new Point(1, 1);
    Point punkt2 = new Point(2, 2);
    punkt2 = punkt1;
    System.out.println("punkt1.x = " + punkt1.x + ", punkt1.y = " + punkt1.y);
    System.out.println("punkt2.x = " + punkt2.x + ", punkt2.y = " + punkt2.y);
  }
}
Sourcefile

Wir erhalten wie erwartet das Ergebnis:


  punkt1.x = 1, punkt1.y = 1
  punkt2.x = 1, punkt2.y = 1

Danach verschieben wir die x-Position des Punktes punkt2 an die Stelle 5 und schauen uns das Ergebnis an.

import java.awt.Point;

public class PointProgram2 {
  public static void main(String[] args) {
    Point punkt1 = new Point(1, 1);
    Point punkt2 = new Point(2, 2);
    punkt2 = punkt1;
    punkt2.x = 5;
    System.out.println("punkt1.x = " + punkt1.x + ", punkt1.y = " + punkt1.y);
    System.out.println("punkt2.x = " + punkt2.x + ", punkt2.y = " + punkt2.y);
  }
}
Sourcefile

Das nun erhaltene Ergebnis kann überraschend scheinen:


  punkt1.x = 5, punkt1.y = 1
  punkt2.x = 5, punkt2.y = 1

Betrachtet man den Speicher des Computers, so wird es klar:

instances2

Mit der Zuweisung von punkt1 an punkt2 haben wir nicht die Inhalte von punkt1 an die Inhalte von punkt2 weitergegeben. Wir haben die Speicheradresse auf die der Verweis punkt1 zeigt, an den Verweis punkt2 weitergegeben. Die Speicheradresse, auf die der Verweis punkt2 ursprünglich zeigte, ist verlorengegangen. Wir haben jetzt nur noch eine Instanz der Klasse Point, aber zwei Verweise darauf. Möchten wir die Inhalte von punkt1 an punkt2 weitergeben, so müssen wir die einzelnen Elemente zuweisen.

import java.awt.Point;

public class PointProgram3 {
  public static void main(String[] args) {
    Point punkt1 = new Point(1, 1);
    Point punkt2 = new Point(2, 2);
    punkt2.x = punkt1.x;
    punkt2.y = punkt1.y;
    System.out.println("punkt1.x = " + punkt1.x + ", punkt1.y = " + punkt1.y);
    System.out.println("punkt2.x = " + punkt2.x + ", punkt2.y = " + punkt2.y);
    punkt2.x = 5;
    System.out.println("punkt1.x = " + punkt1.x + ", punkt1.y = " + punkt1.y);
    System.out.println("punkt2.x = " + punkt2.x + ", punkt2.y = " + punkt2.y);
  }
}
Sourcefile

Jetzt erhalten wir das gewünschte Ergebnis, was im Speicher so aussieht:

instances3

6.4 Verweise im Methodenaufruf

Eine besondere Bedeutung kommt den Verweisen bei der Übergabe von Instanzen an Methoden zu. Wird innerhalb einer Methode die Instanz verändert, so besitzt diese Veränderung auch nach durchlaufen der Methode ihre Gültigkeit.

import java.awt.Point;

public class PointProgram4 {
  public PointProgram4() {
    Point p = new Point(1, 1);
    System.out.println("p.x = " + p.x + ", p.y = " + p.y);
    changePoint(p);
    System.out.println("p.x = " + p.x + ", p.y = " + p.y);
  }

  private void changePoint(Point p) {
    p.x = 4;
    p.y = 4;
  }

  public static void main(String[] argv) {
    new PointProgram4();
  }
}
Sourcefile

Wird dagegen innerhalb der Methode der Variablen eine neue Instanz der Klasse zugewiesen, so ist diese Instanz nur innerhalb der Methode gültig. Die dem Aufruf übergebene Instanz wird nach der Erzeugung der neuen Instanz nicht mehr verändert.

import java.awt.Point;

public class PointProgram5 {
  public PointProgram5() {
    Point p = new Point(1, 1);
    System.out.println("<init.1>: p.x = " + p.x + ", p.y = " + p.y);
    makeNewPoint(p);
    System.out.println("<init.2>: p.x = " + p.x + ", p.y = " + p.y);
  }

  private void makeNewPoint(Point p) {
    p.x = 5;
    p.y = 5;
    System.out.println("makeNewPoint.1: p.x = " + p.x + ", p.y = " + p.y);
    p = new Point(2, 2);
    System.out.println("makeNewPoint.2: p.x = " + p.x + ", p.y = " + p.y);
  }

  public static void main(String[] argv) {
    PointProgram5 program = new PointProgram5();
  }
}
Sourcefile

Auch dieses Ergebnis ist nicht unbedingt ganz wie erwartet:


<init.1>: p.x = 1, p.y = 1
makeNewPoint.1: p.x = 5, p.y = 5
makeNewPoint.2: p.x = 2, p.y = 2
<init.2>: p.x = 5, p.y = 5

Wir betrachten uns den Speicher des Computers:

instances4

Zuerst wird im Konstruktor der Klasse PointProgram5 eine Instanz der Klasse java.awt.Point erzeugt und an eine Variable mit dem Namen p zugewiesen (schwarze Pfeile). Diese Instanz wird an die Methode makeNewInstace übergeben und dort als Variable, ebenfalls, mit dem Namen p verwendet. Nun wird dem x- und y-Wert eine neuer Wert zugewiesen, was auch dem erwarteten Ergebnis entspricht (grün). Danach wird der Variablen p der Methode makeNewInstance eine neue Instanz der Klasse java.awt.Point zugewiesen (rot). Die Variable p des Konstruktors wird davon nicht betroffen, sie zeigt weiterhin auf die ihr zugewiesene Speicheradresse.

6.5 Zuweisungskompatibilität

Damit man Variablen, welche auf ein Objekt verweisen, einander zuweisen kann, müssen die Objekte, auf die sie verweisen kompatibel sein. Schon beim Compilieren muss feststellbar sein, ob diese Kompatibilität vorhanden ist. Die Zuweisung

  a = b;

ist erlaubt, wenn
1. a und b auf die gleiche Klasse verweisen

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Klasse_1 a;
      ...
      Klasse_1 b = new Klasse_1();
      ...
      a = b;
      ...
    }

    ...
  }

2. b auf eine Subklasse von a verweist

  class Klasse_1 {
    ...
  }

  class Klasse_2 extends Klasse_1 {
    ...
  }

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Klasse_1 a;
      ...
      Klasse_2 b = new Klasse_2();
      ...
      a = b;
      ...
    }

    ...
  }

3. a auf die Klasse Object verweist und b ein Array ist

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Object a;
      ...
      int[] b = new int[5];
      ...
      a = b;
      ...
      Klasse_1[] c = new Klasse_1[11];
      ...
      a = c;
    }

    ...
  }

4. a und b auf ein Array mit gleichem primitiven Datentyp verweisen

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      int[] a;
      ...
      int[] b = new int[5];
      ...
      a = b;
      ...
    }

    ...
  }

5. a und b auf ein Array verweisen für deren Datentypen Bedingung 1 oder 2 gilt

  class Klasse_1 {
    ...
  }

  class Klasse_2 extends Klasse_1 {
    ...
  }

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Klasse_1[] a;
      ...
      Klasse_1[] b = new Klasse_1[17];
      ...
      a = b;
      ...
      Klasse_2[] c = new Klasse_2[10];
      ...
      a = c;
    }

    ...
  }

6.6 Cast-Konstrukte

Wie wir es schon bei den primitiven Datentypen kennengelernt haben, so ist auch bei den Verweisen auf Objekten ein cast erlaubt. Wie auch beim cast bei den primitiven Datentypen, wird dabei der Typ in den umgewandelt werden soll in runden Klammern angegeben. Versucht man eine Umwandlung durchzuführen, die nicht erlaubt ist, so wird zur Laufzeit ein Fehler generiert. Der Compiler kann nicht unbedingt erkennen, ob ein cast erlaubt ist oder nicht. Der Compiler kann nur anhand der Zuweisungskompibilität feststellen ob ein cast möglich ist oder nicht.

  class Klasse_1 {
    ...
  }

  class Klasse_2 extends Klasse_1 {
    ...
  }

  class Klasse_3 {
    ...
  }

  class Klasse_4 extends Klasse_3 {
    ...
  }

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Klasse_1 a = new Klasse_1();
      // Zur Laufzeit tritt hier ein Fehler auf, der vom Compiler nicht
      // bemerkt werden konnte, da a zuweisungskompatibel zu b ist
      Klasse_2 b = (Klasse_2)a;
      ...
      Klasse_1 c = new Klasse_2();
      // Gibt zur Laufzeit keinen Fehler
      Klasse_2 d = (Klasse_2)c;
      ...
      Klasse_2 e = new Klasse_2();
      // Hier findet der Compiler den Fehler, da e nicht
      // zuweisungskompatibel zu d ist.
      Klasse_4 d = (Klasse_4)e;
    }

    ...
  }

6.7 Der instanceof Operator

Mit dem instanceof-Operator kann zur Laufzeit eines Programms der Objekttyp einer Instanz überprüft werden. Für alle Fälle, in denen eine Zuweisung erlaubt ist, liefert eine Abfrage mit instanceof den Wert true zurück. In allen anderen Fällen liefert die Operation den Wert false.

  class Klasse_1 {
    ...
  }

  class Klasse_2 extends Klasse_1 {
    ...
  }

  class KlassenName {
    ...

    returnTyp methodenName(ParameterListe) {
      ...
      Klasse_2 a = new Klasse_2();
      ...
      if(a instanceof Klasse_2) {
        // Diese Bedingung ist erfüllt
      }
      ...
      if(a instanceof Klasse_1) {
        // Diese Bedingung ist erfüllt
      }
      ...
      Klasse_2[] b = new Klasse_2[7];
      ...
      if(b instanceof Klasse_2[]) {
        // Diese Bedingung ist erfüllt
      }
      ...
      if(b instanceof Klasse_1[]) {
        // Diese Bedingung ist erfüllt
      }
      ...
      if(b instanceof int[]) {
        // Diese Bedingung ist nicht erfüllt
      }
      ...
    }

    ...
  }

6.8 Vergleichen von Instanzen

Der einfachste Weg zwei Instanzen einer Klasse auf Gleichheit zu überprüfen, scheint der Gleichheitsoperator '=='.
EqualsPoint1.java

public class EqualsPoint1 {
  private int x;
  private int y;

  public EqualsPoint1(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

Sourcefile

Equals1.java:

public class Equals1 {
  public static void main(String[] args) {
    EqualsPoint1 p1 = new EqualsPoint1(2, 2);
    EqualsPoint1 p2 = new EqualsPoint1(2, 2);
    System.out.println("(p1 == p2) = " + (p1 == p2));
  }
}
Sourcefile

Das Ergebnis dieses Vergleichs ist false obwohl beide Punkte den selben Inhalt besitzen. Auch diesen Vergleich wollen wir uns im Speicher betrachten.

equals1

Wie wir hier sehen, werden nur die beiden Speicheradressen miteinander verglichen. Diese sind nicht identisch, da es sich hier um zwei Instanzen handelt.

6.8.1 Die Methode equals der Klasse Object

Zum Vergleichen von Instanzen ist in der Klasse Object die Methode public boolean equals(Object obj) definiert. Allerdings wird in dieser Methode wiederum der Gleichheitsoperator verwendet, so dass wir zum selben Ergebnis kommen.

public class Equals2 {
  public static void main(String[] args) {
    EqualsPoint1 p1 = new EqualsPoint1(2, 2);
    EqualsPoint1 p2 = new EqualsPoint1(2, 2);
    System.out.println("p1.equals(p2) = " + p1.equals(p2));
  }
}
Sourcefile

Um einen inhaltlichen Vergleich zweier Instanzen zu erreichen, müssen wir die Methode equals geeignet überschreiben.
EqualsPoint2.java

public class EqualsPoint2 {
  private int x;
  private int y;

  public EqualsPoint2(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public boolean equals(Object obj) {
    EqualsPoint2 p = (EqualsPoint2)obj;
    if((p.x == x) && (p.y == y)) {
      return true;
    } else {
      return false;
    }
  }
}

Sourcefile

Equals3.java:

public class Equals3 {
  public static void main(String[] args) {
    EqualsPoint2 p1 = new EqualsPoint2(2, 2);
    EqualsPoint2 p2 = new EqualsPoint2(2, 2);
    System.out.println("p1.equals(p2) = " + p1.equals(p2));
  }
}
Sourcefile

Bei den Standard-Javaklassen wie z.B. String ist die equals überschrieben, so dass diese Methode zum Vergleich solcher Instanzen verwendet werden kann.

Created with Vim Zurück   Weiter