Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.
 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Die Klassenbibliothek
14 Einführung in die nebenläufige Programmierung
15 Einführung in Datenstrukturen und Algorithmen
16 Einführung in grafische Oberflächen
17 Einführung in Dateien und Datenströme
18 Einführung ins Datenbankmanagement mit JDBC
19 Einführung in <XML>
20 Testen mit JUnit
21 Bits und Bytes und Mathematisches
22 Die Werkzeuge des JDK
A Java SE Paketübersicht
Stichwortverzeichnis

Download:
- Beispielprogramme, ca. 20,0 MB
- Übungsaufgaben, ca. 1,8 MB
- Musterlösungen, ca. 0,8 MB

Buch bestellen
Ihre Meinung?

Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenbloom
Das umfassende Handbuch
Buch: Java ist auch eine Insel

Java ist auch eine Insel
Rheinwerk Computing
1306 Seiten, gebunden, 11. Auflage
49,90 Euro, ISBN 978-3-8362-2873-2
Pfeil 3 Klassen und Objekte
Pfeil 3.1 Objektorientierte Programmierung (OOP)
Pfeil 3.1.1 Warum überhaupt OOP?
Pfeil 3.1.2 Denk ich an Java, denk ich an Wiederverwendbarkeit
Pfeil 3.2 Eigenschaften einer Klasse
Pfeil 3.2.1 Klassenarbeit mit Point
Pfeil 3.3 Natürlich modellieren mit der UML (Unified Modeling Language) *
Pfeil 3.3.1 Hintergrund und Geschichte der UML
Pfeil 3.3.2 Wichtige Diagrammtypen der UML
Pfeil 3.3.3 UML-Werkzeuge
Pfeil 3.4 Neue Objekte erzeugen
Pfeil 3.4.1 Ein Exemplar einer Klasse mit dem new-Operator anlegen
Pfeil 3.4.2 Der Zusammenhang von new, Heap und Garbage-Collector
Pfeil 3.4.3 Deklarieren von Referenzvariablen
Pfeil 3.4.4 Zugriff auf Objektattribute und -methoden mit dem ».«
Pfeil 3.4.5 Überblick über Point-Methoden
Pfeil 3.4.6 Konstruktoren nutzen
Pfeil 3.5 ZZZZZnake
Pfeil 3.6 Pakete schnüren, Imports und Kompilationseinheiten
Pfeil 3.6.1 Java-Pakete
Pfeil 3.6.2 Pakete der Standardbibliothek
Pfeil 3.6.3 Volle Qualifizierung und import-Deklaration
Pfeil 3.6.4 Mit import p1.p2.* alle Typen eines Pakets erreichen
Pfeil 3.6.5 Hierarchische Strukturen über Pakete
Pfeil 3.6.6 Die package-Deklaration
Pfeil 3.6.7 Unbenanntes Paket (default package)
Pfeil 3.6.8 Klassen mit gleichen Namen in unterschiedlichen Paketen *
Pfeil 3.6.9 Kompilationseinheit (Compilation Unit)
Pfeil 3.6.10 Statischer Import *
Pfeil 3.7 Mit Referenzen arbeiten, Identität und Gleichheit
Pfeil 3.7.1 null-Referenz und die Frage der Philosophie
Pfeil 3.7.2 Alles auf null? Referenzen testen
Pfeil 3.7.3 Zuweisungen bei Referenzen
Pfeil 3.7.4 Methoden mit Referenztypen als Parametern
Pfeil 3.7.5 Identität von Objekten
Pfeil 3.7.6 Gleichheit und die Methode equals(…)
Pfeil 3.8 Arrays
Pfeil 3.8.1 Grundbestandteile
Pfeil 3.8.2 Deklaration von Arrays
Pfeil 3.8.3 Arrays mit Inhalt
Pfeil 3.8.4 Die Länge eines Arrays über das Attribut length auslesen
Pfeil 3.8.5 Zugriff auf die Elemente über den Index
Pfeil 3.8.6 Array-Objekte mit new erzeugen
Pfeil 3.8.7 Typische Feldfehler
Pfeil 3.8.8 Feld-Objekte als Parametertyp
Pfeil 3.8.9 Vorinitialisierte Arrays
Pfeil 3.8.10 Die erweiterte for-Schleife
Pfeil 3.8.11 Arrays mit nichtprimitiven Elementen
Pfeil 3.8.12 Methode mit variabler Argumentanzahl (Vararg)
Pfeil 3.8.13 Mehrdimensionale Arrays *
Pfeil 3.8.14 Nichtrechteckige Arrays *
Pfeil 3.8.15 Die Wahrheit über die Array-Initialisierung *
Pfeil 3.8.16 Mehrere Rückgabewerte *
Pfeil 3.8.17 Klonen kann sich lohnen – Arrays vermehren *
Pfeil 3.8.18 Feldinhalte kopieren *
Pfeil 3.8.19 Die Klasse Arrays zum Vergleichen, Füllen, Suchen, Sortieren nutzen
Pfeil 3.8.20 Eine lange Schlange
Pfeil 3.9 Der Einstiegspunkt für das Laufzeitsystem: main(…)
Pfeil 3.9.1 Korrekte Deklaration der Startmethode
Pfeil 3.9.2 Kommandozeilenargumente verarbeiten
Pfeil 3.9.3 Der Rückgabetyp von main(…) und System.exit(int) *
Pfeil 3.10 Grundlagen von Annotationen und Generics
Pfeil 3.10.1 Generics
Pfeil 3.10.2 Annotationen
Pfeil 3.10.3 Annotationstypen aus java.lang
Pfeil 3.10.4 @Deprecated
Pfeil 3.10.5 @SuppressWarnings
Pfeil 3.11 Zum Weiterlesen
 
