3 Häufig gepostete Fragen

3.1 [LANG] Die Sprache Java

3.2 [STRING] Strings

3.3 [IO] Eingabe/Ausgabe, Streams, etc.

3.4 [NET] Netzwerk

3.5 [AWT] Abstract Window Toolkit

3.6 [SWING] Swing, das bevorzugte GUI

3.7 [APPLET] Java-Applets und ihre Zusammenarbeit mit Browsern

3.8 [SERVER] Servlets und andere Server-Implementierungen in Java

3.9 [NONCORE] Klassen/Packages, die über den Kern der Sprache hinausgehen, also Java3D etc.

3.10 [OOP] OOP-Konzepte und Patterns in Java

3.11 [JDK] Virtuelle Maschinen, alles über JDKs und deren Installation und Verwendung

3.12 [TOOLS] Java-Zusatz-Tool, zum Beispiel IDEs, Build-Tools, Profiler, etc.

3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt

3.14 [ERROR] Fehlermeldungen


3.1 [LANG] Die Sprache Java

Zurück zu "3 Häufig gepostete Fragen"

3.1.1 Gibt es in Java keine Zeiger wie in C++?

Im Prinzip gibt es ein Konstrukt, das den Zeigern anderer Programmiersprachen, wie zum Beispiel C++, sehr ähnlich ist: die sogenannten Referenzen. Referenzen können, im Vergleich zu Zeigern in C++, nicht manipuliert werden; die sogenannte Zeigerarithmetik, bei der man Zeiger auf beliebige Speicherinhalte zeigen lassen kann, wurde aus Sicherheitsgründen nicht in Java übernommen. Der Inhalt einer Referenz kann einer anderen Referenz zugewiesen werden, Referenzen können miteinander verglichen werden. Wird in Java eine Objektvariable angelegt, so ist dies nichts weiter als ein Speicherplatz für eine Referenz für ein Objekt des angegebenen Typs. Der new-Operator erzeugt das eigentliche Objekt und liefert die Referenz darauf zurück, die dann in der Objektvariablen gespeichert wird.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.2 Warum ist Referenz nicht gleich Referenz?

Problem:

public class Test {
    public static void main (String[] args) {
        String a = "A";
        String x = a;
        System.out.println(x);
        a = "B";
        System.out.println(x);
    }
}

Die Ausgabe sollte doch eigentlich so aussehen:

    A
    B

Denn die Variable x speichert doch eine Referenc auf den String a! Im ersten Fall hat a den Wert A und damit auch x, das ja auf diesen String verweist. Im zweiten Fall wird a geändert und damit müßte sich doch auch eigentlich der Wert von x ändern, weil x auf a verweist. Die Ausgabe, die das Programm liefert, ist jedoch:

    A
    A

was eigentlich nicht in das Bild einer normalen Referenz passt.

Ursache:

Der Fehler liegt darin begründet, daß a nicht der eigentliche String ist, sondern nur eine Referenz auf diesen String. Durch x = a referenzieren beide Variablen den selben String und durch a = "B" verweist a auf einen anderen neuen String mit dem Wert B. Dies ändert jedoch nichts an der Referenz, die in x gespeichert ist. Somit ist die Ausgabe völlig korrekt.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.3 Wie werden in Java Funktionsparamter übergeben, by value oder by reference?

Java kennt (genauso wie C, aber im Gegensatz zu etwa Pascal) kein "Call by Reference". Wenn Objekte als Parameter verwendet werden, wird eben die Referenz "by Value" übergeben. Somit kann man zwar das Objekt (so es veränderbar ist) verändern, aber nicht die originale Variable, die als Parameter verwendet wurde.

Beispiel für Call by Reference:
(Pascal - [Syntax bestimmt nicht korrekt])

program ReferenzTest(input, output);

  var
  x, y : integer;

  procedure swap (var a: integer; var b: integer);
    var
    h : Integer;

  begin
    h := a;
    a := b;
    b := h;
  end;

begin
  x := 1;
  y := 2;
  swap(x,y);
  writeLn('x = ', x, ', y = ', y , '.');
end.

Das Programm gibt am Ende x = 2 und y = 1 aus. In Java geht das nicht:

package de.dclj.faq;
class CallByReferenceTest
{
    static void swap (int a, int b)
    {
        int h = a;
        a = b;
        b = h;
    }

    public static void main(String[] test)
    {
        int x = 1;
        int y = 2;
        swap(x,y);
        System.out.println("x = " + x + ", y = " + y);
    }
}

Zur Call-by-Reference-Simulation bietet sich die Verwendung von Arrays an - da diese Objekte sind, wird ja nur die Referenz übergeben.

class CallByReferenceSimulation
{
    static void swap(int[] a, int[] b)
    {
        int h = a[0];
        a[0] = b[0];
        b[0] = h;
    }

    public static void main(String[] test)
    {
        int[] x = {1};
        int[] y = {2};
        swap(x,y);
        System.out.println("x = " + x[0] + ", y = " + y[0]);
    }
}

Es gibt auch eine englisch sprachige Web-Site zu diesm Thema:

Autor: Paul Ebermann

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.4 Warum gibt es in Java keine Destruktoren wie in C++?

In Java hat der Programmierer unter normalen Umständen keinen direkten Einfluß darauf, wann Objekte aus dem Speicher entfernt werden. So ist es unter anderem nicht möglich, ein Objekt per Befehl aus dem Speicher zu löschen. Dies erledigt der sogenannte Garbage-Collector, der Objekte aus dem Speicher entfernt, auf die keine Referenz mehr verweist. Dadurch wird verhindert, daß der Programmierer mehr oder minder mutwillig Speicherfehler erzeugen kann, die das Programm zum Absturz bringen könnten. Wann die Entfernung aus dem Speicher erfolgt, liegt im Ermessen des Computers. Es gibt also keinen definierten Zeitpunkt, wann ein Objekt nicht mehr existiert und deshalb ist es in den meisten Fällen nicht sinnvoll, Operationen zu definieren, die beim Löschen des Objektes ausgeführt werden. Man könnte (ungültige) Annahmen voraussetzen - zum Beispiel bei verketteten Listen, da&szzlig; das Nachfolgerelement noch existiert, obwohl dies gar nicht der Fall ist - und damit Fehler verursachen. Destruktoren wie in C++ existieren deshalb nicht. Ist es aus irgendeinem dringenden Grund dennoch nötig, Operationen beim Löschen des Objektes auszuführen, so kann man eine Methode finalize definieren, die bei der Speicherbereinigung abgearbeitet wird. Wobei nicht garantiert wird die finalize methode überhaubt von der JVM abgearbeitet wird.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.5 Warum funktioniert die equals Methode nicht?

Problem 1:
Man hat eine eigene Klasse entworfen und möchte nun testen, ob zwei Objekte gleich sind. Fein, denkt man sich, dafür bietet die Klasse Object ja die Methode equals(Object obj). Doch das Programm mit Verwendung der equals(Object obj) Methode wird zwar korrekt übersetzt, der Vergleich funktioniert jedoch nicht so, wie er eigentlich sollte.

Per Default sagt equals(), dass es sich um die selbe Instanz einer Klasse handelt:

public class Object {
    ...

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

    ...
}

Lösung 1:
Die Methode equals(Object obj) muß für jede Klasse neu überschrieben werden. Schließlich kann man nicht in allgemeiner Weise die Gleichheit zweier Objekte einer Klasse spezifizieren. Das ist insbesondere dann wichtig falls die Objekte in einer Set oder als Keys für eine Map verwendet werden. Dann muss allerdings auch die Methode hashCode() überschrieben werden. Siehe dazu auch die nächste Frage in dieser FAQ!

Dazu sollte man sich auf alle Fälle in der JavaDoc zu Object, speziell die Methoden hashCode() und equals(Object obj) ansehen:

Für Strings beispielsweise sind equals() und hashCode() bereits überschrieben. So dass String-Objekte als Keys verwandt werden können.

Problem 2:

public class Test {

    private int a;
    private int b;

    public Test (int a , int b) {
        this.a = a;
        this.b = b;
    }

    public boolean equals(Test other) {
        return (this.a == other.o) && (this.b == other.b);
    }
}

Verwendet man nun Objekte dieser Klasse in Containerklassen und hier insbesondere Methoden, die auf die Methode equals des Objekts zurückgreifen, so funktioniert dies nicht. Der Grund liegt in der falschen Signatur der Methode equals. Der Parameter muß vom Typ Object sein und nicht vom Typ der Klasse, zu dem die Methode gehört. Ansonsten existieren für die Klasse Test zwei Versionen der equals Methode: Eine, die von der Klasse Object geerbt wurde und die als Parametertyp auch Object besitzt und als zweite die oben definierte. Containerklassen verwenden aber die erste und da diese nicht verändert wurde, wird nicht das gewünschte Ergebnis erzielt.

Lösung 2:

public class Test {

    private int a;
    private int b;

    public void Test (int a , int b) {
        this.a = a;
        this.b = b;
    }

    //Die hash-Funktion ist aus dem Buch "Extereme Java" von
    //Joshua Bloch.
    public int hashCode() {
        int result = 17;
        result = 37*result + this.a;
        result = 37*result + this.b;
        return result;
    }

    public boolean equals(Object obj) {
        //Für eine bessere Performance.
        if (this == obj) {
            return true;
        }
        //Wenn (obj == null) dann gibt instanceof false zurück
        //Siehe JLS 15.20.2
        if (!(obj instanceof Test)) {
            return false;
        }
        Test other = (Test)obj;
        return (this.a == other.a) && (this.b == other.b);
    }
}

Autor: Markus Reitz, Martin Erren

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.6 Wenn ich eigene Objekte mit einer Hashtable/HashMap verwalte, kommt es zu sonderbaren Effekten. Wieso?

Damit eigene Objekte als Schluessel in einer Hashtable/HashMap funktionieren, muessen zwei Bedingungen erfuellt sein:

  1. Wenn man die equals-Methode einer Klasse überschreibt, sollte man beachten, dass man auch die hashCode-Methode überschreiben muß!
  2. Nichts, was equals-Vergleiche beeinflusst, darf geaendert werden, waehrend das Objekt in einer Hashtable/HashMap ist! Insbesondere darf sich der Hashcode nicht aendern.

Man muss sicherstellen, dass Objekte, die laut der equals-Methode gleich sind, auch einen identischen Hashcode haben müssen. Der Umkehr-Schluss, dass ungleiche Objekte (bei denen equals false liefert) zwangsläufig auch unterschiedliche Hashcodes haben müßen, gilt nicht. Trotzdem sollte man als Programmierer versuchen, möglichst darauf hinzuarbeiten (und keineswegs z.B. für alle Objekte einen gleichen, konstanten Hashcode liefern!), damit die Implementierungen von Hashtable und HashMap effizient arbeiten können.

Als Folge dieser Forderung sollten zur Berechnung des Hashcode genau die Attributwerte einbezogen werden, die auch zur equals-Bestimmung verwendet werden, insbesondere aber keine anderen Werte!

Die Forderung kann auch so formuliert werden:

    a.equals(b) => (a.hashCode() == b.hashCode())

oder

    (a.hashCode() != b.hashCode()) => !a.equals(b)

Eine Hashtable funktioniert vereinfacht folgendermassen:

Die Schluessel-Wert-Paare werden beim Einfuegen in Buckets ("Eimer") verteilt. Dabei entscheidet der Hashcode des Schluessels, in welchen Bucket er kommt.

Beim Suchen wird anhand des Hashcodes des Suchschluessels der Bucket ermittelt, in dem das gesuchte Objekt liegen muss. So können (außer in ungünstigen Fällen) fast alle Schluessel der Tabelle ausgeschlossen werden, der Suchschluessel muss nur noch innerhalb des Buckets gesucht werden; falls er (genauer: ein Schluessel, dessen equals-Vergleich mit dem Suchschluessel true ergibt) dort gefunden wird , wird der Wert zurueckgegeben.

Dies funktioniert deshalb, weil aufgrund der obigen Forderung nur diejenigen Objekte uebereinstimmen koennen, die auch im Hashcode uebereinstimmen (equals => gleicher Hashcode).

Ein Problem ergibt sich, wenn nach dem Einfuegen ein Schluessel geaendert wird. Da sich dadurch auch dessen Hashcode (mit ziemlicher Sicherheit) aendert, liegt er nun im falschen Bucket. Die Hashtable bekommt von der Aenderung ja nichts mit!

Deshalb ist zu beachten, dass sich Schluessel nicht aendern, solange sie in einer Hashtable verwendet werden. Sichergestellt werden kann dies nur durch immutalbe Objekte (siehe 3.10.7).

Beispiel fuer das richtige Ueberschreiben von hashCode:

public class Name {
    private static int zaehler= 0;

    private String     vorname
    private String     nachname;
    private final int  id = zaehler++;

    public Name(String vorname, String nachname) {
        this.vorname = vorname;
        this.nachname = nachname;
    }

    public boolean equals(Object o) {
        // die id ist nur fuer interne Zwecke und hat keinen Einfluss
        // auf Gleichheit
        if(o instanceof Name) {
            Name n = (Name)o;
            return vorname.equals(n.vorname) && nachname.equals(n.nachname);
        } else {
            return false;
        }
    }

    public int hashCode() {
        // Es werden genau die Werte einbezogen, die auch in der
        // equals-Methode verwendet werden
        return vorname.hashCode() + nachname.hashCode();
    }
}

Autor: Ingo R. Homann, Gerhard Bloch

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.7 Was bedeutet das Schlüsselwort final?

Klassen oder Methoden, die das Schlüsselwort final tragen, können nicht mehr überschrieben werden, wenn von dieser Klasse abgeleitet wird. Die Verwendung dieses Schlüsselwortes bietet sich aus zwei Gründen an:

Sicherheitsmaßnahmen werden realisiert, weil es nicht möglich ist, die Bedeutung der Methode in abgeleiteten Klassen zu verändern und damit bestehende Konzepte zu durchbrechen. Codeoptimierung deshalb, weil der Compiler nun davon ausgehen kann, daß sich an den Methoden nichts mehr ändern wird und deshalb elegantere Codeoptimierungen möglich sind. In Anbetracht der Tatsache, daß final-Methoden nicht mehr verändert werden können, muß man sich beim Programmentwurf sehr sicher sein, daß das Feature des Überschreibens definitiv nicht für diese Methode benötigt wird, ansonsten kann es bei Verbesserungen des Codes zu Problemen kommen.

Wenn Variablen mit dem Schlüsselwort final deklariert werden, hat das zur Folge, dass ihr Wert nur einmal zugewiesen (initialisiert) und dann nicht mehr verändert werden kann. Wenn der Compiler das nicht nachweisen kann, gibt es einen Fehler.

Autor: Markus Reitz, Paul Ebermann

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.8 Warum wird der dynamische Parametertyp bei überladenen Funktionen nicht beachtet?

Dies ist ein korrektes Verhalten gemäß der Java-Sprachspezifikation. Wird auf ein Objekt eine Methode angewendet, dann wird durch den Typ des Objekts entschieden, welche Methode aufgerufen werden soll. Dabei ist es egal, wie der Typ der Referenz ist, die auf das Objekt zeigt. Dieses Verhalten ist der sogenannte Polymorphismus (Vielgestaltigkeit). Zu beachten ist, daß diese dynamische Auswahl nur bei der Methodenauswahl geschieht. Bei polymorphen Funktionen entscheidet nicht der Typ des an die Methode übergebenen Objektes über die auszuwählende Methode, sondern der Typ der Referenz, die auf das Objekt verweist, wird zur Auswahl der Methode herangezogen. Der Compiler muß zur Compilierzeit entscheiden, welche Methode ausgewählt wird und eine Aussage kann er deshalb nur über den Typ der Referenz machen, da dies das Einzige ist, was zur Compilierzeit definitiv feststeht.

Das Ganze demonstriert ein Beispielprogramm:

public class BasisKlasse {}

public class AbgeleiteteKlasse extends BasisKlasse {}

public class Test {
    public static void methode(BasisKlasse eineKlasse) {
        System.out.println("Methode mit BasisKlasse!")
    }

    public static void methode(AbgeleiteteKlasse eineKlasse) {
        System.otu.println("Methode mit AbgeleiteteKlasse!")
    }
    public static void testMethode(BasisKlasse a) {
        if (a instanceof AbgeleiteteKlasse) {
            System.out.print("Abgeleitet: ");
        } else {
            System.out.print("Basis: ");
        }

        //Welche Methode wird jetzt gerufen?
        Methode(a);
    }
    public static void main (String[] params) {
        BasisKlasse a = new BasisKlasse();
        AbgeleiteteKlasse b = new AbgeleiteteKlasse();
        TestMethode(a);
        TestMethode(b);
    }
}

Das Programm erzeugt folgende Ausgabe:

    Basis: Methode mit BasisKlasse!
    Abgeleitet: Methode mit AbgeleiteteKlasse!

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.9 Was bedeutet super()?

Werden Ableitungen von Klassen gebildet und dabei Funktionen redefiniert (überschrieben), so ist es in vielen Fällen nötig, auf die Funktionalität der Basisklasse zurückzugreifen. Mit super.methode() teilt man mit, daß man die Methode der Basisklasse und nicht die Methode der aktuellen Klasse benutzen will. Innerhalb eines Konstruktors ist es möglich mit super() den Konstruktor der Basisklasse aufzurufen. Findet im Konstruktor kein expliziter Aufruf mit super() statt, so wird automatisch der parameterlose Konstruktor (Standardkonstruktor) der Basisklasse aufgerufen.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.10 Was sind anonyme Arrays?

Ab Java 1.1 ist folgender Code gültig:

int i[];
i = new int[] {1, 2, 3};

Es ist jetzt also möglich, Arrays auch außerhalb der Definition mit den gewünschten Werten zu initialisieren, indem man mit {...} einfach die Werte angibt, die das neue Array tragen soll.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.11 Gibt es in Java einen Prä-Prozessor wie in C++?

Nein. In C++ existiert der sogenannte Prä-Prozessor, der es mit bestimmten Kommandos erlaubt, Teile des Codes zu übersetzen und andere Teile bei der Übersetzung zu überspringen. Unter Umständen wäre es hilfreich, wenn auch Java eine solche Möglichkeit der Compilationssteuerung zulassen würde, doch einen Prä-Prozessor gibt es hier nicht. Man kann dies aber, zumindest ansatzweise, mit if-Statements nachbilden. Dazu definiert man eine boolesche Variable DEBUG und in Abhängigkeit von dieser Variablen sollen bestimmte Codeteile ausgeführt werden, andere dagegen nicht.

public class Test {
    final static boolean DEBUG = true;

    public static void main (String[] params) {
        int i = 12;
        if (DEBUG) {
            System.out.println("Der Wert von i ist " + i);
        }
    }
}

Verwendet man nun noch Optimierer, die unbenutzten Code aus den Klassendateien entfernen, so hat man im Prinzip ein ähnliches Verhalten wie beim Prä-Prozessor von C++. In diesem Zusammenhang ist es noch erwähnenswert, daß das Verfahren nur bei der if-Abfrage möglich ist, denn das Java-System prüft normalerweise darauf, ob Codezeilen erreicht werden können oder nicht und gibt gegebenenfalls Fehlermeldungen aus. Das if-Statement ist jedoch wie oben beschrieben erweitert worden, um das gewünschte Verhalten simulieren zu können. Dahingegen wird folgender Code mit einer Fehlermeldung quittiert:

public class Test {
    final static boolean DEBUG = true;

    public static void main (String[] params) {
        int i = 12;
        while (DEBUG) {
            System.out.println("Der Wert von i ist " + i);
        }
    }
}

Denn es würde sich je nach Zustand von DEBUG eine Endlosschleife ergeben.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.12 Existiert der const Modifizierer von C++ auch in Java?

C++ kennt das Schlüsselwort const, das es erlaubt, konstante Objekte zu definieren, deren Wert man nicht ändern kann. Auf ein solches Objekt kann man nur Methoden anwenden, die ebenfalls als const definiert sind, die also die Daten des Objektes nicht ändern können.

Ab Java 1.1 können Argumente mit dem Modifizierer final als konstant definiert werden. Bei einer Objektreferenz bedeutet dies allerdings nur, daß die Referenz konstant bleibt, nicht aber das Objekt, auf das die Referenz verweist.

Da Java den Modifizierer const nicht kennt, ist es aber trotzdem recht einfach möglich, diesen nachzubilden. Alles, was man dazu braucht, ist ein Interface, das die konstanten Methoden enthält, die das Objekt nicht ändern können. Will man nun ein konstantes Objekt zurückgeben oder erzeugen, dann gibt man einfach eine Referenz vom Typ des Interfaces zurück und schon hat man das Gewünschte erzielt.

Folgendes Beispiel soll das Ganze etwas verdeutlichen:

interface KonstanterTyp {
    public int get();
}

public class NichtKonstanterTyp implements KonstanterTyp {

    int i;

    public void set(int i) {
        this.i = i;
    }

    public int get() {
        return this.i;
    }
}

Ein konstantes Object wird dann durch

    KonstanterTyp A = new NichtKonstanterTyp();

erzeugt.

Anmerkung: Im Prinzip wird hier durch das Interface eine Art Untermenge der Klasse NichtKonstanterTyp definiert. Bei C++ läuft dieser Prozeß im Prinzip auch so ab, nur wird hier vom Compiler automatisch diese Untermenge durch den Modifizierer const erzeugt und der Programmierer muß sich hierum nicht kümmern. In Java muß man diesen Prozeß selbst durchführen.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.13 Wie kann man Referenzen von Übergabeparametern ändern?

Von Sprachen wie C++ oder Pascal kennt man die Möglichkeit, Referenzen, die an die Methode übergeben werden, innerhalb der Methode zu ändern, an die sie übergeben wurden, wodurch nach dem Aufruf der Methode die Referenzen auf andere Objekte verweisen.

In Java gibt es zwei Möglichkeiten, diesen Effekt zu erreichen:

Der erste Punkt dürfte klar sein, jedoch steht der Aufwand erst dann in einem vernünftigen Verhältnis zum Ergebnis, wenn diese Art der Parametermodifikation öfter innerhalb des Programms vorkommt, denn ansonsten lohnt sich das Design einer speziellen Klasse nicht unbedingt. Den zweiten Weg verdeutlicht das folgende Programm:

public class Test {
    public void parameterModifikation(Object[] paramter) {
        parameter[0] = "Neue Referenz";
    }
    public static void main (String[] args) {
        Objcet parameter[] = new Object[1];
        parameter[0] = "Zu modifizierender Parameter."
        Test test = new Test();
        System.out.println(parameter[0]);
        test.parameterModifikation(parameter);
        System.out.println(parameter[0]);
    }

}

Das Beispielprogramm liefert folgende Ausgabe:

    Zu modifizierender Parameter
    Neue Referenz

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.14 Wie erzeuge ich eine tiefe Kopie eines Objektes mit möglichst wenig Aufwand?

Weist man einer Objektvariablen eine andere zu, so wird nur die Referenz kopiert und beide Objektvariablen können das Objekt modifizieren und diese Modifikation auch sehen. Dies ist das Standardverhalten von Java. Durch clone() wird z.B. eine flache Kopie von dem Vector angelegt, d.h. z.B. das Hinzufügen eines neuen Elements in die Kopie des Vektors ist im Original-Vector nicht sichtbar. Die enthaltenen Objekte jedoch werden *nicht* mitkopiert, d.h. Änderungen an den enthaltenen Objekten sind in beiden Vectoren sichtbar. Wenn wirklich alles - also auch die enthaltenen Objekte und die rekursiv darin enthaltenen Objekte - kopiert werden soll, dann braucht man eine sog. tiefe Kopie.

Ein sehr eleganter Weg, eine tiefe Kopie eines Objektes zu erzeugen, verwendet den Serialisierungs-Mechanismus. Objekte, die man mit diesem Verfahren kopieren möchte, müssen also das Interface Serializable implementieren.

import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.IOException;

public class TiefeKopie {
    public static Object kopiere(Object einObjekt)
            throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);

        oos.write(einObjekt);

        ByteArrayInputStream bais =
                new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);

        return ois.readObject();
    }
}

Zuerst werden zwei Ausgabeströme angelegt: Ein Byte-Strom und ein Object-Strom. Der Byte-Strom wird hinter den Object-Strom geschaltet und das zu kopierende Objekt wird auf dem Object-Strom ausgegeben und durch die Verknüpfung der beiden Ströme schließlich in den Byte-Strom geschrieben. Dieser Byte-Strom wird dann mit einem zweiten Strom wieder eingelesen und mit einem weiteren Object-Strom wird aus den einzelnen Bytes wieder ein Objekt rekonstruiert. Die Ausgabe des Object-Stroms ist dann das kopierte Objekt.

Die Anwendung der Klasse zeigt folgendes Codefragment:

public class Test {
    public static void main(String[] args) {
        int einArray[] = {56, 42, 67, 90, 12, 45};
        int tiefeKopieVonEinArray = (int[]) TiefeKopie.kopiere(einArray);
    }
}

Ein Array ist in Java nichts weiteres als ein Objekt und implementiert auch das Serializeable-Interface, weshalb das obige Kopierverfahren problemlos greifen kann.

Anmerkung: Bei komplexen Objekten, die viele Referenzen auf andere Objekte besitzen schlägt das Verfahren meist fehl. Der Serialisierungsmechanismus verwendet Rekursion, um alle referenzierten Objekte zu speichern. Wird auf Objekte referenziert, die wieder auf Objekte referenzieren usw. kann es geschehen, daß der Stack-Speicher für die Rekursion überläuft. Diese elegante Methode der tiefen Kopie kann also nur bei einfachen Objekten angewendet werden. In Sachen Performance liegt diese Lösung jedoch um einiges hinter der klasischen Lösung alle Elemente einzeln zu kopieren. Dessen sollte man sich klar sein, wenn man diese Lösung einsetzt.

Autor: Markus Reitz, Ingo R. Homann

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.15 Wie kann ich in Java eine dem Programmierer unbekannt Anzahl gleichartiger Objekte erzeugen und ihnen passende Namen zuweisen, also label1, label2 usw.?

Das ist in dieser Form nicht sinnvoll. Die bessere Lösung besteht in der Verwendung eines Arrays. In Java kann die Größe eines Arrays bei seiner Erzeugung zur Laufzeit (einmalig!) festgelegt werden. Ein Zugriff auf die einzelnen Objekte erfolgt dann über den Arrayindex:

// Deklaration
Label[] myLabels;

...

// Erzeugung und Initalisierung zur Laufzeit
int anzahl = 10;
myLabels = new Label[anzahl];

for (int i = 0; i < myLabels.length; i++) {
    myLabels[i] = new Label("Label Nr. " + i);
}

// Verwendung
myLabel[3].setBackground(Color.red);

Autor: Michael Paap, Christian Kaufhold

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.16 Wie kann ich den Typ enum aus C++ in Java umsetzen?

C++ bietet einen sogenannten Enumerationstyp. Eine Variable eines solchen Typs kann nur definierte Werte annehmen, die mit symbolischen Namen bezeichnet werden. Folgende Enumeration könnte zum Beispiel für eine Ampel verwendet werden:

    enum Ampel {ROT, GELB, GRUEN};

Java bietet diesen Typ nicht, kann diesen aber recht einfach mit einem Interface nachbilden:

interface Ampel {
    public static final int ROT = 1;
    public static final int GELB = 2;
    public static final int GRUEN = 3;
}

Ein C++ Compiler führt automatisch die Zuordnung von Variablennamen zu eindeutigen Zahlenwerten durch, in Java muß der Programmierer diesen Prozeß erledigen. Die Verwendung differiert zwischen Java und C++:

    Ampel eineVariable = ROT; // Das ist C++
    int eineVariable = Ampel.ROT; // Das ist Java

Man erkennt einen großen Nachteil der Java-Version: Da es sich um eine int-Variable handelt, ist es prinzipiell möglich, jeden Wert an diese Variable zuzuweisen, eben nicht nur Werte aus dem Wertebereich rot, gruen und gelb. Im Gegensatz dazu läßt ein C++-Compiler nur Zuweisungen von Symbolen aus dem Enumerationstyp zu. Insofern ist die Nachbildung in Java weniger sicher als das C++-Pendant und sollte daher mit Vorsicht angewendet werden. Sprich sie ist nicht Typsicher!

Wie schon oben angesprochen, bietet diese direkte Implementierung den Nachteil, daß sie nicht typsicher ist. Doch es ist recht einfach möglich, mit Java eine typsichere Implementierung zu erhalten:

public final class Ampel {

    private String name;
    public final int ord;
    private static int obereGrenze = 0;

    private Ampel(String name) {
        this.name = name;
        this.ord = obereGrenze++;
    }

    public String toString() {
        return this.name;
    }

    public static int groesse() {
        return this.obereGrenze;
    }

    public static final Ampel ROT = new Ampel("Rot");
    public static final Ampel GELB = new Ampel("Gelb");
    public static final Anpel GRUEN = new Ampel("Greun");
}

Besonders interessant ist hier die Kombination von automatischer Zuweisung eines eindeutigen Zahlenwertes mit den Symbolwerten eines Strings.

Auf den ersten Blick sieht die Klassendefinition ziemlich kompliziert und unverständlich aus, das Prinzip ist jedoch nicht schwer zu verstehen:

Will man nun diesen Typ von Enumeration benutzen, so sieht das in etwa wie folgt aus:

public class Test {
    public static void main(String[] args){
        Ampel meineAmpel = Ampel.ROT;

        if (meineAmpel == Ampel.ROT) {
            System.out.println("Ampel ist " + meineAmpel + ". ");
            System.out.println("Anhalten!");
        }
        if (meineAmpel == Ampel.GELB) {
            System.out.println("Ampel ist " + meineAmpel + ". ");
            System.out.println("Motor starten -oder- Anhalten!");
        }
        if (meineAmpel == Ampel.GRUEN) {
            System.out.println("Ampel ist " + meineAmpel + ". ");
            System.out.println("Gib Gas!");
        }
    }
}

Dieser Typ der Enumeration verwendet also Referenzen und nicht, wie Version 1, int-Werte, die nicht typsicher sind. Durch die Typprüfungsmechanismen von Java wird diese Art von Enumeration vollkommen typsicher und steht dem enum Konstrukt von C++ nun in nichts mehr nach.

Wer mehr über dieses Pattern erfahren möchte sei dem sei folgender Link empfohlen:

Autor: Markus Reitz, Uwe Günther, Ulf Jährig

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.17 Kann man Mehrfachvererbung mit Java simulieren?

Java kennt im Gegensatz zu C++ nicht das Feature der Mehrfachvererbung, eine Klasse kann nur genau einen Vorfahren haben, im Gegensatz zu beliebig vielen bei Mehrfachvererbung.

Häufig liest man, daß man die Mehrfachvererbung mit Hilfe von Interfaces nachbilden kann, indem man die Methodenschnittstelle der einzelnen Klassen als Interfaces definiert und diese Interfaces alle gleichzeitig von der fraglichen Klasse implementieren läßt, denn dies ist in Java möglich.

Mit Mehrfachvererbung hat dies aber nichts zu tun, wenn man den eigentlichen Sinn und Zweck von Vererbung betrachtet. Vererbung ist eines der möglichen Prinzipien, um die Codewiederverwertung zu garantieren. Code, der in mehreren Klassen benötigt wird, ist nur in der Basisklasse aufgeführt und durch die Vererbung können die Erben auch diesen Code benutzen. Der Code steht also nur an einer Stelle. Bei der oben beschriebenen Nachbildung wird in keiner Weise Code gespart, denn die Methoden der Interfaces müssen ja von der Klasse noch implementiert werden.

In diesem Sinne kann man Mehrfachvererbung nicht in Java nachbilden.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.18 Wie realisiere ich eine variable Parameterliste?

Es gibt Anwendungsfälle, in denen es äußerst nützlich wäre, einer Methode eine variable Anzahl an Parametern übergeben zu können. Ein Anwendungsfall wäre eine Klasse beliebig-dimensionaler Vektoren. Ein Konstruktor dieser Klasse müßte die Initialisierung eines Vektors mit bestimmten Werten erlauben und da beliebig-dimensionale Vektoren von dieser Klasse verarbeitet werden, müßte der Konstruktor mit einer variablen Parameteranzahl arbeiten können. Mit einem Trick kann man eine variable Parameterliste realisieren: Man übergibt der Methode ein Array von Referenzen auf die Klasse Object. Da in Java alle Objekte von der Klasse Object abstammen - sie ist quasi die Klasse aller Klassen - kann das Array Referenzen auf beliebige Objekte aufnehmen. Innerhalb der Methode kann dann mit dem length-Feld des Arrays die Anzahl der übergebenen Parameter ermittelt werden. Mit dem instanceof-Operator von Java kann dann der Typ des Objekts ermittelt werden, auf den die Referenzen verweisen und an Hand dieser Informationen kann man dann festlegen, was getan werden soll. Ein Programmfragment verdeutlicht das bisher Gesagte:

public class Test {
    void methodeMitVariablerParameterListe(Object[] parameterList) {
        //Länge: parameterList.length ===> Anzahl der Parameter
        //Typ:   if (parameterList[i] instanceof <Type>)
    }
}

Ein Nachteil ist offensichtlich: Es kann nur genau eine Methode mit variabler Parameterliste geben bzw. innerhalb dieser einen Methode müssen alle Variationen berücksichtigt und implementiert werden, was nicht unbedingt zur Übersichtlichkeit des Programms beiträgt.

Ein Aufruf der Methode gestaltet sich nun wie folgt:

Test obj = new Test();
obj.methodeMitVariablerParameterListe(new Object[] {
        paramObject1, paramObject2, ..., paramObjectn
    });

Will man elementare Datentypen wie double oder int an die Methode übergeben, so muß man die zugehörigen Wrapper-Klassen verwenden, die diese elementaren Datentypen in Klassen einpacken.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.19 Wie realisiere ich eine Methodenauswahl nach den dynamischen Parametertypen?

Java wählt Methoden nach dem statischen Typ der Übergabeobjekte aus. Man kann jedoch eine dynamische Auswahl simulieren, indem man das oben angesprochene Prinzip der variablen Parameter hierauf überträgt. Man prüft die übergebenen Referenzen mit dem instanceof-Operator, der den dynamischen Typ des Parameters liefert. Anhand dieser Informationen kann man dann ein dynamisches Verhalten der Methodenaufrufe realisieren. Auch hier gilt, wie auch schon bei der variablen Parameterliste, daß eine einzige Methode alle Aufrufmöglichkeiten abdecken muß.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.20 Sind Methoden in Java immer virtuell?

In Java sind alle Methoden virtuell, eine Unterscheidung zwischen virtuellen und nicht-virtuellen Methoden wie zum Beispiel in C++, gibt es in Java nicht. Ist es für die Funktionsweise eines Objektes wichtig, daß die vorhandenen Methoden nicht überschrieben werden können - etwa um zu verhindern, daß sich die Funktionalität des Objekts dadurch grundlegend ändern läßt - so muß man entsprechende Methoden mit dem Modifizierer final deklarieren, der ein Überschreiben verhindert.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.21 Ich stosse ab und zu auf den Begriff "Wrapper-Klassen". Könnte mir jemand erklären was das ist?

Eine sehr gute Frage, auf die ich mal mit einem Zitat aus Go to Java 2 antworten moechte, denn sie wird zu selten gestellt:

Zitat (Goto Java 2):

Zu jedem primitiven Datentyp in Java gibt es eine korrespondierende Wrapper-Klasse. Diese kapselt die primitive Variable in einer objektorientierten Hülle und stellt eine Reihe von Methoden zum Zugriff auf die Variable zur Verfügung. Zwar wird man bei der Programmierung meist die primitiven Typen verwenden, doch gibt es einige Situationen, in denen die Anwendung einer Wrapper-Klasse sinnvoll sein kann.

Diese Klassen, wie zum Beispiel "Integer" koennen einem das Leben angenehmer gestalten, wenn man Dinge tun muss, die man mit einfachen ints tun will, aber mangels vorhandener Methoden nicht kann, weil diese Primitiven eben keine richtigen Objekte sind.

Zum Beispiel kann Integer (im Gegensatz zu int) Strings nach Zahlen parsen, oder Integers als Binaercode ausgeben oder als Hex oder in andere Zahlentypen umwandeln und vieles mehr. Vielleicht ist es fuer Dich noch interessant zu erfahren, dass diese Wrapper Klassen in der Praxis oftmals nicht instantiiert, sondern ihre Methoden statisch aufgerufen werden.

Um zum Beispiel aus dem String "42" das betreffende int zu machen, rufst Du:

String zweiundvierzig = new String ("42") ;
int answer = Integer.parseInt (zweiundvierzig) ;

Das brauchst Du zum Beispiel zum Auswerten von GUI-Zahlenfeldern. Ich hoffe, ich konnte ein wenig Licht ins Dunkel bringen. Das Verstaendnis von Wrapperklassen und deren Sinn halte ich naemlich fuer essentiell, wenn man mit OOP beginnt.

Autor: Stephan Menzel

Zurück zu "3.1 [LANG] Die Sprache Java"

3.1.22 Warum ist private nicht privat?

public class Person {

    private int kontostand;

    public Person(int kontostand) {
        this.kontostand = kontostand;
    }

    public void addGehalt(int gehalt) {
        this.kontostand += gehalt;
    }

    public void klauen(Person opfer) {
        this.kontostand += opfer.kontostand;
        opfer.kontostand = 0;
    }

    public void zeigeKontostand() {
        System.out.println("Kontostand: " + this.kontostand);
    }
}

public class Test {
    public static void main(String[] args) {
        //Der Dieb eröffnet ein Konto
        Person dieb = new Person(10);

        //Das Opfer eröffnet ein Konto
        Person opfer = new Person(50000);

        //Das Opfer bekommt Gehalt
        opfer.addGehalt(10000);

        //Der Dieb geht an die Arbeit
        dieb.klauen(opfer);

        //Das opfer ist nun pleite!!!
        opfer.zeigeKontostand();

        //Und der Dieb un 60000 Euro reicher.
        dieb.zeigeKontostand();
    }
}

Wenn man obiges Programm testet, so wird man feststellen, daß es möglich ist, die private-Datenfelder eines Objektes zu manipulieren; diese Daten sind also nicht privat im sonst üblichen Sinne. Allerdings ist die Privatsphäre nur für Objekte der gleichen Klasse aufgehoben, Objekte anderer Klassen haben keinen Zugriff auf die private-Daten von Objekten anderer Klassen. Damit ist obige Möglichkeit nicht weiter tragisch, denn der Entwickler der Klasse kann eine wie oben gezeigte Möglichkeit wirksam unterbinden. Wenn er dies nicht tut, so ist die Schuld bei ihm zu suchen, denn nur der Entwickler allein ist für das Verhalten der Klassen zuständig und nur der Entwickler muß solche Möglichkeiten durch das Design ausschließen. Die private-Deklaration ist damit als Deklaration der Privatsphäre gegenüber Objekten anderer Klassen zu sehen, zwischen Objekten der gleichen Klasse herrscht ein freundschaftliches Verhältnis, sie dürfen sich gegenseitig in die Daten schauen.

Autor: Markus Reitz

Zurück zu "3.1 [LANG] Die Sprache Java"
Hosted by www.Geocities.ws

1