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 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 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

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 Schlüsselwort new anlegen
Pfeil 3.4.2 Der Zusammenhang von new, Heap und Garbage-Collector
Pfeil 3.4.3 Deklarieren von Referenzvariablen
Pfeil 3.4.4 Jetzt mach mal ’nen Punkt: Zugriff auf Objektattribute und -methoden
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 (Gleichwertigkeit)
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 (Gleichwertigkeit) 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 Array-Fehler
Pfeil 3.8.8 Arrays als Methodenparameter
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 Array-Inhalte 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 (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 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 typenlos, kann also jeder Referenzvariablen zugewiesen und jeder Methode übergeben werden, die ein Objekt erwartet.[ 114 ](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äsenation 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.[ 115 ](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.

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 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[ 116 ](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.10NullPointer.java

public class NullPointer { // 1

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

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

String s = null; // 4

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

s.length(); // 6

} // 7

} // 8

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 NullPointer.main(NullPointer.java:5)

Die Laufzeitumgebung teilt uns in der Fehlermeldung mit, dass sich der Fehler, die NullPointerException, in Zeile 5 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.

»null« in anderen Programmiersprachen *

Ist Java eine pure 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 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, womit 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, 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.[ 117 ](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.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.

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 bieten für dieses Konstrukt eine Abkürzung über den so genannten null coalescing operator (Coalescing, zu Deutsch »verschmelzend«), der geschrieben wird mal als ?? oder als ?:, für unser Beispiel: o ?? non_null_o. Besonders hübsch ist das bei sequenziellen Tests der Art o ?? p ?? q ?? r, wo es dann sinngemäß lautet: Liefere die erste Referenz ungleich null.

Java-Programmierer kommen nicht zu diesem Glück.

 

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

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 (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 ) {

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

 

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 dasselbe 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 (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, 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 nichtidentische, 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

== bzw. !=

nichts zu tun

Gleichheit der Zustände

equals(…) bzw. ! 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 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 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 ist auch eine Insel

Java ist auch eine Insel




Zum Katalog: Java SE 9-Standard-Bibliothek

Java SE 9-Standard-Bibliothek




Zum Katalog: Professionell entwickeln mit Java EE 8

Professionell entwickeln mit Java EE 8




Zum Katalog: Entwurfsmuster

Entwurfsmuster




Zum Katalog: IT-Projektmanagement

IT-Projektmanagement




 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2017

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