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.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt

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

3.13.1 Warum rechnet Java falsch?

Problem:

public class Test {
    public static void main(String[] args) {
        float zahl = 0.0F;
        for (int i = 0; i <= 100; i++) {
            System.out.println(zahl);
            zahl += 0.1F;
        }
    }
}

Läßt man obiges Programm laufen so ergibt sich folgende Ausgabe:

    0.0
    0.1
    0.2
    0.3
    0.4
    0.5
    0.6
    0.70000005
    0.8000001
    0.9000001
    ...

Scheinbar rechnet Java an einigen Stellen falsch und ist nicht fähig, zu einer Zahl den Wert 0.1 korrekt zu addieren. Dies sieht jedoch nur so aus und ist auch keineswegs typisch für die Programmiersprache Java, sondern ein allgemeines Problem.

Zahlendarstellung im Computer:
Um dies zu verstehen, muß man sich klar machen, daß ganze Zahlen intern in Form von Binärzahlen und Fließkommazahlen mit Hilfe von Binärbrüchen dargestellt werden. Nun kann der (nicht gerade seltene) Fall eintreten, daß sich eine Zahl im Dezimalsystem zwar darstellen läßt, in Binärdarstellung jedoch zu einem unendlichen, nicht abbrechenden, Bruch wird. Für die Speicherung einer Zahl steht aber nur ein beschränkter Speicherplatz zur Verfügung, d.h. die unendliche Binärdarstellung wird nur bis zu einer gewissen Stelle gespeichert. Das Resultat sind Ungenauigkeiten. Addiert man jetzt solche Zahlen (und 0.1 ist ein Beispiel für so eine Zahl) mehrfach auf, so addieren sich die unvermeidbaren Ungenauigkeiten immer mehr auf und führen zu dem obigen Verhalten. Dies ist keineswegs charakteristisch für Java, sondern auch in jeder anderen Sprache, die Fließkommazahlen verwendet, reproduzierbar. Eine Abmilderung des Problems besteht in einer schlauen Rundung von Zwischenergebnissen an geeigneten Stellen der Berechnung, so daß Fehler kompensiert oder zumindest abgeschwächt werden.

Runden von Fließkommazahlen:
Wenige Zeilen weiter oben wurde davon gesprochen, mögliche Rechenfehler auszugleichen, indem man an geeigneten Stellen rundet. Wie aber rundet man eine Fließkommazahl? Nun, das Prinzip hat sich seit den Tagen des C-64 nicht geändert:

Berücksichtigt man, daß das Verschieben des Kommata um n Stellen nach rechts der Multiplikation der Zahl mit 10^n entspricht und die Verschiebung nach links in analoger Weise durch die Division durch 10^n erledigt wird, kann man die Rundungsfunktion wie folgt formulieren:

double runden(double d) {
    return ((int) z * Math.pow(10, n)) / Math.pow(10, n);
}

Es ist unter anderem auch in Betracht zu ziehen die Klasse java.math.BigDecimal zu verwenden, die hier beim Erzeugen einer Instanz dieser Klasse explizit den scale Factor mitangeben kann.

Autor: Markus Reitz

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.2 Wie belege ich ein Array mit einem bestimmten Wert?

Häufig möchte man Arrays mit bestimmten Werten vorbelegen, doch leider gibt es in Java keine Standardfunktion, die diese Aufgabe erledigen könnte. Der naheliegendste Weg ist die Verwendung einer for-Schleife, die der Reihe nach alle Elemente durchläuft und den gewünschten Wert in jedes Array-Element schreibt, jedoch ist dieser Weg insbesondere für große Arrays nicht unbedingt schnell. Mit Hilfe der System-Routine arraycopy kann man aber einen Algorithmus aufstellen, der diese Aufgabe recht schnell erledigt. Dabei arbeitet der Algorithmus nach dem folgenden Prinzip: Zuerst wird in das erste Feld der gewünschte Wert geschrieben. Mit Hilfe der arraycopy-Methode wird dann dieses Feld hinter die aktuelle Position kopiert. Dann wird dieser Bereich wieder hinten angehängt und nun sind schon vier Array-Einträge mit dem gewünschten Wert initialisiert. Bei erneutem Durchführen sind es acht Werte usw. D.h. die Anzahl der initialisierten Array-Einträge verdoppelt sich bei jedem Durchlauf. Handelt es sich bei der Anzahl der Elemente um eine Zweierpotenz, so ist das Array nach log_2 n Durchläufen mit dem gewünschten Wert initialisiert. Handelt es sich nicht um eine Zweierpotenz, so verbleibt noch ein uninitialisierter Bereich, welcher mit einem weiteren arraycopy-Aufruf initialisiert wird.

Das nachfolgende Beispiel zeigt die konkrete Implementation des beschriebenen Algorithmus:

public class Test {
    public static void main (String[] args) {
        //Das zu initialisierende Array.
        int array[] = new int[10000];

        //Der Index, ab dem initialisiert werden soll.
        int startPosition = 0;

        //Anzahl der zu initialisierenden Elemente.
        int anzahlDerElemente = 10000;

        //Initialisierungswert
        int wert = 2;

        int zaehler = 1;
        int aktuellePosition = startPosition + 1;

        array[startPosition] = wert;
        anzahlDerElemente--;

        while (anzahlDerElemente > zaehler) {
            System.arraycopy(array, startPosition, array,
                    aktuellePosition, zaehler);
            anzahlDerElemente -= zaehler;
            aktuellePosition += zaehler;
            zaehler << 1;
        }

        //Ist die Anzahl der Elemente keine Zweierpotenz,
        //dann muessen im letzten Schritt noch die restlichen
        //Array Einträge initialisiert werden.

        System.arraycopy(array, startPosition, array,
                     aktuellePosition, zaehler);
    }
}

Autor: Markus Reitz

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.3 Wie kann ich in Java Zufallszahlen im Bereich 0..n erzeugen?

Zur Erzeugung von Zufallszahlen bietet Java das Random-Objekt. Bevor Zufallszahlen erzeugt werden können, muß ein solches Objekt erzeugt werden, wobei ein Random-Seed genannter Zahlenwert übergeben wird, der Anteil an der Berechnung von Zufallszahlen hat. Generell muß man sagen, daß es sich bei auf dem Computer erzeugten Zufallszahlen um keine echten zufälligen Zahlenfolgen handelt, der Begriff Pseudo-Zufallszahlen trifft das Ganze besser. Grundlage dieser Zahlen sind Generator-Funktionen, die eine mehr oder minder zufällig erscheinende Folge von Zahlen erzeugen.

Für die Berechnung der Zahlen spielt der Random-Seed (quasi der Samen für die Berechnung) eine große Rolle. Wird dieser Random-Seed möglichst zufällig gewählt, so sind die resultieren-den Zahlenfolgen beinahe echte Zufallszahlen. In der Praxis hat es sich als brauchbar erwiesen, als Random-Seed die jeweils aktuelle Systemzeit zu benutzen, damit gewährleistet ist, daß sich der Seed möglichst häufig ändert.

Damit erzeugt man ein Random-Objekt am Besten wie folgt:

    Random zufall = new Random(System.currentTimeMillis());

oder

    Random zufall = new Random();

Wobei der Default-Kostruktor intern auch nur

    this(System.currentTimeMillis());

aufruft, wie die Firma Sun in ihrer Online Dokumentation schreibt.

Mit Hilfe der Member-Funktion nextInt() wird die nächste erzeugte Integerzahl geliefert. In einem Großteil der Fälle möchte man jedoch nicht Integerzahlen haben, die auch negative Werte annehmen können, sondern Zahlen im Bereich von 0 bis n. Hier kommt eine Variation der nextInt() Methode ins Spiel, deren Anwendung die folgende Zeile demonstriert:

    zahl = zufall.nextInt(n+1);

