Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil3 Klassen und Objekte
Pfeil3.1 Objektorientierte Programmierung (OOP)
Pfeil3.1.1 Warum überhaupt OOP?
Pfeil3.1.2 Denk ich an Java, denk ich an Wiederverwendbarkeit
Pfeil3.2 Eigenschaften einer Klasse
Pfeil3.2.1 Klassenarbeit mit Point
Pfeil3.3 Natürlich modellieren mit der UML (Unified Modeling Language) *
Pfeil3.3.1 Hintergrund und Geschichte der UML *
Pfeil3.3.2 Wichtige Diagrammtypen der UML *
Pfeil3.3.3 UML-Werkzeuge *
Pfeil3.4 Neue Objekte erzeugen
Pfeil3.4.1 Ein Exemplar einer Klasse mit dem Schlüsselwort new anlegen
Pfeil3.4.2 Deklarieren von Referenzvariablen
Pfeil3.4.3 Jetzt mach mal ’nen Punkt: Zugriff auf Objektattribute und -methoden
Pfeil3.4.4 Der Zusammenhang von new, Heap und Garbage-Collector
Pfeil3.4.5 Überblick über Point-Methoden
Pfeil3.4.6 Konstruktoren nutzen
Pfeil3.5 ZZZZZnake
Pfeil3.6 Pakete schnüren, Importe und Compilationseinheiten
Pfeil3.6.1 Java-Pakete
Pfeil3.6.2 Pakete der Standardbibliothek
Pfeil3.6.3 Volle Qualifizierung und import-Deklaration
Pfeil3.6.4 Mit import p1.p2.* alle Typen eines Pakets erreichen
Pfeil3.6.5 Hierarchische Strukturen über Pakete und die Spiegelung im Dateisystem
Pfeil3.6.6 Die package-Deklaration
Pfeil3.6.7 Unbenanntes Paket (default package)
Pfeil3.6.8 Compilationseinheit (Compilation Unit)
Pfeil3.6.9 Statischer Import *
Pfeil3.7 Mit Referenzen arbeiten, Vielfalt, Identität, Gleichwertigkeit
Pfeil3.7.1 null-Referenz und die Frage der Philosophie
Pfeil3.7.2 Alles auf null? Referenzen testen
Pfeil3.7.3 Zuweisungen bei Referenzen
Pfeil3.7.4 Methoden mit Referenztypen als Parametern
Pfeil3.7.5 Identität von Objekten
Pfeil3.7.6 Gleichwertigkeit und die Methode equals(…)
Pfeil3.8 Zum Weiterlesen
 

Zum Seitenanfang

3.7    Mit Referenzen arbeiten, Vielfalt, Identität, Gleichwertigkeit Zur vorigen ÜberschriftZur nächsten Überschrift

In Java gibt es mit null eine sehr spezielle Referenz, die Auslöser vieler Probleme ist. Doch ohne sie geht es nicht, und warum das so ist, wird der folgende Abschnitt zeigen. Anschließend wollen wir sehen, wie Objektvergleiche funktionieren und was der Unterschied zwischen Identität und Gleichwertigkeit ist.

 

Zum Seitenanfang

3.7.1    null-Referenz und die Frage der Philosophie Zur vorigen ÜberschriftZur nächsten Überschrift

In Java gibt es drei spezielle Referenzen: null, this und super. (Wir verschieben die Beschreibung von this und super auf Kapitel 6, »Eigene Klassen schreiben«.) Das spezielle Literal null lässt sich zur Initialisierung von Referenzvariablen verwenden. Die null-Referenz ist typenlos, kann also jeder Referenzvariablen zugewiesen und jeder Methode übergeben werden, die ein Objekt erwartet.[ 116 ](null verhält sich also so, als ob es ein Untertyp jedes anderen Typs wäre. )

[zB]  Beispiel

Deklaration und Initialisierung zweier Objektvariablen mit null:

Point  p = null;

String s = null;

System.out.println( p ); // null

Die Konsolenausgabe über die letzte Zeile liefert kurz »null«. Wir haben hier die String-Repräsentation vom null-Typ vor uns.