Zum Seitenanfang

3.7Mit Referenzen arbeiten, Identität und Gleichheit 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 Gleichheit ist.

 
Zum Seitenanfang

3.7.1null-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 this und super auf Kapitel 5, »Eigene Klassen schreiben«). Das spezielle Literal null lässt sich zur Initialisierung von Referenzvariablen verwenden. Die null-Referenz ist typlos, kann also jeder Referenzvariablen zugewiesen und jeder Methode übergeben werden, die ein Objekt erwartet.[ 117 ]

[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«.

Da es nur ein null gibt, ist zum Beispiel (Point) null == (String) null. Der Wert ist ausschließlich für Referenzen vorgesehen und kann in keinen primitiven Typ wie die Ganzzahl 0 umgewandelt werden.[ 118 ]

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.

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 der null ein Attribut zu erfragen. Der Compiler kennt zwar den Typ jedes Objekts, aber erst die Laufzeitumgebung (JVM) weiß, was referenziert wird. Wird versucht, über die null-Referenz auf eine Eigenschaft eines Objekts zuzugreifen, löst eine JVM eine NullPointerException[ 119 ] aus:

Listing 3.10NullPointer.java

/* 1 */public class NullPointer {
/* 2 */ public static void main( String[] args ) {
/* 3 */ java.awt.Point p = null;
/* 4 */ String s = null;
/* 5 */
/* 6 */ p.setLocation( 1, 2 );
/* 7 */ s.length();
/* 8 */ }
/* 9 */}

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

java.lang.NullPointerException
at NullPointer.main(NullPointer.java:6)
Exception in thread "main"

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 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.

 
Zum Seitenanfang

3.7.2Alles 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.11NullCheck.java

public class NullCheck {

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.

»null« in anderen Programmiersprachen

Ist Java eine pure objektorientiere Programmiersprache? Nein, da Java einen Unterschied zwischen primitiven Typen und Referenztypen macht. Nehmen wir für einen Moment an, dass es primitive Typen nicht gibt. Wäre Java dann eine reine objektorientierte Programmiersprache, bei der jede Referenz ein pures Objekt referenziert? Die Antwort ist immer noch nein, da es mit null etwas gibt, mit dem Referenzvariablen initialisiert werden können, aber was 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 das Ruby mit nil. Der feine Unterschied ist, dass nil ein Exemplar der Klasse NilClass ist, genau genommen ein Singleton, was es im System nur einmal gibt. nil hat auch ein paar öffentliche Methoden wie to_s (wie Javas toString()), was 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 Standardsprache für iPhone-Programme, gibt es das Null-Objekt nil. Üblicherweise passiert nichts, wenn eine Nachricht an das nil-Objekt gesendet wird; die Nachricht wird einfach ignoriert.[ 120 ]

 
Zum Seitenanfang

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

Eine Referenz erlaubt den Zugriff auf das referenzierte Objekt. Es kann durchaus mehrere Kopien dieser Referenz geben, die in Variablen mit unterschiedlichen Namen abgelegt sind – 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.

Verweisen zwei Objektvariablen auf das gleiche 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.12ItsTheSame.java

import java.awt.Point;

public class ItsTheSame {

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.4Methoden mit Referenztypen als Parametern Zur vorigen ÜberschriftZur nächsten Überschrift

Dass sich das gleiche 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 wir 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«.

Listing 3.13DrawPlayerAndSnake.java

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 ( playerPosition.distanceSq( x, y ) == 0 )
System.out.print( '&' );
else if ( snakePosition.distanceSq( x, y ) == 0 )
System.out.print( 'S' );
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.

[+]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 (engl. 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.14JavaIsAlwaysCallByValue.java

import java.awt.Point;

public class JavaIsAlwaysCallByValue {

static void clear( Point p ) {
p = new Point();
}

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 der 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, was 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 Referenz-Parameter 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 sein, Parametervariablen oder auch Objektvariablen oder Klassenvariablen. 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. Finale deklariere Parametervariablen machen im Programmcode deutlich, dass eine Änderungen der Referenzvariablen unsinnig sind, 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(); // 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 »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 Objektveränderungen vornehmen, 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.

Finale Parameter in der Vererbung *

In der Vererbung von Methoden spielen finale Parametervariablen keine Rolle. Wir können es als zusätzliche Information für die jeweilige Methode betrachten. Eine Unterklasse kann demnach beliebig das final hinzufügen oder auch wegnehmen. Alte Bibliotheken lassen sich so leicht weiterverwenden.

 
Zum Seitenanfang

3.7.5Identitä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 diese übereinstimmen, also auf das gleiche Objekt verweisen, != das Gegenteil, ob sie nicht übereinstimmen, also die Referenzen 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.6Gleichheit 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 sind. Dazu kann jede Klasse eine Methode equals(…) implementieren, die Exemplare dieser Klasse mit beliebigen anderen Objekten vergleichen kann. 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 inhaltlich gleiche Punkt-Objekte, verglichen mit == und equals(…):

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 Gleichheit.

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

Getestet mit

Implementierung

Identität der Referenzen

==

nichts zu tun

Gleichheit der Zustände

equals(…)

abhängig von der Klasse

Tabelle 3.4Identität und Gleichheit 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.15java/awt/Point.java, Ausschnitt

public class Point … {

public int x;
public int y;

public boolean equals( Object obj ) {
if ( obj instanceof Point ) {
Point pt = (Point) obj;
return (x == pt.x) && (y == pt.y); // (*)
}
return super.equals( obj );
}

}

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 Objekts, 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 5, »Eigene Klassen schreiben«, 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. Die sieht wie folgt aus:

Listing 3.16java/lang/Object.java, Ausschnitt

public class Object {
public boolean equals( Object obj ) {
return ( this == obj );
}

}

Wir erkennen, dass hier die Gleichheit auf die Gleichheit 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.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
 Zum Katalog
Zum Katalog: Java ist auch eine Insel Java ist auch eine Insel
Jetzt bestellen

 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?
Ihre Meinung

 Buchempfehlungen
Zum Katalog: Java SE 8 Standard-Bibliothek
Java SE 8 Standard-Bibliothek


Zum Katalog: Professionell entwickeln mit Java EE 7
Professionell entwickeln mit Java EE 7


Zum Katalog: Schrödinger programmiert Java
Schrödinger programmiert Java


Zum Katalog: Einführung in Java
Einführung in Java


Zum Katalog: Programmieren lernen mit Java
Programmieren lernen mit Java


Zum Katalog: Apps entwickeln für Android 5
Apps entwickeln für Android 5


Zum Katalog: Apps entwickeln mit Android Studio
Apps entwickeln mit Android Studio


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo

 
 


Copyright © Rheinwerk Verlag GmbH 2016
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.

 
Nutzungsbestimmungen | Datenschutz | Impressum

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