Ist einem die Saat von Random nicht zufällig genug, dann sollte man die Klasse SecureRandom aus dem Package java.security verwenden. Hier werden unter anderem je nach Plattform Betriebsystem spezifische Mechanismen gewählt, wie zum Beispiel unter UNIX /dev/urandom.

Autor: Markus Reitz, Uwe Günther

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.4 Ich komme mit dem import-Statement nicht klar, was mache ich falsch?

Problem:
In einem Programm kommen folgende beide import-Statements vor:

    import java.awt.*;
    import java.awt.event.*;

Warum reicht nicht das erste Statement aus, um die nötigen Klassen und Interfaces zu importieren?

Intuitiv würde man sagen, daß mit Hilfe des * alle Klassen und Interfaces importiert werden, die unterhalb des Pfades java.awt stehen, doch leider ist dies nicht so. Mit import java.awt.*; werden nur alle Klassen importiert, die im Verzeichnis java.awt stehen, nicht jedoch Klassen, die noch tiefer verschachtelt abgespeichert sind. Deshalb ist das zweite import-Statement nötig, welches alle Klassen in java.awt.event importiert.

Autor: Markus Reitz

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.5 Warum gibt es Probleme bei final Werten in Verbindung mit elementaren Typen?

Angenommen, eine Klasse A ist wie folgt definiert:

public class A {
    final static boolean test = true;
}

Aus irgendeinem Grund wird die Definition der Klasse A auf Folgendes geändert:

public class A {
    final static boolean test = false;
}

Die Klasse A wird neu compiliert und eine neue Klassendatei erzeugt. Alle anderen Klassen, die das in Klasse A definierte Feld verwenden, müßten nun mit dem korrigierten Wert false arbeiten. Probiert man dies in der Praxis aus, so stellt man fest, daß dies nicht der Fall ist. Der Grund liegt in der Sprachdefinition der Sprache Java. Ein primitiver (elementarer) Datentyp, der das Modifiziererpaar final static trägt, wird vom Compiler als Konstante behandelt und kann überall, wo er im Programm auftritt, vom Compiler direkt durch den Wert ersetzt werden. Um also zu erreichen, daß sich die in der Klasse A gemachte Änderung auf alle sie benutzenden Klassen auswirkt, müssen alle betroffenen Klassen neu übersetzt werden. Dies gilt aber nur, wenn der Wert tatsächlich mit einer Compile-Zeit-Konstante initialisiert wird.

Ein Ausweg aus diesem Dilemma stellt hier die Verwendung des Typ sicheren enum (type safe enum) dar, siehe weiter unten.

Eine weitere Typ sichere Variante findet sich in folgendem Beispiel:

public class A {
    final static boolean test = new Boolean(true).booleanValue();
}

Geändert dann:

public class A {
    final static boolean test = new Boolean(false).booleanValue();
}

Autor: Markus Reitz, Paul Ebermann

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.6 Was bedeuten "$" im Namen von Class-Files?

Das Dollarzeichen innerhalb eines Klassennamens taucht auf, wenn innere Klassen, also Klassen, die innerhalb einer anderen Klasse definiert sind, verwendet werden. Somit ist

    Testklasse$InnereKlasse.class

die Class-Datei der inneren Klasse "InnereKlasse", die innerhalb der Klasse Testklasse definiert worden ist.

Autor: Markus Reitz

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.7 Wie lassen sich Bilder im Dateiformat XYZ laden oder speichern?

Seit Java 1.0 lassen sich mit java.awt.Toolkit GIF- und JPEG-Dateien laden, seit Version 1.3 auch PNG (genaugenommen nicht nur aus Dateien, sondern von beliebigen URLs und sogar aus Byte-Arrays). Das Speichern von Bildern in diesen oder anderen Formaten wird nicht unterstützt. Da weder java.awt.Image noch java.awt.image.BufferedImage die Schnittstelle Serializable implementieren, lassen sich auch die eingebauten Routinen zum Objekt-I/O nicht verwenden.