Da null typenlos ist und es nur ein null gibt, kann null zu jedem Typ typangepasst werden, und so ergibt zum Beispiel ((String) null == null && (Point) null == null das Ergebnis true. Das Literal null ist ausschließlich für Referenzen vorgesehen und kann in keinen primitiven Typ wie die Ganzzahl 0 umgewandelt werden.[ 117 ](Hier unterscheiden sich C(++) und Java. )

Mit null lässt sich eine ganze Menge machen. Der Haupteinsatzzweck sieht vor, damit uninitialisierte Referenzvariablen zu kennzeichnen, also auszudrücken, dass eine Referenzvariable auf kein Objekt verweist. In Listen oder Bäumen kennzeichnet null zum Beispiel das Fehlen eines gültigen Nachfolgers oder bei einem grafischen Dialog, dass der Benutzer den Dialog abgebrochen hat; null ist dann ein gültiger Indikator und kein Fehlerfall.

[»]  Hinweis

Bei einer mit null initialisierten lokalen Variablen funktioniert die Abkürzung mit var nicht; es gibt einen Compilerfehler:

var text = null;  // inline image Cannot infer type for local variable initialized to 'null'

Auf null geht nix, nur die NullPointerException

Da sich hinter null kein Objekt verbirgt, ist es auch nicht möglich, eine Methode aufzurufen oder von null ein Attribut zu erfragen. Der Compiler kennt zwar den Typ jedes Ausdrucks, aber erst die Laufzeitumgebung (JVM) weiß, was referenziert wird. Bei dem Versuch, über die null-Referenz auf eine Eigenschaft eines Objekts zuzugreifen, löst eine JVM eine NullPointerException[ 118 ](Der Name zeigt das Überbleibsel von Zeigern. Zwar haben wir es in Java nicht mit Zeigern zu tun, sondern mit Referenzen, doch heißt es NullPointerException und nicht NullReferenceException. Das erinnert daran, dass eine Referenz ein Objekt identifiziert und eine Referenz auf ein Objekt ein Pointer ist. Das .NET Framework ist hier konsequenter und nennt die Ausnahme NullReferenceException. ) aus:

Listing 3.12    src/main/java/com/tutego/insel/oop/NullPointer.java

package com.tutego.insel.oop;                    // 1

public class NullPointer { // 2

public static void main( String[] args ) { // 3

java.awt.Point p = null; // 4

String s = null; // 5

p.setLocation( 1, 2 ); // 6

s.length(); // 7

} // 8

} // 9

Wir beobachten eine NullPointerException zur Laufzeit, denn das Programm bricht bei p.setLocation(…) mit folgender Ausgabe ab:

Exception in thread "main" java.lang.NullPointerException

at com.tutego.insel.oop.NullPointer.main(NullPointer.java:6)

Die Laufzeitumgebung teilt uns in der Fehlermeldung mit, dass sich der Fehler, die NullPointerException, in Zeile 6 befindet. Um den Fehler zu korrigieren, müssen wir entweder die Variablen initialisieren, das heißt, ein Objekt zuweisen wie in

p = new java.awt.Point();

s = "";

oder vor dem Zugriff auf die Eigenschaften einen Test durchführen, ob Objektvariablen auf etwas zeigen oder null sind, und in Abhängigkeit vom Ausgang des Tests den Zugriff auf die Eigenschaft zulassen oder nicht.

[»]  »null« in anderen Programmiersprachen *

Ist Java eine rein objektorientierte Programmiersprache? Nein, da Java einen Unterschied zwischen primitiven Typen und Referenztypen macht. Nehmen wir für einen Moment an, dass es primitive Typen nicht gäbe. Wäre Java dann eine rein objektorientierte Programmiersprache, bei der jede Referenz ein pures Objekt referenziert? Die Antwort ist immer noch nein, da es mit null etwas gibt, womit Referenzvariablen initialisiert werden können, was aber kein Objekt repräsentiert und keine Methoden besitzt. Und das kann bei der Dereferenzierung eine NullPointerException geben.

Andere Programmiersprachen haben andere Lösungsansätze, und null-Referenzierungen sind nicht möglich. In der Sprache Ruby zum Beispiel ist immer alles ein Objekt. Wo Java mit null ein »nicht belegt« ausdrückt, macht Ruby das mit nil. Der feine Unterschied ist, dass nil ein Exemplar der Klasse NilClass ist, genau genommen ein Singleton, das es im System nur einmal gibt. nil hat auch ein paar öffentliche Methoden wie to_s (wie Javas toString()), das dann einen leeren String liefert. Mit nil gibt es keine NullPointerException mehr, aber natürlich immer noch einen Fehler, wenn auf diesem Objekt vom Typ NilClass eine Methode aufgerufen wird, die es nicht gibt. In Objective-C, der (bisherigen) Standardsprache für iOS-Programme, gibt es das Null-Objekt nil. Üblicherweise passiert nichts, wenn eine Nachricht an das nil-Objekt gesendet wird; die Nachricht wird einfach ignoriert.[ 119 ](Es gibt auch Compiler wie den GCC, der mit der Option -fno-nil-receivers dieses Verhalten abschaltet, um schnelleren Maschinencode zu erzeugen. Denn letztendlich muss in Maschinencode immer ein Test stehen, der auf 0 prüft. )

 

Zum Seitenanfang

3.7.2    Alles auf null? Referenzen testen Zur vorigen ÜberschriftZur nächsten Überschrift

Mit dem Vergleichsoperator == oder dem Test auf Ungleichheit mit != lässt sich leicht herausfinden, ob eine Referenzvariable wirklich ein Objekt referenziert oder nicht:

if ( object == null )

// Variable referenziert nichts, ist aber gültig mit null initialisiert

else

// Variable referenziert ein Objekt

null-Test und Kurzschluss-Operatoren

Wir wollen an dieser Stelle noch einmal auf die üblichen logischen Kurzschluss-Operatoren und den logischen, nicht kurzschließenden Operator zu sprechen kommen. Erstere werten Operanden nur so lange von links nach rechts aus, bis das Ergebnis der Operation feststeht. Auf den ersten Blick scheint es nicht viel auszumachen, ob alle Teilausdrücke ausgewertet werden oder nicht. In einigen Ausdrücken ist dies aber wichtig, wie das folgende Beispiel für die Variable s vom Typ String zeigt:

Listing 3.13    src/main/java/NullCheck.java, main

public static void main( String[] args ) {

String s = javax.swing.JOptionPane.showInputDialog( "Eingabe" );

if ( s != null && ! s.isEmpty() )

System.out.println( "Eingabe: " + s );

else

System.out.println( "Abbruch oder keine Eingabe" );

}

Die Rückgabe von showInputDialog(…) ist null, wenn der Benutzer den Dialog abbricht. Das soll unser Programm berücksichtigen. Daher testet die if-Bedingung, ob s überhaupt auf ein Objekt verweist, und prüft gleichzeitig, ob die Länge größer 0 ist. Dann folgt eine Ausgabe.

Diese Schreibweise tritt häufig auf, und der Und-Operator zur Verknüpfung muss ein Kurzschluss-Operator sein, da es in diesem Fall ausdrücklich darauf ankommt, dass die Länge nur dann bestimmt wird, wenn die Variable s überhaupt auf ein String-Objekt verweist und nicht null ist. Andernfalls bekämen wir bei s.isEmpty() eine NullPointerException, wenn jeder Teilausdruck ausgewertet würde und s gleich null wäre.

[»]  Das Glück der anderen: null coalescing operator *

Da null viel zu oft vorkommt, null-Referenzierungen aber vermieden werden müssen, gibt es viel Code der Art o != null ? o : non_null_o. Diverse Programmiersprachen wie JavaScript, Kotlin, Objective-C, PHP oder Swift bieten für dieses Konstrukt eine Abkürzung über den sogenannten null coalescing operator (coalescing heißt auf Deutsch »verschmelzend«). Er wird mal als ?? oder als ?: geschrieben, für unser Beispiel so: o ?? non_null_o. Besonders hübsch ist das bei sequenziellen Tests der Art o ?? p ?? q ?? r, wo es dann sinngemäß heißt: »Liefere die erste Referenz ungleich null.« Java bietet keinen solchen Operator.

 

Zum Seitenanfang

3.7.3    Zuweisungen bei Referenzen Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Referenz erlaubt den Zugriff auf das referenzierte Objekt, und eine Referenzvariable speichert eine Referenz. Es kann durchaus mehrere Referenzvariablen geben, die die gleiche Referenz speichern. Das wäre so, als ob ein Objekt unter verschiedenen Namen angesprochen wird – so wie eine Person von den Mitarbeitern als »Chefin« angesprochen wird, aber von ihrem Mann als »Schnuckiputzi«. Dies nennt sich auch Alias.

[zB]  Beispiel

Ein Punkt-Objekt wollen wir unter einem alternativen Variablennamen ansprechen:

Point p = new Point();

Point q = p;

Ein Punkt-Objekt wird erzeugt und mit der Variablen p referenziert. Die zweite Zeile speichert nun dieselbe Referenz in der Variablen q. Danach verweisen p und q auf dasselbe Objekt. Zum besseren Verständnis: Wichtig ist, wie oft es new gibt, denn das sagt aus, wie viele Objekte die JVM bildet. Und bei den zwei Zeilen gibt es nur ein new, also auch nur einen Punkt.

Verweisen zwei Objektvariablen auf dasselbe Objekt, hat das natürlich zur Konsequenz, dass über zwei Wege Objektzustände ausgelesen und modifiziert werden können. Heißt die gleiche Person in der Firma »Chefin« und zu Hause »Schnuckiputzi«, wird der Mann sich freuen, wenn die Frau in der Firma keinen Stress hat.

Wir können das Beispiel auch gut bei Punkt-Objekten nachverfolgen. Zeigen p und q auf dasselbe Punkt-Objekt, können Änderungen über p auch über die Variable q beobachtet werden:

Listing 3.14    ItsTheSame.java, main

public static void main( String[] args ) {

Point p = new Point();

Point q = p;

p.x = 10;

System.out.println( q.x ); // 10

q.y = 5;

System.out.println( p.y ); // 5

}
 

Zum Seitenanfang

3.7.4    Methoden mit Referenztypen als Parametern Zur vorigen ÜberschriftZur nächsten Überschrift

Dass sich dasselbe Objekt unter zwei Namen (über zwei verschiedene Variablen) ansprechen lässt, können wir gut bei Methoden beobachten. Eine Methode, die über den Parameter eine Objektreferenz erhält, kann auf das übergebene Objekt zugreifen. Das bedeutet, die Methode kann dieses Objekt mit den angebotenen Methoden ändern oder auf die Attribute zugreifen.

Im folgenden Beispiel deklarieren wir zwei Methoden. Die erste Methode, initializeToken(Point), soll einen Punkt mit Zufallskoordinaten initialisieren. Übergeben werden ihr dann zwei Point-Objekte: einmal für einen Spieler und einmal für eine Schlange. Die zweite Methode, printScreen(Point, Point), gibt das Spielfeld auf dem Bildschirm aus und gibt dann, wenn die Koordinate einen Spieler trifft, ein & aus und bei der Schlange ein S. Falls Spieler und Schlange zufälligerweise zusammentreffen, »gewinnt« die Schlange.

Listing 3.15    src/main/java/com/tutego/insel/oop/DrawPlayerAndSnake.java

package com.tutego.insel.oop;

import java.awt.Point;



public class DrawPlayerAndSnake {



static void initializeToken( Point p ) {

int randomX = (int)(Math.random() * 40); // 0 <= x < 40

int randomY = (int)(Math.random() * 10); // 0 <= y < 10

p.setLocation( randomX, randomY );

}



static void printScreen( Point playerPosition,

Point snakePosition ) {

for ( int y = 0; y < 10; y++ ) {

for ( int x = 0; x < 40; x++ ) {

if ( snakePosition.distanceSq( x, y ) == 0 )

System.out.print( 'S' );

else if ( playerPosition.distanceSq( x, y ) == 0 )

System.out.print( '&' );

else System.out.print( '.' );

}

System.out.println();

}

}



public static void main( String[] args ) {

Point playerPosition = new Point();

Point snakePosition = new Point();

System.out.println( playerPosition );

System.out.println( snakePosition );

initializeToken( playerPosition );

initializeToken( snakePosition );

System.out.println( playerPosition );

System.out.println( snakePosition );

printScreen( playerPosition, snakePosition );

}

}

Die Ausgabe kann so aussehen:

java.awt.Point[x=0,y=0]

java.awt.Point[x=0,y=0]

java.awt.Point[x=38,y=1]

java.awt.Point[x=19,y=8]

........................................

......................................&.

........................................

........................................

........................................

........................................

........................................

........................................

...................S....................

........................................

In dem Moment, in dem main(…) die statische Methode initializeToken(Point) aufruft, gibt es sozusagen zwei Namen für das Point-Objekt: playerPosition und p. Allerdings ist das nur innerhalb der virtuellen Maschine so, denn initializeToken(Point) kennt das Objekt nur unter p, aber kennt die Variable playerPosition nicht. Bei main(…) ist es umgekehrt: Nur der Variablenname playerPosition ist in main(…) bekannt, er hat aber vom Namen p keine Ahnung. Die Point-Methode distanceSq(int, int) liefert den quadrierten Abstand vom aktuellen Punkt zu den übergebenen Koordinaten.

[»]  Hinweis

Der Name einer Parametervariablen darf durchaus mit dem Namen der Argumentvariablen übereinstimmen, was die Semantik nicht verändert. Die Namensräume sind völlig getrennt, und Missverständnisse gibt es nicht, da beide – die aufrufende Methode und die aufgerufene Methode – komplett getrennte lokale Variablen haben.

Wertübergabe und Referenzübergabe per Call by Value

Primitive Variablen werden immer per Wert kopiert (Call by Value). Das Gleiche gilt für Referenzen, die ja als eine Art Zeiger zu verstehen sind, und das sind im Prinzip nur Ganzzahlen. Daher hat auch die folgende statische Methode keine Nebenwirkungen:

Listing 3.16    JavaIsAlwaysCallByValue.java

package com.tutego.insel.oop;

import java.awt.Point;



public class JavaIsAlwaysCallByValue {



static void clear( Point p ) {

System.out.println( p ); // java.awt.Point[x=10,y=20]

p = new Point();

System.out.println( p ); // java.awt.Point[x=0,y=0]

}



public static void main( String[] args ) {

Point p = new Point( 10, 20 );

clear( p );

System.out.println( p ); // java.awt.Point[x=10,y=20]

}

}

Nach der Zuweisung p = new Point() in der clear(Point)-Methode referenziert die Parametervariable p ein anderes Punkt-Objekt, und der an die Methode übergebene Verweis geht damit verloren. Diese Änderung wird nach außen hin natürlich nicht sichtbar, denn die Parametervariable p von clear(…) ist ja nur ein temporärer alternativer Name für das p aus main; eine Neuzuweisung an das clear-p ändert nicht den Verweis vom main-p. Das bedeutet, dass der Aufrufer von clear(…) – und das ist main(…) – kein neues Objekt unter sich hat. Wer den Punkt mit null initialisieren möchte, muss auf die Zustände des übergebenen Objekts direkt zugreifen, etwa so:

static void clear( Point p ) {

p.x = p.y = 0;

}
[»]  Call by Reference gibt es in Java nicht – ein Blick auf C und C++ *

In C++ gibt es eine weitere Argumentübergabe, die sich Call by Reference nennt. Würde eine Methode wie clear(…) mit Referenzsemantik deklariert, würde die Variable p ein Synonym darstellen, also einen anderen Namen für eine Variable – in unserem Fall q. Damit würde die Zuweisung im Rumpf den Zeiger auf ein neues Objekt legen. Die swap(…)-Funktion ist ein gutes Beispiel für die Nützlichkeit von Call by Reference:

void swap( int& a, int& b ) { int tmp = a; a = b; b = tmp; }

Zeiger und Referenzen sind in C++ etwas anderes, was Spracheinsteiger leicht irritiert. Denn in C++ und auch in C hätte eine vergleichbare swap(…)-Funktion auch mit Zeigern implementiert werden können:

void swap( int *a, int *b ) { int tmp = *a; *a = *b; *b = tmp; }

Die Implementierung gibt in C(++) einen Verweis auf das Argument.

Final deklarierte Referenzparameter und das fehlende const

Wir haben gesehen, dass finale Variablen dem Programmierer vorgeben, dass er Variablen nicht wieder beschreiben darf. Final können lokale Variablen, Parametervariablen, Objektvariablen oder Klassenvariablen sein. In jedem Fall sind neue Zuweisungen tabu. Dabei ist es egal, ob die Parametervariable vom primitiven Typ oder vom Referenztyp ist. Bei einer Methodendeklaration der folgenden Art wäre also eine Zuweisung an p und auch an value verboten:

public void clear( final Point p, final int value )

Ist die Parametervariable nicht final und ein Referenztyp, so würden wir mit einer Zuweisung den Verweis auf das ursprüngliche Objekt verlieren, und das wäre wenig sinnvoll, wie wir im vorangehenden Beispiel gesehen haben. final deklarierte Parametervariablen machen im Programmcode deutlich, dass eine Änderung der Referenzvariablen unsinnig ist, und der Compiler verbietet eine Zuweisung. Im Fall unserer clear(…)-Methode wäre die Initialisierung direkt als Compilerfehler aufgefallen:

static void clear( final Point p ) {

p = new Point(); // inline image The final local variable p cannot be assigned.

}

Halten wir fest: Ist ein Parameter mit final deklariert, sind keine Zuweisungen möglich. final verbietet aber keine Änderungen an Objekten – und so könnte final im Sinne der Übersetzung als »endgültig« verstanden werden. Mit der Referenz des Objekts können wir sehr wohl den Zustand verändern, so wie wir es auch im letzten Beispielprogramm taten.

final erfüllt demnach nicht die Aufgabe, schreibende Objektzugriffe zu verhindern. Eine Methode mit übergebenen Referenzen kann also Objekte verändern, wenn es etwa setXXX(…)-Methoden oder Variablen gibt, auf die zugegriffen werden kann. Die Dokumentation muss also immer ausdrücklich beschreiben, wann die Methode den Zustand eines Objekts modifiziert.

In C++ gibt es für Parameter den Zusatz const, an dem der Compiler erkennen kann, dass Objektzustände nicht verändert werden sollen. Ein Programm nennt sich const-korrekt, wenn es niemals ein konstantes Objekt verändert. Dieses const ist in C++ eine Erweiterung des Objekttyps, die es in Java nicht gibt. Zwar haben die Java-Entwickler das Schlüsselwort const reserviert, doch genutzt wird es bisher nicht.

 

Zum Seitenanfang

3.7.5    Identität von Objekten Zur vorigen ÜberschriftZur nächsten Überschrift

Die Vergleichsoperatoren == und != sind für alle Datentypen so definiert, dass sie die vollständige Übereinstimmung zweier Werte testen. Bei primitiven Datentypen ist das einfach einzusehen und bei Referenztypen im Prinzip genauso (zur Erinnerung: Referenzen lassen sich als Pointer verstehen, was Ganzzahlen sind). Der Operator == testet bei Referenzen, ob sie übereinstimmen, also auf dasselbe Objekt verweisen. Der Operator != testet das Gegenteil, also ob sie nicht übereinstimmen, die Referenzen somit ungleich sind. Demnach sagt der Test etwas über die Identität der referenzierten Objekte aus, aber nichts darüber, ob zwei verschiedene Objekte möglicherweise den gleichen Inhalt haben. Der Inhalt der Objekte spielt bei == und != keine Rolle.

[zB]  Beispiel

Zwei Objekte mit drei unterschiedlichen Punktvariablen p, q, r und die Bedeutung von ==:

Point p = new Point( 10, 10 );

Point q = p;

Point r = new Point( 10, 10 );

System.out.println( p == q ); // true, da p und q dasselbe Objekt referenzieren

System.out.println( p == r ); // false, da p und r zwei verschiedene Punkt-

// Objekte referenzieren, die zufällig dieselben

// Koordinaten haben

Da p und q auf dasselbe Objekt verweisen, ergibt der Vergleich true. p und r referenzieren unterschiedliche Objekte, die aber zufälligerweise den gleichen Inhalt haben. Doch woher soll der Compiler wissen, wann zwei Punkt-Objekte inhaltlich gleich sind? Weil sich ein Punkt durch die Attribute x und y auszeichnet? Die Laufzeitumgebung könnte voreilig die Belegung jeder Objektvariablen vergleichen, doch das entspricht nicht immer einem korrekten Vergleich, so wie wir ihn uns wünschen. Ein Punkt-Objekt könnte etwa zusätzlich die Anzahl der Zugriffe zählen, die jedoch für einen Vergleich, der auf der Lage zweier Punkte basiert, nicht berücksichtigt werden darf.

 

Zum Seitenanfang

3.7.6    Gleichwertigkeit und die Methode equals(…) Zur vorigen ÜberschriftZur nächsten Überschrift

Die allgemeingültige Lösung besteht darin, die Klasse festlegen zu lassen, wann Objekte gleich(wertig) sind. Dazu kann jede Klasse eine Methode equals(…) implementieren, und mit ihrer Hilfe kann sich jedes Exemplar dieser Klasse mit beliebigen anderen Objekten vergleichen. Die Klassen entscheiden immer nach Anwendungsfall, welche Attribute sie für einen Gleichheitstest heranziehen, und equals(…) liefert true, wenn die gewünschten Zustände (Objektvariablen) übereinstimmen.

[zB]  Beispiel

Zwei nichtidentische, inhaltlich gleiche Punkt-Objekte, werden mit == und equals(…) verglichen:

Point p = new Point( 10, 10 );

Point q = new Point( 10, 10 );

System.out.println( p == q ); // false

System.out.println( p.equals(q) ); // true, da symmetrisch auch q.equals(p)

Nur equals(…) testet in diesem Fall die inhaltliche Gleichwertigkeit.

Bei den unterschiedlichen Bedeutungen müssen wir demnach die Begriffe Identität und Gleichwertigkeit (auch Gleichheit) von Objekten sorgfältig unterscheiden. Daher zeigt Tabelle 3.4 noch einmal eine Zusammenfassung:

Getestet mit

Implementierung

Identität der Referenzen

== bzw. !=

Nichts zu tun

Gleichwertigkeit der Zustände

equals(…) bzw. ! equals(…)

Abhängig von der Klasse

Tabelle 3.4    Identität und Gleichwertigkeit von Objekten

equals(…)-Implementierung von Point *

Die Klasse Point deklariert equals(…), wie die API-Dokumentation zeigt. Werfen wir einen Blick auf die Implementierung, um eine Vorstellung von der Arbeitsweise zu bekommen:

Listing 3.17    java/awt/Point.java, Ausschnitt

public class Point ... {



public int x;

public int y;

...

public boolean equals( Object obj ) {

...

Point pt = (Point) obj;

return (x == pt.x) && (y == pt.y); // (*)

...

}

}

Obwohl bei diesem Beispiel für uns einiges neu ist, erkennen wir den Vergleich in der Zeile (*). Hier vergleicht das Point-Objekt seine eigenen Attribute mit den Attributen des Punktobjekts, das als Argument an equals(…) übergeben wurde.

Es gibt immer ein equals(…) – die Oberklasse Object und ihr equals(…) *

Glücklicherweise müssen wir als Programmierer nicht lange darüber nachdenken, ob eine Klasse eine equals(…)-Methode anbieten soll oder nicht. Jede Klasse besitzt sie, da die universelle Oberklasse Object sie vererbt. Wir greifen hier auf Kapitel 7, »Objektorientierte Beziehungsfragen«, vor; der Abschnitt kann aber übersprungen werden. Wenn eine Klasse also keine eigene equals(…)-Methode angibt, dann erbt sie eine Implementierung aus der Klasse Object. Diese Klasse sieht wie folgt aus:

Listing 3.18    java/lang/Object.java, Ausschnitt

public class Object {

public boolean equals( Object obj ) {

return ( this == obj );

}

...

}

Wir erkennen, dass hier die Gleichwertigkeit auf die Identität der Referenzen abgebildet wird. Ein inhaltlicher Vergleich findet nicht statt. Das ist das Einzige, was die vorgegebene Implementierung machen kann, denn sind die Referenzen identisch, sind die Objekte logischerweise auch gleich. Nur über Zustände »weiß« die Basisklasse Object nichts.

[»]  Sprachvergleich

Es gibt Programmiersprachen, die für den Identitätsvergleich und Gleichwertigkeitstest eigene Operatoren anbieten. Was bei Java == und equals(…) sind, sind bei Python is und ==, bei Swift === und ==.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern