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 Sprachbeschreibung
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Exceptions
7 Generics<T>
8 Äußere.innere Klassen
9 Besondere Klassen der Java SE
10 Architektur, Design und angewandte Objektorientierung
11 Die Klassenbibliothek
12 Bits und Bytes und Mathematisches
13 Datenstrukturen und Algorithmen
14 Threads und nebenläufige Programmierung
15 Raum und Zeit
16 Dateien, Verzeichnisse und Dateizugriffe
17 Datenströme
18 Die eXtensible Markup Language (XML)
19 Grafische Oberflächen mit Swing
20 Grafikprogrammierung
21 Netzwerkprogrammierung
22 Verteilte Programmierung mit RMI
23 JavaServer Pages und Servlets
24 Datenbankmanagement mit JDBC
25 Reflection und Annotationen
26 Dienstprogramme für die Java-Umgebung
A Die Begleit-DVD
Stichwort
Ihre Meinung?

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

Java ist auch eine Insel
geb., mit DVD
1482 S., 49,90 Euro
Rheinwerk Computing
ISBN 978-3-8362-1506-0
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 Die Klasse Point
  Pfeil 3.3 Die UML (Unified Modeling Language) *
    Pfeil 3.3.1 Hintergrund und Geschichte zur 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 Garbage-Collector (GC) – Es ist dann mal weg
    Pfeil 3.4.3 Deklarieren von Referenzvariablen
    Pfeil 3.4.4 Zugriff auf Variablen und Methoden mit dem ».«
    Pfeil 3.4.5 Konstruktoren nutzen
  Pfeil 3.5 Mit Referenzen arbeiten, Identität und Gleichheit
    Pfeil 3.5.1 Die null-Referenz
    Pfeil 3.5.2 null-Referenzen testen
    Pfeil 3.5.3 Zuweisungen bei Referenzen
    Pfeil 3.5.4 Methoden mit nicht-primitiven Parametern
    Pfeil 3.5.5 Identität von Objekten
    Pfeil 3.5.6 Gleichheit und die Methode »equals()«
  Pfeil 3.6 Kompilationseinheiten, Imports und Pakete schnüren
    Pfeil 3.6.1 Volle Qualifizierung und import-Deklaration
    Pfeil 3.6.2 Mit import p1.p2.* alle Typen eines Pakets erreichen
    Pfeil 3.6.3 Hierarchische Strukturen über Pakete
    Pfeil 3.6.4 Die package-Deklaration
    Pfeil 3.6.5 Unbenanntes Paket (default package)
    Pfeil 3.6.6 Klassen mit gleichen Namen in unterschiedlichen Paketen *
    Pfeil 3.6.7 Compilationseinheit (Compilation Unit)
    Pfeil 3.6.8 Statischer Import
    Pfeil 3.6.9 Eine Verzeichnisstruktur für eigene Projekte *
  Pfeil 3.7 Arrays
    Pfeil 3.7.1 Deklaration von Arrays
    Pfeil 3.7.2 Arrays mit Inhalt
    Pfeil 3.7.3 Die Länge eines Arrays über das Attribut length auslesen
    Pfeil 3.7.4 Zugriff auf die Elemente über den Index
    Pfeil 3.7.5 Array-Objekte mit new erzeugen
    Pfeil 3.7.6 Fehler bei Arrays
    Pfeil 3.7.7 Die erweiterte for-Schleife
    Pfeil 3.7.8 Arrays mit nicht-primitiven Elementen
    Pfeil 3.7.9 Mehrdimensionale Arrays *
    Pfeil 3.7.10 Vorinitialisierte Arrays *
    Pfeil 3.7.11 Mehrere Rückgabewerte *
    Pfeil 3.7.12 Methode mit variabler Argumentanzahl (Vararg)
    Pfeil 3.7.13 Klonen kann sich lohnen – Arrays vermehren *
    Pfeil 3.7.14 Feldinhalte kopieren *
    Pfeil 3.7.15 Die Klasse Arrays zum Vergleichen, Füllen und Suchen nutzen
  Pfeil 3.8 Der Einstiegspunkt für das Laufzeitsystem: »main()«
    Pfeil 3.8.1 Kommandozeilenargumente verarbeiten
    Pfeil 3.8.2 Der Rückgabewert von »main()« und »System.exit()«
  Pfeil 3.9 Annotationen
    Pfeil 3.9.1 Annotationstypen @Override, @Deprecated, @SuppressWarnings
  Pfeil 3.10 Zum Weiterlesen


Rheinwerk Computing - Zum Seitenanfang

3.5 Mit Referenzen arbeiten, Identität und Gleichheit  Zur nächsten ÜberschriftZur vorigen Ü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.


Rheinwerk Computing - Zum Seitenanfang

3.5.1 Die null-Referenz  Zur nächsten ÜberschriftZur vorigen Ü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. [null verhält sich also so, als ob es Untertyp jeden anderen Typs wäre. ] Daher ist Folgendes gültig:

Point  p = null;
String s = null;
System.out.println( 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. [Hier unterscheiden sich C(++) und Java. ]

Mit null lässt sich eine ganze Menge machen. Der Haupteinsatz sieht vor, damit uninitialisierte Referenzvariablen zu kennzeichnen, also auszudrücken, dass eine Referenzvariable auf kein Objekt verweist. In Listen oder Bäumen kennzeichnet null aber auch das Fehlen eines gültigen Nachfolgers; 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. 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 [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.2  NullPointer.java

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

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

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

Die Laufzeitumgebung teilt uns in der Fehlermeldung mit, dass sich der Fehler, die NullPointerException, in Zeile 10 befindet.


Rheinwerk Computing - Zum Seitenanfang

3.5.2 null-Referenzen testen  Zur nächsten ÜberschriftZur vorigen Ü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 initialisert
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:

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

Die Bedingung testet, ob s überhaupt auf ein Objekt verweist und ob die Länge größer 0 ist. 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.length() 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.[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. ]



Rheinwerk Computing - Zum Seitenanfang

3.5.3 Zuweisungen bei Referenzen  Zur nächsten ÜberschriftZur vorigen Ü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.

Wir wollen uns dies an einem Punkt-Objekt näher ansehen, das wir unter einem alternativen Variablennamen ansprechen wollen:

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.


Beispiel Dies hat zur Konsequenz, dass bei einer Änderung des Punkt-Objekts über die in der Variablen p gespeicherte Referenz die Änderung auch bei einem Zugriff über die Variable q beobachtet werden kann:

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


Rheinwerk Computing - Zum Seitenanfang

3.5.4 Methoden mit nicht-primitiven Parametern  Zur nächsten ÜberschriftZur vorigen Ü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:

Listing 3.3  InitPoint.java

import java.awt.*;

public class InitPoint
{

  static void clear( Point p )
  {
    p.setLocation( 0, 0 );
  }

  public static void main( String[] args )
  {
    Point q = new Point( 47, 11 );   // Koordinaten gesetzt auf (x=47,y=11)
    clear( q );
    System.out.println( q.x );     // 0
  }
}

In dem Moment, in dem main() die statische Methode clear() aufruft, gibt es sozusagen die Namen q und p für das Objekt, wobei nur clear() das Objekt unter p kennt und main() nicht und clear() von dem Namen q keine Ahnung hat.


Hinweis Der Name einer Parametervariablen darf durchaus mit dem Namen einer lokalen Variablen übereinstimmen, was die Semantik nicht verändert. In unserem Fall hätte clear() die Point-Variable auch q nennen können.


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. Daher hat auch die folgende statische Methode keine Nebenwirkungen:

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

Nach der Zuweisung referenziert die Variable p ein anderes Punkt-Objekt, und das an die Methode übergebene Argument geht verloren. Diese Änderung wird nach außen hin nicht sichtbar, was bedeutet, dass der Aufrufer kein neues Objekt unter sich hat.

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


Rheinwerk Computing - Zum Seitenanfang

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

Der Vergleichsoperator == ist für alle Datentypen so definiert, dass er die vollständige Übereinstimmung zweier Werte testet. Bei primitiven Datentypen ist das einfach einzusehen und bei Referenztypen im Prinzip genauso. Der Operator == testet bei Referenzen, ob diese übereinstimmen, also auf das gleiche Objekt verweisen. 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 == keine Rolle.


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 );
if ( p == q )    // wahr, da p und q dasselbe Objekt referenzieren
  ...
if ( p == r )    // falsch, 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.


Rheinwerk Computing - Zum Seitenanfang

3.5.6 Gleichheit und die Methode »equals()«  topZur vorigen Überschrift

Die allgemein gü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.


Beispiel Zwei inhaltlich gleiche Punkt-Objekte, verglichen mit == und equals():

Point p = new Point( 10, 10 );
Point q = new Point( 10, 10 );

  ...
if ( 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:


Tabelle 3.1  Identität und Gleichheit von Objekten

Identität/Gleichheit Getestet mit Implementierung

Identität der Referenzen

==

nichts zu tun

Gleichheit der Zustände

equals()

abhängig von der Klasse


Es gibt immer ein »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.

Die Unterklasse Point überschreibt equals(), wie die API-Dokumentation zeigt. Werfen wir einen Blick auf die equals()-Methode aus Point, um eine Vorstellung von der Arbeitsweise zu bekommen:

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.

Die Oberklasse »Object« und ihr »equals()« *

Wenn eine Klasse keine equals()-Methode angibt, dann erbt sie eine Implementierung aus der Klasse Object, die wie folgt aussieht:

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.


Hinweis Der Datentyp für den Parameter in der equals()-Methode ist immer Object und niemals etwas anderes, da sonst equals() nicht überschrieben, sondern überladen wird. Folgendes für eine Klasse C ist also falsch:

public class C
{
  private int v;
  public boolean equals( C that ) { return this.v == that.v; }
}

Im Vokabular der Informatiker gesprochen: Java unterstützt bisher keine kovarianten Typ-Parameter, wohl aber seit Java 5 kovariante Rückgabetypen.




Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen. >> Zum Feedback-Formular
 <<   zurück
 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 Bibliotheken






 Java SE Bibliotheken


Zum Katalog: Professionell entwickeln mit Java EE 7






 Professionell
 entwickeln mit
 Java EE 7


Zum Katalog: Einstieg in Eclipse






 Einstieg in
 Eclipse


Zum Katalog: Einstieg in Java






 Einstieg in
 Java


 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich
InfoInfo




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

Cookie-Einstellungen ändern