Eine Notlösung stellt eventuell die Verwendung von PixelGrabber bzw. MemoryImageSource aus java.awt.image dar. Mit ersterem lassen sich Pixel als RGBA-int-Werte aus einem beliebigen java.awt.Image-Objekt extrahieren, mit letzterem erzeugt man ein Image-Objekt aus solchen "int-Pixeln". Lesen und Schreiben von int-Arrays unterstützen u. a. DataIn/OutputStream bzw. ObjectIn/OutputStream. Dabei ist zu bedenken, daß dieser Ansatz vier Bytes pro Pixel verbraucht. Eventuell führt die zusätzliche Verwendung von java.util.zip.DeflaterOut/InflaterInputStream zu Einsparungen.

Für gängige Dateiformate sind zahlreiche externe Lösungen verfügbar, sowohl frei als auch kommerziell, in stark unterschiedlicher Qualität. Unter http://www.geocities.com/marcoschmidt.geo/java-image-coding.html befindet sich eine Liste. Die Bibliothek JIMI http://java.sun.com/products/jimi/ ist ein guter Einstiegspunkt, sie ist (seit Sun sie aufgekauft hat) kostenlos erhältlich und deckt eine größere Anzahl Formate ab. Das Package com.sun.image.codec.jpeg - zum Laden und Speichern von Bildern im JPEG-Format - ist in neueren Sun-JREs und JDKs enthalten, man kann sich jedoch nicht darauf verlassen, es auch in anderen Java-Distributionen zu finden.

Bei Verwendung des LZW-Algorithmus' (z. B. in GIF bzw. TIFF/LZW) ist darauf zu achten, daß sowohl kommerzielle als auch Freeware-Produkte in bestimmten Ländern (inkl. USA und Deutschland) eine Lizenz beim Patentbesitzer Unisys erwerben müssen (siehe auch http://dmoz.org/Computers/Data_Formats/Graphics/2D/GIF/. Als Alternative zu GIF ist PNG gut geeignet, da es GIFs Einsatzgebiet - mit Ausnahme von Animationen - abdeckt und in modernen Browsern unterstützt wird.

Mit Java 1.4 wurde ein eigenständiges Package zum Laden und Speichern eingeführt: javax.imageio. Dieses unterstützt zunächst nur das Lesen von GIF, JPEG und PNG sowie das Schreiben von JPEG und PNG. Allerdings plant Suns JAI-Team (Java Advanced Imaging, eine Java-Bibliothek für Bildverarbeitung), fast alle Codecs aus JAI zu portieren, so daß sie der Spezifikation von javax.imageio folgen. Das schließt BMP, PNM (Portable Anymap) und TIFF ein. In Zukunft soll JAI für Codecs komplett auf die ImageIO-API aufbauen. Eine Einführung befindet sich auf http://java.sun.com/j2se/1.4/docs/guide/imageio/ oder bei installiertem JDK unterhalb des Installationsverzeichnisses in /docs/guide/imageio/index.html.

Auf http://www.jalice.net/myJava.htm finden sich zahlreiche Code-Beispiele und weiterführende Informationen zum Umgang mit Bildern, Java2D und der neuen ImageIO-API.

Autor: Marco Schmidt

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"

3.13.8 Was geht nicht mit Java?

Einige Features sind sehr systemnah und können wegen der Plattformunabhängigkeit von Java nicht direkt gelöst werden.

Oft ist liegt dabei das eigentliche Problem eher im Ansatz, weil zu plattformspezifisch gedacht wird, statt gängige Java-Paradigmen umzusetzen. So sind z.B. Properties jederzeit Environment Variablen vorzuziehen.

Ansonsten schafft ein entsprechendes System.exec(...) Abhilfe oder ein Bibliotheksaufruf, z.B. über JNI (Java Native Interface). Ein für jede Plattform einzeln zu lösendes Problem.

Einige Bibliotheken wie JavaComm sind bereits für die verschiedensten Plattformen implementiert.

Autor: Martin Erren

Zurück zu "3.13 [MISC] Alles, was nicht in eine der anderen Rubriken paßt"
Hosted by www.Geocities.ws

1