Update von Swing Look and Feel Synthetica V2.9 – tolle Screenshots

Die Version 2.9 verbesser der Aussehen insbesondere bei den neuen Java 6 Updates. Infos gibt http://weblogs.java.net/blog/wzberger/archive/2009/11/22/synthetica-blackeye-highlights.

Window Shape

Smart Panel Background

 

Outer Focus

 

Disabled IconsDisabled Icons

 

ProgressBar

 

Alternating Tabe Rows

 

JFileChooser Popup

Labels: ,

Der vorgegebene Konstruktor (engl. default constructor) und Begriffsverwirrungen

Wenn wir in unserer Klasse überhaupt keinen Konstruktor angeben, legt der Compiler automatisch einen an. Dieser Konstruktor nennt Sun default constructor, was wir als vorgegebener Konstruktor (selten auch Vorgabekonstruktor) eindeutschen wollen.

Schreiben wir nur

class Player
{
}

macht der Compiler daraus immer automatisch:

class Player
{
Player() { }
}

Der vorgegebene Konstruktor hat immer die gleiche Sichtbarkeit wie die Klasse. Ist also die Klasse public/private/protected, wird auch der automatisch eingeführte Konstruktor public/private/protected sein. Ist die Klasse paketsichtbar, ist es auch der Konstruktor.

Vorgegebener und expliziter Standard-Konstruktor

Ob ein parameterloser Konstruktor vom Compiler oder Entwickler angelegt wurde ist ein Implementierungsdetail, der für Nutzer der Klasse irrelevant ist. Daher ist es im Grunde egal, ob wir einen Standard-Konstruktor selbst anlegen oder wir uns einen vorgegebenen Konstruktor vom Compiler generieren lassen: im Bytecode lässt sich das nicht mehr unterscheiden. Selbst die JavaDoc API-Dokumentation, von einer public class C1 {} und public class C2 { public C2(){} } wäre strukturell gleich.

constructorBytecode1  constructorBytecode2

Bytecode der Klassen C1 und C2 im Vergleich

In der Begriffswelt der Insel heißt ein parameterloser Konstruktor immer Standard-Konstruktor, was natürlich den Unterschied verschwimmen lässt, ob der Standard-Konstruktor von Hand angelegt wurde oder als vorgegebener Konstruktor vom Compiler eingeführt wurde. Um das noch klarer zu unterscheiden, können wir es mit vorgegebener (Standard-)Konstruktor und expliziter Standard-Konstruktor weiter präzisieren.

Auch wenn der Compiler einen vorgegeben Konstruktor anlegt, ist es oft sinnvoll, einen eigenen Standard-Konstruktor anzugeben, auch wenn der Rumpf leer ist. Ein Grund ist, ihn zu mit JavaDoc zu dokumentieren, eine anderer, die Sichtbarkeit explizit zu wählen, etwa wenn die Kasse public ist, aber der Konstruktor nur die Paketsichtbarkeit haben soll.

Begrifflichkeit I In der Java-Language Specification gibt es bei den Konstruktoren nur die Trennungen in no-arg-constructor (parameterloser Konstruktor) und default constructor (vorgegebener Konstruktor), aber den Begriff „standard constructor“ gibt es nicht. Viele Autoren übersetzen die englische Bezeichnung „default constructor“ (unserem vorgegebenen Konstruktor) einfach nur mit „Standard-Konstruktor“.

 

Begrifflichkeit II Einige Autoren nennen nur den vom Entwickler explizit geschriebenen parameterlosen Konstruktor „Standard-Konstruktor“ und trennen dies sprachlich vom Compiler generierten Konstruktor, den sie weiterhin „Default-Konstruktor“ nennen. Beide werden dann zusammengefasst einfach parameterlose Konstruktoren genannt. Wenn also etwa die Frage gestellt wird, ob die Deklaration class C { } einen Standard-Konstruktor enthält, ist die Begrifflichkeit des Autors zu prüfen. Wenn der Autor nur den ausprogrammierten parameterlosen Konstruktor „Standard-Konstruktor“ genannt hat, so hätte die Klasse C nach dessen Definition keinen „Standard-Konstruktor“. Nach der Insel-Definition hätte die Klasse zwar einen vorgegebenen (Standard-)Konstruktor, aber keinen expliziten Standard-Konstruktor.

Labels:

Frustrierend: Java 7 doch später

Heute auf der Devoxx Konferenz gab es die Info, dass wegen der neuen Spracheigenschaften sich Java 7 von Februar auf etwa September verschieben wird. Immerhin wird es dann wohl Closures geben und das Projekt Coin wird vermutlich noch mehr aufnehmen, etwa die Aufzählung von mehreren Exceptions im catch-Block.

Labels:

Überraschung: Wohl doch Closures in Java 7

GWT 2.0 RC1 ist raus, Updates beim Google Eclipse Plugin

Infos dazu hier: http://code.google.com/p/google-web-toolkit/wiki/GWT_2_0_RC.

Major New Features in the GWT SDK
In-Browser Development Mode
Prior to 2.0, GWT hosted mode provided a special-purpose "hosted browser" to debug your GWT code. In 2.0, the web page being debugged is viewed within a regular-old browser. Development mode is supported through the use of a native-code plugin called the "Google Web Toolkit Developer Plugin" for many popular browsers. In other words, you can use development mode directly from Safari, Firefox, IE, and Chrome.

Developer-guided Code Splitting
Code splitting using GWT.runAsync(), along with compile reports (also known as The Story of Your Compile) allows you to chunk your GWT code into multiple fragments for faster startup. Imagine having to download a whole movie before being able to watch it. Well, that's what you have to do with most Ajax apps these days -- download the whole thing before using it. With code splitting, you can arrange to load just the minimum script needed to get the application running and the user interacting, while the rest of the app is downloaded as needed.

Declarative User Interfaces with UiBinder
GWT's UiBinder now allows you to create user interfaces mostly declaratively. Previously, widgets had to be created and assembled programmatically, requiring lots of code. Now, you can use XML to declare your UI, making the code more readable, easier to maintain, and faster to develop. The Mail sample has been updated to show a practical example of using UiBinder.

Bundling of Resources via ClientBundle
GWT introduced ImageBundle in 1.4 to provide automatic spriting of images. ClientBundle generalizes this technique, bringing the power of combining and optimizing resources into one download to things like text files, CSS, and XML. This means fewer network round trips, which in turn can decrease application latency -- especially on mobile applications.

Simplified Unit Testing with HtmlUnit
Using HtmlUnit for running test cases based on GWTTestCase: Prior to 2.0, GWTTestCase relied on SWT and native code versions of actual browsers to run unit tests. As a result, running unit tests required starting an actual browser. As of 2.0, GWTTestCase no longer uses SWT or native code. Instead, it uses HtmlUnit as the built-in browser. Because HtmlUnit is written entirely in the Java language, there is no longer any native code involved in typical test-driven development. Debugging GWT Tests in development mode can be done entirely in a Java debugger.
Major New Features in the Google Plugin for Eclipse
Development Mode Launch View
Integrates your Development Mode logs right into Eclipse, which means one less window to shuffle around

UiBinder Support
The UiBinder template editor provides auto-completion and formatting for editing ui.xml files (and embedded CSS blocks)
Validation of
UiBinder templates and backing Java classes
New UiBinder wizard to quickly get started

ClientBundle Support
"New ClientBundle" wizard to bundle CSS and other resources together to minimize HTTP round-trips
As-you-type validation ensures that your app's static resources are always in the right place

RPC Refactoring
Automatically updates sync/async pairs of RPC interfaces and their methods

JNSI Reference Auto-completion
Auto-completion takes the pain out of referencing Java members from JSNI methods

Labels: ,

Fork und Join aus Java 7

Gesucht ist ein Framework zum Lösen von parallelen D&C-Algorithmen, die berechnungsintensiv sind. Sun hat in Java 7 das Fork/Join integriert, was im Rahmen von jsr166y (http://gee.cs.oswego.edu/dl/concurrency-interest/) unter maßgeblicher Arbeit von Doug Lea entwickelt wurde. Die grundlegende Idee ist, neben Threads, noch eine andere Arbeitseinheit einzuführen, die Tasks.
  • Threads: Werden vom Betriebssystem verwaltet und laufen entweder pseudo-parallel auf einem Prozessor/Core oder echt parallel. Threads können sich mit anderen Threads koordinieren. Zu viele Threads, die sich im Weg stehen und aufeinander Warten führen zu keiner verbesserten Ausführungszeit gegenüber einer sequentiellen Lösung.
  • Tasks: Werden von Threads bzw. einem Thread-Pool ausgeführt. Sie sind Arbeitseinheiten, die nicht auf andere Tasks warten

Die Tasks sind kleine Arbeitspakete und werden in eine Task-Queue gelegt und dann von Threads abgearbeitet. Hat das System zwei Prozessoren und hat der Thread-Pool die Größe 2, so ist es wahrscheinlich, dass 2 Tasks parallel abgearbeitet werden. Gibt es 4 Prozessoren, können 4 Tasks vielleicht parallel laufen. Tasks lassen sich also grundsätzlich auf einer beliebigen Anzahl Threads und somit Prozessoren/Cores bringen, wobei im Gegensatz die Effektivität von Threads immer mit der physikalischen Anzahl von Prozessoren/Cores assoziiert ist.

Das Fork/Join-Framework löst die Probleme effektiv. Wie der Name schon andeutet, geht es bei Fork um das Erstellen eines neuen Tasks und bei Join um das Zusammenführen der Ergebnisse. Die Fork/Join-Bibliothek bietet dazu die Klasse ForkJoinPool und zwei zentrale Methoden: fork() und join(). Zur Abarbeitung der Tasks stellt das Framework die Threads zu Verfügung, deren Anzahl wir zwar selbst bestimmen können, aber die Anzahl Prozessoren/Cores eine gute Standardgröße ist.[1] Die Methode fork() erzeugt einen neuen Task, der an den Anfang (!) einer Queue gestellt wird. Dabei haben alle Threads eine Queue für ihre Arbeitsaufträge und sollte einmal eine Queue leer gelaufen sein, so nimmt sich der Thread einfach einen Task vom Ende (!) einer anderen nicht-leeren Queue. (Das nennt sich work-stealing und ist in der Realwelt ziemlich selten anzutreffen.) Dass neue Tasks an den Anfang gestellt werden ist einfach zu erklären: Die Tasks werden ja immer kleiner und somit stehen die kleinen, schnell lösbaren Aufgaben vorne. Erst später folgen die größeren Aufgaben, die auf die Ergebnisse der kleinen Aufgaben zurückgreifen, die dann logischerweise schon berechnet wurden.

Zur Theorie ein Beispiel: Es geht darum, mit Fork/Join ein Programm zu haben, welches parallel das Maximum eines Arrays sucht. Der Start ist:

int[] array = { 0, 9, 10, 111, 1, 12, 13, 14, 17 };
System.out.println( MaxElementInArrayFinder.findMax( array ) );

Die eigene Klasse MaxElementInArrayFinder bietet die Methode findMax(), die auf den ForkJoinPool zurückgreift, um mit invoke() den Haupt-Task abzusetzen.

class MaxElementInArrayFinder
{
private static final ForkJoinPool fjPool = new ForkJoinPool();
...
public static int findMax( int[] array )
{
return fjPool.invoke( new MaxElemTask( array, 0, array.length -1 ) );
}
}

Die Klasse MaxElemTask repräsentiert unser Arbeitspakt. Die Tasks referenzieren jeweils das Array, und die Anfangs-/Endeposition, ab der sie nach dem Maximum suchen sollen.

private static class MaxElemTask extends RecursiveTask<Integer>
{
private final int[] array;
private final int start, end;
MaxElemTask( int[] array, int start, int end )
{
assert array != null && start >= 0 && start <= end;
this.array = array;
this.start = start;
this.end = end;
}
@Override protected Integer compute()
{

}
}

Unsere Klasse erweitert die Basisklasse RecursiveTask<Integer> und deutet durch den generischen Typ schon an, das das Ergebnis des Tasks eine Ganzzahl sein wird, nämlich das Feldmaximum aus dem gewünschten Bereich. Der Konstruktor sichert die Werte und compute() führt die eigentliche Arbeit aus: Es löst entweder das Problem direkt, wenn es klein genug ist, oder spannt Unter-Tasks auf und wartet anschließend auf deren Ergebnisse.

@Override protected Integer compute()
{
assert array != null && array.length > 0;
System.out.printf( "max( start=%d, end=%d )%n", start, end );
if ( end - start < 4 )
{
int max = array[start];
for ( int i = start + 1; i <= end; i++ )
if ( array[i] > max )
max = array[i];
return max;
}
int middle = (start + end) / 2;
MaxElemTask leftTask = new MaxElemTask( array, start, middle );
leftTask.fork();
MaxElemTask rightTask = new MaxElemTask( array, middle + 1, end );
int rightMax = rightTask.compute();
int leftMax = leftTask.join();
return Math.max( rightMax, leftMax );
}

[1] Das die Maximalanzahl von Threads beim ForkJoinPool zurzeit 32767 ist, dürfte für normale Nutzer keine Einschränkung sein.

Labels: ,

Algorithmendesign teile und herrsche

Eine effektive Problemlösungsstrategie ist es, zunächst das Problem in Teilprobleme zu legen, dann die Teilprobleme zu lösen und anschließend zur Gesamtlösung zu kommen. Wer morgens im Bett liegt und Hunger verspürt, wird erst dann satt sein, wenn gewisse Teilprobleme gelöst sind. Diese Problemlösungsstrategie wird teile und herrsche (engl. divide and conquer, D&C[1]) genannt. Zunächst wird die Aufgabe ist kleine Häppchen zerlegt und anschließend abgearbeitet.

Teile und herrsche ist nicht nur eine Lösung, wie wir eine große Pizza „verarbeiten“, sondern auch in der Informatik eine beliebte algorithmische Methode: Das Hauptproblem wird in Teilprobleme zerlegt, die Teilprobleme dann gelöst und zur großen Lösung zusammengefügt. Zwei populäre Beispiele sind Sortierungen und die Multiplikation von großen Zahlen.

Sortieren über das Merge-Sort Verfahren

Der von John von Neumann vorgestellte Algorithmus basiert auf der Idee, die zu sortierende Liste ein zwei Teillisten zu zerlegen, diese dann wiederum in zwei Teile zu zerlegen, diese wiederum, usw., bis die Listen so klein sind, dass sie vielleicht nur noch aus zwei Zahlen bestehen, die trivial in eine Reihenfolge zu bringen sind. Ist eine Teilfolge dann sortiert, muss sie mit sortieren Nachbarfolge zusammenfügt (engl. merge) werden. Während also das Zerlegen und Sortieren von oben nach unten erfolgt, läuft das Zusammenlegen der sortierten Teillisten zur neuen größeren sortieren Teillisten von unten nach oben, bis schließlich die Gesamtliste sortierten ist. Der Algorithmus lässt sich sehr gut rekursiv implementieren. Auch das bekannte Quicksort arbeitet ähnlich. (Hier geht es allerdings darum, ein sogenanntes Pivot-Element zu wählen, dann die Liste in zwei Teillisten aufzuspalten, wobei in die erste Liste (erst einmal unsortiert) die Elemente kleiner dem Pivot-Element verschoben werden und in die andere Liste die Elemente größer dem Pivot-Element. Die Auswahl eines neuen Pivot-Elemens und das Kopieren in den richtigen Bereich wird rekursiv für die Unterbereiche wiederholt, was natürlich zu einer Sortierung führt. In der Regel kommt Quicksort mit weniger Speicher aus und ist in der Praxis schneller, da Merge-Sort in der einfachen Implementierung immer neue Teillisten aufbauen muss und Quicksort die Vertauschoperationen auf der originalen Datenstruktur (in-place genannt) ausführen kann.

Multiplikation von großen Ganzzahlen

In Java ist das Multiplizieren von Ganzahlen einfach. Sind die Zahlen klein genug, erledigt der *-Operator die Aufgabe, sind sie größer hilft die Klasse BigInteger und die Methode multiply(). Das sind natürlich hübsche Abstraktionen, aber im Java Bytecode gibt für die Multiplikation von int und long lediglich imul und lmul[2], und alles andere, etwa die Multiplikation von großen Zahlen für RSA-Schlüssel.

Das Produkt von großen Zahlen lässt sich einfach auf das Produkt von kleinern Zahlen mit ein paar Additionen abbilden. Statt Zahlen mit hunderten von Stellen zu nehmen, ein einfacheres Beispiel, was das Prinzip zeigt. Nehmen wir dazu die Zahl A = 1234, die mit B = 5678 multipliziert werden soll. Dann ist AB = (12 · 10^2 + 34) · (56 · 10^2 + 78) = 12 · 56 · 10^4 + (12 · 78 + 34 · 56) · 10^2 + 34 · 78. Waren bei 1234 und 5678 die Zahlen noch vierstellig, sind sie bei der Umschreibung nur noch zweistellig. Zählen wir die Anzahl Multiplikationen – und lassen wir die einfachen Multiplikation mit 10^4 bzw. 10^2 beiseite – so kommen wir auf vier, denn wir müssen 12 · 56, 12 · 78, 34 · 56 und 34 · 78 ausführen. Bei einem rekursiven D&C-Algorithmus ist also das Problem zur Multiplikation von 1234 · 5678 auf die vier Multiplikationen und Additionen abgeschwächt worden. Das können wir dann auch weiter aufspalten bis wir bei einstelligen Zahlen sind. Stehen wir also vor der Aufgabe beliebig große Zahlen mit n Stellen zur multiplizieren, können wir das Abbilden auf eine Multiplikation von Zahlen der Größe n/2 und ein paar Additionen. Kommen wir noch zu einer kleinen Optimierung. Wenn Zahlen sehr groß werden und dann multipliziert werden müssen (etwa zu Schlüsselgenerierung) ist es wichtig, jede überflüssige Operation wegzulassen, da arithmetische Operationen dann bei großen Zahlen und dem häufiger Durchführung doch ihre Zeit kosten. Interessanterweise kann durch geschickte Umstellung kann die Anzahl Multiplikationen von 4 auf 3 gesenkt werden. Zwei der Multiplikationen stammen aus 12 · 56 · 10^4 + (12 · 78 + 34 · 56) · 10^2 + 34 · 78 stammen aus dem Teil 12 · 78 + 34 · 56. Hier können wir etwas umschreiben, denn 12 · 78 + 34 · 56 = (12 + 34) · (56 + 78) – 12 · 56 – 34 · 78. Obwohl das auf den ersten Blick schlimmer aussieht (drei Multiplikationen statt zwei), fällt bei zweiten Blick auf, dass wie die beiden Produkte 12 · 56 und 34 · 78 schon im ersten Schritt berechnet haben. Also ergibt sich letztendlich 12 · 56 · 10^4 + ((12 + 34) · (56 + 78)12 · 5634 · 78) · 10^2 + 34 · 78 und das macht insgesamt drei Multiplikationen für den Preis von ein paar zusätzlichen Subtraktionen, die im allgemeinen billiger ist als die Multiplikation, die bei dem D&C-Ansatz ja recht aufwändig ist.

Die Arbeitsweise von D&C-Algorithmen im Pseudocode sieht wie folgt aus:

löse Problem:

ist Problem klein:

löse Problem direkt

andernfalls:

zerlege das Problem in Teilprobleme

löse die Teilprobleme

setzte Problemlösung aus den Teillösungen zusammen

Attraktiv sind D&C-Algorithmen dann, wenn die Teilprobleme unabhängig voneinander und parallel gelöst werden können.

Bei unserem Eingangsbeispiel mit dem Aufstehen und Essen gibt es eine Abhängigkeit, so dass zwei beide Teilprozesse zwar eine Teilaufgabe des Gesamtproblems lösen, aber ohne Aufstehen man nicht zum Kühlschrank kommt. Das Sortieren über Merge-Sort ist erfüllt dabei das Kriterium, dass wenn die Liste in zwei Unterlisten zerlegt wird, die beiden Unterlisten problemlos parallel sortiert werden können.


[1] Nicht D&G, „ein anspruchsvolles und zeitgemäßes Markenzeichen und Ausdruck einer sich wandelnden Welt“ …

[2] http://java.sun.com/docs/books/jvms/second_edition/html/Mnemonics.doc.html

Labels:

Merkwürdige Geschäftsbedingungen für Android Market

Darüber berichtet Heise:

Ein Entwickler, der sein Produkt aus dem Market aus unterschiedlichen Gründen (z.B. aufgrund von Urheberrechtsverletzungen) zurückzieht, muss alle Einkünfte, die er innerhalb eines Jahres, bevor die Software entfernt wurde, an die Endverbraucher, die für das Produkt gezahlt haben, zurückerstatten.

Vielleicht ist das aber gar nicht so falsch. Wenn ich mit Nacktbildern von Angela M. richtig viel Kohle mache, dann aber wegen unsittlichen urheberrechtlich geschützten Privatfotos meine App zurückziehen muss, habe ich dann schon auf dem Rücken von Angela M. (oh, ja, welch’ Bild) viel verdient. Bin gespannt, wie sich das entwickelt.

switch auf Strings seit Java 7

Seit Java 7 sind switch-Anweisungen auf String-Objekten möglich.

String input = javax.swing.JOptionPane.showInputDialog( "Eingabe" );

switch ( input.toLowerCase() )

{

case "kekse":

System.out.println( "Ich mag Keeeekse" );

break;

case "kuchen":

System.out.println( "Ich mag Kuchen" );

break;

case "scholokade":

case "lakritze":

System.out.println( "Hm. lecker" );

break;

default:

System.out.printf( "Kann man %s essen?", input );

}

Obwohl Zeichenkettenvergleiche nun möglich sind, fallen Überprüfungen auf reguläre Ausdrücke leider heraus, die insbesondere Skriptsprachen anbieten.

Labels: ,

Java 7 Milestone 5: Build b76 vorgestellt

Download gibt es unter http://download.java.net/jdk7/m5/, die Neuerungen bei http://download.java.net/jdk7/changes/jdk7-b76.html. Am Interessantesten dürfen sein:

  • bb3. 6865582. jsr166y - jsr166 maintenance update
  • 6865571. Add a lightweight task framework known as ForkJoin
  • 6445158. Phaser - an improved CyclicBarrier
  • 6865579. Add TransferQueue/LinkedTransferQueue

und

Labels:

Release von JProfiler 6.0

Während man die Profiler TPTP und den NetBeans Profiler als Standards im Open-Source Umfeld bezeichnen kann, so ist das auf der kommerziellen Seite JProfiler und YourKit Java Profiler 8.0. Die ej-technologies GmbH stellt mit dem JProfiler nun ein Tool vor, das interessante Neuigkeiten bietet. Ein Trend ist die Zuwendung zu einem Tool welches Möglichkeiten bietet Thread-Locks zu visualisieren, was es klarer Hinweis ist, dass Entwickler um die parallele Programmierung nicht mehr rumkommen und Hilfe bei der Optimierung der (gedachten) Optimierung benötigen.

Neu ist auch die Darstellung, welche Locks wie oft angefragt werden:

Auch folgende Darstellung ist neu und von einem Klassiker inspiriert, der Dateigrößen erstmalig so darstellte. Nun ist das auf die Ausführungszeiten und Speicherverbrauch angewendet. Eine wunderbare Idee:

 

PS: Die deutschsprachige Frage-/Antwort-Plattform http://stackunderflow.de/ freut sich über neue Fragen und Antworten aus dem IT-Bereich. Java, Datenbanken, .NET, … alles ist erlaubt.

Kommen jetzt die Androids?

Vor nicht all zu Langer Zeit kam Google mit Android 2.0 um die Ecke und nun kommt es mir vor, dass alle möglichen Hersteller Android-Geräte ankündigen. Heise gibt nun auch ein Handy von LG an. Damit wächst die Liste der Android-Handys auf 13. Einige sind jedoch nur Ankündigungen doch ich bin überzeugt, es werden mehr. Es wäre Zeit, nun ein Android-Buch zu schreiben …

Labels:

Thema der Woche: Ladebalken über dem Textfeld

Mit JXLayer (https://jxlayer.dev.java.net/) lassen sich beliebige Swing-Komponenten mit anderen Swing-Komponenten (oder Zeichnungen) auf eine Ebene legen und somit interessante Effekte und Lösungen entwerfen.

  1. Schreibe eine kleine Swing-Anwendung mit einem Textfeld und einer Schaltfläche. Wird die Schaltfläche gedrückt soll ein Web-Server kontaktiert und die Daten von dort geladen und in das Textfeld gesetzt werden.
  2. Das Laden soll künstlich verzögert werden. Schreibe einen eigenen ThrottleReader, der den FilterReader erweitert und deine künstliche Verzögerung einführt.
  3. Beim Laden soll nun ein (indefinite) Fortschrittsbalken mit JXLayer über dem Textfeld erscheinen, der dann ausgeblendet wird, wenn der Text geladen wurde.
  4. Überlege, wie sich der Fortschnitt des Fortschrittsbalken bestmöglich an der Anzahl geladenen Zeichen festmachen lässt.

Labels:

Ersetze in einem String die alle diakritischen Zeichen (ä->a, ...)

String s = "Müller";
s = Normalizer.normalize( s, Normalizer.Form.NFD );
s = s.replaceAll( "[\\p{InCombiningDiacriticalMarks}\\p{IsLm}\\p{IsSk}]+", "" );
System.out.println( s );  // Muller

Die Lösung geht zweistufig vor. Der Normalisier zerlegt zunächst den String und macht die eigentliche Arbeit. replaceAll() entfernt dann übriggebliebene Punke, Striche, Kreise, Häkchen.

Labels:

Swing-Komponenten neu erstellen oder verändern und JLayer

Zum Aufbau neuer Swing-Komponenten kommen eine Reihe von Möglichkeiten in Frage. Wenn passend, lässt sich eine existierende Swing-Komponente als Basisklasse nehmen und um nötige Eigenschaften erweitern, sofern die Basisklassen diese Möglichkeit im Grunde schon bieten. Soll etwa ein Texteingabefeld nur IP-Adressen zulassen, so ist dafür keine völlig neue Textkomponentenimplementierung nötig, sondern nur eine Unterklasse der Standard-Komponente mit passendem Dokumenten-Modell. Oder soll ein Liste nur Kontrollkästen (mit Text) darstellen soll, ist das schon über die JList mit passendem Renderer und Modell möglich.

Unproblematisch ist auch, wenn sich neue Komponenten aus anderen Swing Teilkomponenten zusammenzusetzen lassen und. Dann erweitert die neue Swing-Klasse erweitert einen Container wie JPanel, der einfach die anderen Elemente wie gewünscht platziert. Möglich ist dies zum Beispiel bei einer Statuszeile, da diese nichts großartiges macht, als einfach horizontal andere Komponenten anzuordnen und einen besonderen Rahmen zu setzen. Einen Dialog zur Auswahl eines Zeichensatzes bietet Swing bisher auch nicht an, der lässt sich aber als JDialog mit passenden Swing-Komponenten leicht nachbauen.

Mehr Arbeit ist nötig, wenn sich auf keine allgemeinen Swing-Komponenten zurückgreifen lässt. Die Swing-Bibliothek bietet etwa keine Ribbon-Komponente, keinen wirklich guten HTML-Renderer, oder ein Docking-Framework. Bei Anforderungen dieser Art lässt sich nicht so einfach auf Standardkomponenten zurückgreifen, sondern spezieller Programmcode zum Zeichnen nötig. Der wesentliche Unterschied ist also der, dass sich die Darstellung nicht vollständig an Standardkomponenten delegieren lässt sondern immer etwas einer Java-Code zum Zeichnen nötig ist.
Um es richtig gut zu machen, sind für eine eigene Swing-Komponente drei Dinge nötig: Die Komponentenklasse, eine Modellklasse und ein UI-Delegate. Die Komponentenklasse ist die Hauptklasse und eine JComponent, die der Entwickler auf die Gui setzt. Sie bietet die API zum Setzten der Zustände. Die Modell-Daten werden nicht selbst in der Komponentenklasse gespeichert, sondern idealweise über eine eigene Klasse modelliert. Die Tabelle nimmt zum Beispiel die Zellen aus einem Tabellemodell, eine Textkomponenten den Text aus einem Dokumentenmodell. Als letztes bleibt der UI-Delegate, der das wirkliche Zeichen und die Ereignisbehandlung übernimmt. Es kann sehr anspruchvoll sein ein gutes Aussehen und effektive Navigation zu erreichen und insbesondere wenn die Komponente in verschiedenen Look-and-Feels arbeiten soll, eine Menge Arbeit werden. Und das die eigene Swing-Komponente die UI-Eigenschaften wie Farben, Abstände und Antialiasing-Modus toleriert ist selbstverständlich.

Überlagerungen mit dem Swing-Komponenten-Dekorator JLayer

Können Swing-Komponten überlagert werden, können dadurch interessante Effekte erzieht werden. Ein paar Beispiele:

·    Während ein Text in die Textbox geladen wird, erscheint ein JProgressBar.
·    Bei aufwändigen Operationen wird das Haupt-Panel gesperrt und eine drehende Sanduhr erscheint.
·    Ist die Eingabe in einem Textfeld falsch, erscheint ein kleines Symbol, welches über die invalide Eingabe informiert.
·    Über einer leeren Tabelle liegt eine Beschriftung, die erklärt, dass Doppelklick eine neue Zeile einfügt.

Alle die Darstellungen lassen sich mit Hilfe der in Java 7 eingefügten Klassen JLayer einfach lösen (Nutzer vor Java 7 greifen auf SwingX zurück, denn von dort kommt die Klasse auch; sie heißt nur dort JXLayer. Als Alternative haben Autoren auch oft auf Glass-Pane zurückgegriffen.)
Haupteigenschaft von JLayer ist, sich um existierende Swing-Komponenten zu legen. Soll ein JLayer um ein Textfeld gelegt werden, heißt es:

JLayer layer = new JLayer( textField );
 

Die zu ummantelnde Komponente wird über den Konstruktor angegeben und nicht über add(), da JLayer kein Container ist. Der nächste Schritt ist die Angabe eines Objekts, dass das Zeichnen übernimmt.

layer.setUI( layerUI );

Die Angabe erfordert ein LayerUI-Objekt, welches eine paint()-Methode realisiert. Die Implementierung kann super.paint() aufrufen, um die ummantelte Komponente zu zeichnen, und dann eigenen Programmcode hinzufügen, um etwa einen Sanduhr darzustellen.
Das folgende Beispiel fasst die Schritte zusammen und realisiert ein Programm, welches bei Eingabe von „pu“ einen kleinen roten Kreis anzeigt.

JFrame f = new JFrame();
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setLayout( new BorderLayout(2, 2) );

f.add( new JSeparator(), BorderLayout.PAGE_START );
f.add( new JLabel( "Name:" ), BorderLayout.LINE_START );

final JTextField textField = new JTextField();

LayerUI layerUI = new LayerUI()
{
  @Override
  public void paint( Graphics g, JComponent component )
  {
    super.paint( g, component );

    if ( textField.getText().equalsIgnoreCase( "pu" ) )
    {
      g.setColor( new Color( 255, 0, 0, 100 ) );
      g.fillOval( 0, component.getHeight() - 10, 10, 10 );
    }
  }
};

JLayer layer = new JLayer( textField );
layer.setUI( layerUI );
f.add( layer );
f.add( new JSeparator(), BorderLayout.PAGE_END );

f.pack();
f.setVisible( true );


Die JLayer kann auch das Hauptpanel dekorieren und die Events auffangen. Das ist eine zweites Anwendungsfeld neben dem Änderung der Darstellung. Die JLayer-Komponente kann einfach Events auffangen und verarbeiten und so zum Beispiel global F1 für die Hilfe abfangen.

Labels: ,

JSON-Serialisierung mit Jackson

Nehmen wir folgende Zeile JavaScript-Code, das ein Person-Objekt mit zwei Properties für Name und Alter definiert. Eine Property wird über ein Schlüssel/Werte-Paar beschrieben:

var person = { "name" : "Michael Jackson", "age" : 50 };

Die Definition eines Objekts geschieht in der JSON (JavaScript Object Notation). Als Datentypen unterstützt JSON Zahlen, Wahreiswerte, Strings, Arrays, null und Objekte – wie unser Beispiel zeigt. Die Deklarationen können geschachtelt sein, um Unterobjekte aufzubauen.
Zum Zugriff auf die JSON-Daten kommt der Punkt zum Einsatz, sodass der Name nach der Auswertung durch person.name zugänglich ist.

Eine Personenbeschreibung wie diese kann auch in einem String stehen, die von JavaScript zur Laufzeit ausgewertet wird.

var json = 'person = { "name" : "Michael Jackson", "age" : 50 };';
eval( json );

Der Zugriff auf person.name liefert wie vorher den Namen, denn nach der Auswertung mit eval() wird JavaScript ein neues Objekt mit person im Kontext anlegen.

JSON ist besonders praktisch, wenn es darum geht, Daten zwischen einem Server und Browser mit JavaScript-Interpreter auszutauschen. Denn wenn der String json nicht von Hand mit einem String initialisiert wurde, sondern ein Server die Zeichenkette person = { … }; liefert, haben wir das, was heutzutage in modernen Ajax-Webanwendungen passiert. Die letzte Frage ist nun, wie elegant der Server Zeichenketten im Datenaustauschformat JSON erzeugt und so Objekte überträgt. Den String per Hand aufzubauen ist eine Lösung, aber es geht besser.

Die Open-Source Bibliothek  Jackson (http://jackson.codehaus.org/) gehört zu den populärsten Lösungen, die JSON-Daten einliest und ausgibt und auf JavaBeans überträgt, sodass eine unkomplizierte Serialisierung in JSON möglich wird.

ObjectMapper mapper = new ObjectMapper();
MyClass myObject = mapper.readValue( input, MyClass.class );
mapper.writeValue( output, myObject );

Der ObjectMapper übernimmt das Lesen/Schreiben. In der zweiten Zeile wird aus der Eingabequelle input gelesen und ein Objekt vom Typ MyClass rekonstruiert. In der dritten Zeile wird es in die Ausgabe output geschrieben.

JSON ist nicht nur für die Objektübertragung zwischen Server und Browser gut, sondern ist eine elegante Alternative zu XML, wenn es etwa um lokale Konfigurationsdateien geht. JSON ist viel kürzer als XML und kann somit zum Beispiel für Konfigurationsdateien übersichtlicher sein.

Labels:

Bean-Zustände kopieren

In mehrschichtigen Anwendungen gibt es oft das Muster, dass eine JavaBean etwa über eine Objekt-Relationale-Mapping-Technologie automatisch aus einer Datenbankzeile aufgebaut wird und dann internen in der Geschäftsschicht verwendet wird. Soll nun diese Information über das Netzwerk an einen anderen Rechner verteilt werden, ist es nicht immer angebracht, diese JavaBean etwa direkt über Serialisierung zu versenden. Stattdessen kann ein Transfer-Objekt aufgebaut werden, eine spezielle JavaBean zum Beispiel, sodass der Empfänger keine Abhängigkeit zu der Bean in der internen Geschäftsschicht hat. Nun werden sich aber diese Geschäftsschicht-Bean und Transfer-Bean sehr ähnlich sein und viele Entwickler scheuen die Mühe, lästigen Kopiercode zu erstellen. Doch manuelle Arbeit ist nicht nötig und eine Lösung  für das Kopierproblem ist über Refection schnell geschrieben. Über die BeanInfo kommen wir an den PropertyDescriptor (siehe dazu „Properties einer Bean erfragen“) und dann liefern getReadMethod() und getWriteMethod() die Setter/Getter. Bei einer eigenen Kopiermethode wie copyProperties(Object source, Object target) müssen wir bei der Quell-Bean jede Property auslesen und entsprechend beim Ziel-Bean nach der Property suchen und den Setter aufrufen. Wenn das ganze ohne Typkonvertierungen programmiert werden soll, sind es nur wenige Zeilen Programmcode. Kommen einfache Konvertierungen dazu, etwa wenn einmal ein Wrapper als Property-Typ genutzt wird und einmal der primitive Datentyp, ist es etwas mehr.
Der Aufwand mit einer eigenen Implementierung ist allerdings nicht nötig, denn zwei populäre Implementierungen können helfen:
•    Apache Commons BeanUtils (http://commons.apache.org/beanutils/). Die Klasse org.apache.commons.beanutils.BeanUtils bietet praktische statische Methoden wie copyProperty(Object bean, String name, Object value), copyProperties(Object dest, Object orig), Object     cloneBean(Object bean) oder populate(Object bean, Map properties).
•    Dozer (http://dozer.sourceforge.net/). Dozer bringt ausgefeilte Mapping-Möglichkeiten mit, die weit über BeansUtils hinausgehen. Das geht soweit, dass es ein Eclipse-Plugin zur Konfiguration der Abbildungen gibt.

Labels:

Java-Ribbon Komponente Flamingo 4.2 ist raus

Zu den Features zählen:
Dass der Command-Button nun flexibler geworden ist, ist prima, denn die Komponente lässt sich auch prima außerhalb der Ribbon-Komponente nutzen.


https://flamingo.dev.java.net/release-info/4.2/command-buttons-icon-and-text1.png


Mehr Infos gibt es unter https://flamingo.dev.java.net/.

Mit dem Substance LaF sieht das hervorragend aus.


Labels: ,

Process-Ströme in Dateien umlenken

Ist der Unterprozess über start() gestartet, lassen sich über das Process-Objekt die Ein-/Ausgabe-Datenströme erfragen. Die Process-Klasse bietet getInputStream(), mit dem wir an genau die Daten kommen, die der externe Prozess in seinen Ausgabestrom schreibt, denn sein Ausgabestrom ist unser Eingebestrom, den wir konsumieren können. Auch ist getErrorStream() ein InputStream, denn das, was die externe Anwendung in den Fehlerkanal schreibt, empfangen wir in einem Eingabestrom. Mit getOutputStream() bekommen wir einen OutputStream, dass das externe Programm mit Daten füttert. Dies ist der Pipe-Modus, sodass wir einfach mit externen Programmen Daten austauschen können.
Neben diesem Pipe-Modus gibt es seit Java 7 eine Alternative, die Ströme direkt auf Dateien umzulenken. Dazu definiert die ProcessBuilder-Klasse diverse redirectXXX()-Methoden. (Sollte dann ein getXXXStream()-Aufruf gemacht werden, so kommen nicht-aktive Ströme zurück, denn das externe Programm kommuniziert ja dann direkt mit einer Datei und die Java-Pipe hängt nicht dazwischen.)


class java.lang.ProcessBuilder   

§    ProcessBuilder redirectInput( File file )
ProcessBuilder redirectInput( ProcessBuilder.Redirect source )
Der Unterprozess wird die Eingaben aus der angegeben Quelle beziehen.
§    ProcessBuilder redirectOutput( File file )
§    ProcessBuilder redirectOutput( ProcessBuilder.Redirect destination )
Der Unterprozess wird Standardausgaben an das angegebene Ziel senden.
§    ProcessBuilder redirectError( File file )
§    ProcessBuilder redirectError( ProcessBuilder.Redirect destination )
Der Unterprozess wird Fehlerausgaben an das angegebene Ziel senden.

Die redirectXXX(File file)-Methoden bekommen als Ziel ein einfaches File-Objekt. Die redirectXXX()-Methoden sind aber überladen mit einem anderen Typ Redirect, der als innere statische Klasse in ProcessBuilder angelegt ist. Mit Redirect.PIPE und Redirect.INHERIT gibt es zwei Konstanten, und drei statischen Methoden Redirect.from(File), Redirect.to(File), Redirect.appendTo(File) die Redirect-Objekte für die Umleitung zur Datei liefern. Die mit File parametrisierten Methoden greifen auf die Redirect-Klasse zurück, so dass es bei redirectOutput(File file) intern auf ein redirectOutput(Redirect.to(file)) herausläuft.

Labels: ,

NumberFormat, Währungen angeben und die Klasse Currency

Die NumberFormat-Klasse liefert mit getCurrencyInstance() einen Format-Objekt, welches neben der Dezimalzahl auch noch ein Währungssymbol mit anzeigt. So liefert NumberFormat.getCurrencyInstance().format(12345.6789) dann 12.345,68 €, also automatisch mit einem Euro-Zeichen. Dass es ein Euro-Zeichen ist, und kein Yen-Symbol liegt einfach daran, dass Java standardmäßig das eingestellte Land „sieht“ und daraus die Währung ableitet.  Wenn wir explizit den Formater mit einem Land initialisieren, etwa wie in

NumberFormat frmt1 = DecimalFormat.getCurrencyInstance( Locale.FRANCE );
System.out.println( frmt1.format( 12345.6789 ) );         // 12 345,68 €

so ist die Währung automatisch Euro (denn Frankreich nutzt den Euro); schreiben wir DecimalFormat.getCurrencyInstance(Locale.JAPAN) ist sie Yen und wir bekommen ¥12,346. (Es gibt standardmäßig keine Nachkommastellen beim Yen.) Locale-Objekten repräsentieren immer eine sprachliche Region.

DecimalFormat bzw. schon die Oberklasse NumberFormat ermöglicht die explizite Angabe der Währung. In der Java-Bibliothek wird sich durch die Klasse java.util.Currency repräsentiert. NumberFormat liefert mit getCurrency() die eingestellte Currency, die zur Formatierung verwendet wird und setCurrency() setzt sie neu. Das löst Szenarios, in denen etwa ein Euro-Zeichen die Währung darstellt, aber die Zahlenformatierung englisch ist, wie die folgenden Zeilen zeigen:

NumberFormat frmt = DecimalFormat.getCurrencyInstance( Locale.ENGLISH );
frmt.setCurrency( Currency.getInstance( "EUR" ) );
System.out.println( frmt.format( 12345.6789 ) );  // EUR12,345.68

Die Currency-Klasse bietet drei statische Methoden, die Currency-Objekte liefern. Da ist einmal getAvailableCurrencies(), was ein Set liefert und die beiden Fabrikfunktion getInstance(Locale locale) und getInstance(String currencyCode). Currency-Objekte besitzen eine ganze Reihe von Objektfunktionen, die etwa den ISO 4217 Währenscode liefen oder den ausgeschriebenen Währungsnamen (und das auch noch in verschiedenen Sprachen wenn gewünscht).
Folgendes Programm geht über alle Währungen und gibt die zentralen Informationen aus:
 

for ( Currency currency : Currency.getAvailableCurrencies() )
{
  System.out.printf( "%s, %s, %s (%s)%n",
                     currency.getCurrencyCode(),
                     currency.getSymbol(),
                     currency.getDisplayName(),
                     currency.getDisplayName(Locale.ENGLISH) );
}

Wir bekommen dann mehr als 200 Ausgaben, und die Ausgabe beginnt mit:
EGP, EGP, Ägyptisches Pfund (Egyptian Pound)
IQD, IQD, Irak Dinar (Iraqi Dinar)
GHS, GHS, Ghana Cedi (Ghana Cedi)
AFN, AFN, Afghani (Afghani)
MUR, MUR, Mauritius Rupie (Mauritius Rupee)
SGD, SGD, Singapur Dollar (Singapore Dollar)

Labels: ,

Eclipse 3.6 M3 ist verfügbar

Unter http://download.eclipse.org/eclipse/downloads/drops/S-3.6M3-200910301201/eclipse-news-M3.html gibt es die News. Die Änderungen in der JDT halten sich mal wieder sehr in Grenzen und bei NetBeans geht viel mehr die Post ab. (Warten wir mal, wie sich Oracles Motivation auf NetBeans so auswirkt.) JUnit wurde auf 4.7 aktualisiert.

Labels: ,

Wrapperklassen-Vergleiche durchführen mit compare() und compareTo()

Haben wie zwei Ganzzahlen 1 und 2 vor uns, so ist es trivial zu sagen, dass 1 kleiner 2 ist. Bei Fließkommazahlen ist das ein wenig komplizierter, da es hier „Sonderzahlen“ wie Unendlich oder eine negative bzw. positive null gibt. Da insbesondere Vergleichsalgorithmen die Beantwortung der Frage, ob zwei Werte a und b kleiner, größer oder gleich sind, erwarten, gibt es zwei Typen von Methoden in den Wrapper-Klassen.

·    Sie implementieren eine Objektmethode compareTo(). Die Methode ist nicht zufällig da, denn  Wrapper-Klassen implementieren die Schnittstelle Comparable. (Wir haben die Schnittstelle schon im Kapitel 6 kurz vorgestellt.)

·    Wrapper-Klassen besitzen statische compare()-Methoden.

Die Rückgabe der Methoden ist ein int und es kodiert, ob ein Wert größer, kleiner oder gleich ist.

Beispiel   Teste verschiedene Werte.

System.out.println( Integer.compare(1, 2) );        // -1
System.out.println( Integer.compare(1, 1) );        //  0
System.out.println( Integer.compare(2, 1) );        //  1

System.out.println( Double.compare(2.0, 2.1) );     // -1
System.out.println( Double.compare(Double.NaN, 0) );// 1

System.out.println( Boolean.compare(true, false) ); //  1
System.out.println( Boolean.compare(false, true) ); // -1

Ein true ist „größer“ als als false.   

Die Tabelle fasst von den Wrapper-Klassen die Methoden zusammen.

Klasse    Methode aus Comparable    Statische Methode compare()      
Byte    int compareTo(Byte anotherByte)    int compare(int x, int y)      
Short    int compareTo(Short anotherShort)    int compare(short x, short y)      
Float    int compareTo(Float anotherFloat)    int compare(float f1, float f2)      
Double    int compareTo(Double anotherDouble)    int compare(double d1, double d2)      
Integer    int compareTo(Integer anotherInteger)    int compare(int x, int y)      
Long    int compareTo(Long anotherLong)    int compare(long x, long y)      
Character    int compareTo(Character anotherCharacter)    int compare(char x, char y)      
Boolean    int compareTo(Boolean b)    int compare(boolean x, boolean y)   

Die Implementierung einer statischen Methode WrapperKlasse.compare() ist äquivalent zu WrapperKlasse.valueOf(x).compareTo(WrapperKlasse.valueOf(y)).

Die Klassen BigInteger, BigDecimal implementieren zwar Number und somit Comparable, aber eine statische compare()-Methode bieten sie nicht. Auch String implementiert Comparable, aber eine statische Methode fehlt. Der Grund ist, dass es eine statische Methode Objects.compare() gibt, zwei Objekte mit einem Comperator vergleicht.

Labels: ,

Die Utility-Klasse java.lang.Objects

In Java 7 ist die Klasse Objects hinzugekommen, die einige statische Utility-Funktionen bereithält. Sie führen in erster Linie null-Tests durch.

null-Tests um equals()/hashCode()/toString()

Ist zum Beispiel eine Objektvariable name einer Person null, so kann nicht einfach name.toString() aufgerufen werden, ohne dass eine NullPointerException folgt. Drei Methoden von Objects führen null-Test durch, bevor sie an die Object-Methode equals()/hashCode()/toString() weiterleiten. Eine zusätzliche Hilfsmethode arbeitet mit Comparatoren, die in im Kapitel über Datenstrukturen genauer vorgestellt werden.

class java.lang.Objects
§          static boolean equals( Object a, Object b )
Liefert true wenn beide Argument entweder null sind, oder a.equls(b) ebenfalls true ergibt. Sonst false. Das Objects.equals(null, null) die Rückgabe true ergibt ist sinnvoll und so erspart die Methode einige händische Tests.
§          static int hashCode( Object o )
Liefert 0 wenn o gleich null ist, sonst o.hashCode().
§          static String toString(Object o)
Liefert den String "null" wen das Argument null ist sonst o.toString().
§          static int compare( T a, T b, Comparator c )
Liefert 0, wenn a und b beide entweder null sind, oder der Comparator die Objekte a und b für gleich erklärt. Sind a und b beide ungleich null, so ist die Rückgabe c.compare(a, b). Ist nur a oder b gleich null, so hängt es vom Comparator ab und der Reihenfolge der Parameter ab.
Erinnern wir uns ans hashCode() vom Spieler, wo der Spielername in den Hashcode eingehen soll, so sehen wir, wo die statische Objects.hashCode()-Methode gut untergebracht werden kann.
com/tutego/insel/object/hashcode/Player.java, hashCode() Ausschnitt
result = 31 * result + ((name == null) ? 0 : name.hashCode());
Mit Objects.hashCode() verkürzt sich dies – nicht spektakulär im Sinne von eingesparten Zeichen – zu:
result = 31 * result + Objects.hashCode( name.hashCode() );

Null-Prüfungen mit eingebauter Ausnahmebehandlung

Zu den drei statischen Methoden kommen zwei hinzu, die null-Prüfungen übernehmen und im Fehlerfall eine Ausnahme auslösen. Das ist praktisch bei Konstruktoren oder Settern, die Werte initialisieren sollen, aber verhindern möchten, dass null durchgeleitet wird.

Beispiel   Die Methde setName() soll keine name-Argument gleich null erlauben.
public void setName( String name )
{
 this.name = Objects.nonNull( name );
}
Alternativ ist eine Fehlermeldung möglich:
public void setName( String name )
{
 this.name = Objects.nonNull( name, "name is not supposed to be null" );
}


class java.lang.Objects

§          static T nonNull( T obj )
Löst eine NullPointerExcpetion aus, wenn obj gleich null ist. Sonst lieferte obj als Rückgabe.
§          static T nonNull( T obj, String message )
Wie nonNull(obj), nur das die Meldung der NullPointerExcpeption bestimmt wird.

Labels: ,

equals()/hashCode() tief oder flach und mehrdimensionale Arrays

Tiefe oder flache Vergleiche/Hash-Werte

Referenziert ein Objekt Unterobjekte (etwa eine Person ein String-Objekt für den Namen – keine Primitiven – so geben die Methoden equals() und hashCode()den Vergleich bzw. Berechung des Hashcodes an das referenzierte Unterobjekt weiter (wenn es denn nicht-null ist). Ablesen können wir das an folgendem Ausschnitt unserer equals()-Methode.
com/tutego/insel/object/hashcode/Player.java, equals() Ausschnitt
if ( name == null )
  if ( ((Player)that).name != null )
    return false;
else if ( !name.equals( ((Player)that).name ) )
  return false;

Es ist demnach Aufgabe der String-Klasse (name ist vom Typ String) den Gleichheitstest vorzunehmen. Das heißt, dass zwei Personen problemlos equals()-gleich sein können, auch wenn sie zwei nicht-identische, aber equals()-gleiche String-Objekte referenzieren.
Auch bei hashCode() ist diese Delegation an das referenzierte Unterobjekt abzulesen:
com/tutego/insel/object/hashcode/Player.java, hashCode() Ausschnitt
result = 31 * result + ((name == null) ? 0 : name.hashCode());
Dass eine equals()-Methode bzw. hashCode() einer Klasse den Vergleich bzw. die Hashcode-Berechnung nicht an die Unterobjekte delegiert, sondern selbst umsetzt, ist unüblich.

equals()/hashCode()-Berechnung bei (mehrdimensionalen) Arrays

Einen gewissen Sonderfall bei equals()/hashCode() nehmen mehrdimensionale Arrays ein. Mehrdimensionale Arrays sind nichts anderes als Arrays von Arrays. Das erste Array für die erste Dimension referenziert jeweils auf Unterarrays für die zweite Dimension. Wichtig wird diese Realisierung bei der Frage, wie diese Verweise der ersten Dimension nun bei equals() betrachtet werden sollen. Denn hier stellt sich die Frage, ob die Unterarrays von zwei zu testenden Arrays nur identisch oder auch gleich sein dürfen. Diese Frage hatten wir schon im Kapitel 3, „Felder vergleichen mit Arrays.equals() und Arrays.deepEquals()“ angesprochen.
Enthält unsere Klasse ein Array und es soll in einem equals() mit berücksichtigt werden, so sind prinzipiell drei Varianten zum Umgang mit diesem Array möglich. Felder selbst einfach mit == wie primitive Werte zu vergleichen ist keine gute Lösung, da Arrays Objekte sind, die wie Strings nicht einfach mit == zu vergleichen sind. Während allerdings Objekte ein equals() haben, bieten Arrays keine eigene equals()-Methode, sondern diese ist in die Utility-Klasse Arrays gewandert. Hier gibt es jedoch 2 Methoden die die in Frage kämen. Arrays.equals(Object[] a, Object[] a2)  geht jedes Element von a, also bei mehrdimensionalen Arrays jede Referenz auf ein Unterarray durch, und testet, ob es identisch zu einem zweiten Feld a2 ist. Wenn also zwei gleiche aber nicht-identische Hauptarrays identische Unterarray besitzen, liefert Arrays.equals() die Rückgabe true, aber nicht, wenn die Unterarrays zwar gleich, aber nicht identisch sind. Spielt das eine Rolle, so ist Arrays.deepEquals() die passende Methode, denn sie geht fragt immer mit equals() die Unterarray ab.
Bei der Berechnung vom Hash-Wert gibt es eine vergleichbare Frage. Die Arrays-Klasse bietet zur Berechnung vom Hash-Wert eines ganzes Arrays die Methoden Arrays.hashCode() und Arrays.deepHashCode(). Die erste Methode fragt jedes Unterelement über die von Object angebotene Methode hashCode()  nach dem Hash-Wert. Nehmen wir ein mehrdimensionales Array an. Dann ist das Unterelement ebenfalls ein Feld. Arrays.hashCode() wird dann wie erwähnt nur die hashCode()-Methode auf dem Feld-Objekt aufrufen, während Arrays.deepHashCode() auch Unterarray herabsteigt und solange Arrays.deepHashCode() auf allen Unterfeldern aufruft, bis ein equals()-Vergleich auf einem nicht-Feld möglich ist.
Was heißt das nun für unsere equals()/hashCode()-Methode? Üblich ist der Einsatz von Arrays.equals() und nicht von Arrays.deepEquals() genauso wie Arrays.hashCode() üblicher als Arrays.deepHashCode() ist.
Das folgende Beispiel zeigt das in der Anwendung. Die Methoden wurden von Eclipse generiert und etwas kompakter geschrieben:
com/tutego/insel/object/hashcode/Chess.java, Chess
char[][] chessboard;

@Override public int hashCode()
{
  return 31 + Arrays.hashCode( chessboard );
}

@Override public boolean equals( Object obj )
{
  if ( this == obj )
    return true;
  if ( obj == null )
    return false;
  if ( getClass() != obj.getClass() )
    return false;
  if ( ! Arrays.equals( chessboard, ((Chess) obj).chessboard ) )
    return false;
  return true;
}

Labels: