http://cr.openjdk.java.net/~anazarov/jdk8-demo-bulkoperations/webrev.00/
command-line tool to find static dependencies in java 8 könnte kommen
Automatisch Fehler finden
Das verspricht ein neues Google Werkzeug
„error-prone – Catch common Java mistakes as compile-time errors – Google Project Hosting“ http://feedly.com/k/18e1CMT
Was sind eure Ergebnisse?
Die SQL3-Datentypen ARRAY, STRUCT und REF
Seitdem JDBC 2.0 die SQL3-Datentypen unterstützt, sind weitere Spaltentypen über Java-Programme zugänglich:
- ARRAY. Der Datentyp erlaubt eine Aufzählung mehrerer Werte wie ein Feld in einer Spalte.
- STRUCT. Neue benutzerdefinierte Typen – auch UDTs (user-defined types) genannt –, die auf der Datenbankseite mit der SQL-Anweisung CREATE TYPE angelegt werden.
- REF. Verweise auf strukturierte Typen.
Am Nützlichsten ist das Feld.
ARRAY
SQL-Felder werden in JDBC durch die Schnittstelle java.sql.Array behandelt. Nehmen wir in einer Tabelle eine Spalte Aktienverlauf an, die eine Liste (Array) von Zahlen enthält. Aus dem ResultSet gibt dann getArray() Zugriff auf die Informationen:
Array verlauf = rs.getArray( "Aktienverlauf" );
Die Variable verlauf verweist jetzt auf das Array-Objekt, was dem SQL ARRAY entspricht. Die Werte können nun entnommen werden, doch nicht mit einem Iterator, und auch nicht mit get(index) Funktionen, sondern wieder mit einem Aufruf getArray(). Es liefert ein Feld genau des passenden Typs zurück.
String[] verläufe = (String[]) verlauf.getArray(); for ( int i = 0; i < verläufe.length; i++ ) ...
Neben dem lesenden getArray() setzt setArray() ein Feld, und updateArray() aktualisiert die Werte.
Kreditkartennummern in Java testen
Sind Kreditkartennummern korrekt aufgebaut? Wie lassen sich sich Kreditkartennummern generieren?
E-Commerce-Lösungen sind im Internet mittlerweile häufig anzutreffen. Lassen sich für kleine Beträge Sonderlösungen finden, werden für größere Beträge immer noch Kreditkarten verwendet. Grund genug für uns Java-Programmierer, die Nummern der Karten zu testen, um zu überprüfen, ob uns nicht ein Anwender täuschen wollte.
Die Nummer einer Kreditkarte setzt sich nicht willkürlich zusammen. Die Nummern von Karten eines bestimmten Herstellers bestehen aus einer festen Anzahl von meistens 14 bis 16 Ziffern. Als Kennung für einen Hersteller (Visa (Veni, Vidi, VISA: I came, I saw, I did a little shopping.) , MasterCard, American Express) ist jeder Nummer eine zusätzliche Kennung von einer bis vier Ziffern vorangestellt. Die Ziffern der Kartennummer werden durch mathematische Verfahren überprüft. Wir wollen eines dieser Verfahren auch kennen lernen; den so genannten Luhn-Algorithmus. Dieser Algorithmus testet die Korrektheit des Aufbaus einer Nummernfolge. Die letzte Ziffer ist oft eine berechnete Checksummen-Ziffer.
Die folgende Tabelle gibt eine Übersicht über einige Kartenhersteller. Sie führt die Kennung, die Länge der Kartennummer und ein gültiges Beispiel auf:
Hersteller |
Anfang |
Gesamtlänge |
Beispiel |
Visa |
4 |
13,16 |
4111 1111 1111 1111 |
Master |
51,52,53,54,55 |
16 |
5500 0000 0000 0004 |
Diner’s Club11 |
30,36,38 |
14 |
3000 0000 0000 04 |
American Express12 |
34,37 |
15 |
3400 0000 0000 009 |
Discover |
6011 |
16 |
6011 0000 0000 0004 |
en Route |
2014,21 |
15 |
2014 0000 0000 009 |
JCB |
3088,3096,3112,3158,3337,3528 |
16 |
3088 0000 0000 0009 |
Neben den Herstellernummern sind auch folgende Nummern von den ausgebenden Banken im Umlauf: Manufacturers Hanover Trust (1033), Citibank (1035), Chemical Bank (1263), Chase Manhattan (1665), Bank of America (4024), Citicorp (4128), New Era Bank (4209), HHBC (4302), Imperial Savings (4310), MBNA (4313), California Federal (4317), Wells Fargo (5282), Citibank (5424), Wells Fargo (5410), Bank of New York (5432), MBNA (6017). Carte Blanche und Diner’s Club haben die gleichen Nummern.
Einen Abend im Februar 1950 vergaß Frank MacNamara seine Brieftasche. Er kam auf die Idee, eine Kreditkarte aus Karton anzubieten. Mit seinen Freunden gründete er am 28.2.1950 den Diner’s Club, der im Gründerjahr mehr als 10.000 Mitglieder und 1.000 Vertragspartner hatte. So war die erste Kreditkarte geboren. Im Jahre 1958 entschloss sich das internationale Transport-, Reise-, und Finanzierungsunternehmen American Express, eine eigene Karte herauszugeben.
Die Überprüfung mit dem Luhn-Algorithmus
Der Luhn-Algorithmus (auch modulus 10 oder mod 10-Algorithmus genannt) basiert auf dem ANSI-Vorschlag X4.13. Er wurde Ende 1960 von einer Gruppe Mathematiker ausgearbeitet und veröffentlicht. Danach nutzten Kreditkartenhersteller dieses Verfahren zur Prüfung der Kreditkartennummern. Auch die Versichertennummer in Kanada, die Canadian Social Insurance Number (CSIN), wird über das Luhn-Verfahren geprüft.
Der Algorithmus testet, ob die letzte Ziffer der Kreditkartennummer korrekt zu den angegebenen Zahlen passt. Die Testziffer wird von allen Ziffern außer der letzten Ziffer berechnet und anschließend mit der angegebenen Testziffer verglichen. Stimmt sie überein, ist die Karte seitens der Prüfnummer in Ordnung. Wir wollen das Verfahren hier nicht näher vertiefen, sondern einfach den Algorithmus angeben:
class LuhnTest
{
static boolean luhnTest( String s )
{
int len = s.length();
int digits[] = new int[len];
for ( int i = 0; i < len; i++ )
{
try {
digits[i] = Integer.parseInt( s.substring(i,i+1) );
}
catch ( NumberFormatException e ) {
System.err.println( e );
return false;
}
}
int sum=0;
while ( len > 0 )
{
sum += digits[len-1];
len--;
if ( len > 0 )
{
int digit = 2*digits[len-1];
sum += ( digit>9) ? digit-9 : digit;
len--;
}
}
return ( sum%10 == 0 );
}
static boolean isVisa( String s )
{
if ( ( (s.length() == 16) || (s.length() == 13) ) &&
(s.charAt(0) == '4') )
return luhnTest( s );
return false;
}
public static void main( String args[] )
{
System.out.println( luhnTest( "4111111111111111" ) );
System.out.println( luhnTest( "5500000000000004" ) );
System.out.println( luhnTest( "340000000000009" ) );
System.out.println( luhnTest( "30000000000004" ) );
System.out.println( luhnTest( "601100000000000" ) );
System.out.println( luhnTest( "201400000000009" ) );
System.out.println( luhnTest( "3088000000000009" ) );
System.out.println( luhnTest( "9238475098787444" ) );
System.out.println( isVisa( "4111111111111111" ) );
System.out.println( isVisa( "5500000000000004" ) );
// Böse: Visa-Nummer generieren
char[] c = "4123456789123456".toCharArray();
while ( !isVisa(new String(c)) )
c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);
System.out.println( c );
}
}
Im Quelltext ist eine zusätzliche Methode eingebaut, die testet, ob die Karte von Visa ist. Dazu müssen wir nur überprüfen, ob die erste Ziffer eine 4 und die gesamte Zahl nach dem Luhn-Verfahren gültig ist. Andere Tests sind genauso einfach durchzuführen. Eine mögliche Erweiterung wäre, die Methode fehlertoleranter zu gestalten, indem Trennzeichen herausgefiltert werden. Dies und die Implementierung der übrigen Tests überlasse ich als Übung den Lesern.
Wir beginnen mit einer vorgegebenen, unsinnigen Kartennummer, deren erste Stelle "4" ist, wie für eine Visa-Karte erforderlich. Anschließend ändern wir in einer Schleife eine zufällig ausgewählte Stelle der Kartennummer (außer der ersten) in eine ebenfalls zufällig bestimmte Ziffer aus dem Bereich "0" bis "9". Das wiederholen wir so lange, bis die abgewandelte Zahl irgendwann passt.
Beispiel Mit diesen Methoden ist es natürlich leicht möglich, Nummern zu erzeugen. Betrachten wir Folgendes:
char c[] = "4123456789123456".toCharArray();
while ( !isVisa(new String(c)) )
c[(int)(Math.random()*c.length-1)+1] = (char)('0'+Math.random()*9.9);
System.out.println( c );
JDBC LOBs (Large Objects)
Auf der Datenbankseite gibt es zwei Typen für besonders große Daten: BLOB und CLOB. »B« steht für Binary und »C« für Character, also steht ein SQL BLOB für beliebig große Binärdaten und ein CLOB für beliebig große Textdaten.
Die LOBs unterscheiden sich von den anderen Datentypen dadurch, dass der Treiber erst dann die Daten überträgt, wenn sie auch angesprochen werden: Wird eine Zeile mit einer Zahl übertragen, so wird der Treiber auch diese Zahl immer mitschicken. Bei den LOBs sieht das anders aus: Intern steckt dort eine Art Verweis (LOCATION), die mitgeschickt wird, aber nicht die Daten selbst. Durch die Kapselung im Treiber fällt das allerdings nicht auf.
Für den BLOB gibt es in JDBC die Schnittstelle Blob und für CLOB Clob. In beiden Fällen können die großen Daten erfragt, aktualisiert und angelegt werden. Außerdem lässt sich die Länge erfragen und lassen sich ab einer bestimmten Position Daten auslesen.
Einen BLOB besorgen
Einem BLOB/CLOB steht genau, wie es für andere Datentypen entsprechende getXXX()-Funktionen gibt, eine getBlob()/getClob()-Funktion zur Verfügung. Der Unterschied besteht nur darin, dass getInt() direkt ein int zurückgibt, während getBlob() nur eine Referenz auf ein Blob-Objekt liefert, über die im zweiten Schritt die Daten zu beziehen sind.
Beispiel: Eine Tabelle Persons weist eine Spalte Image auf, die eine Grafik einer Person speichert. Die Grafik ist in einer Spalte vom Typ BLOB.
ResultSet rs = stmt.executeQuery( "SELECT * FROM Persons" );
rs.first();
Blob data = rs.getBlob( "Image" );
data bezieht sich jetzt auf das Blob-Objekt. Es enthält noch nicht die Daten. Sie lassen sich zum Beispiel über die Methode data.getBinaryStream() beziehen. Damit lässt sich dieser InputStream toll im Konstruktor von ImageIcon() einsetzen, der aus den Daten dann gleich eine Swing-taugliche Grafik konstruiert. Teile des Datenfeldes werden mit byte[] getBytes(start, ende) angefordert. data.length() liefert die Anzahl der Elemente des LOBs.
Nostalgia: Das JavaBeans Development Kit (BDK)
Das BDK ist (war) eine Testumgebung für anzeigeorientierte Beans und Komponenten ohne visuelle Repräsentation wie Datenstrukturen und Container. Es ist nicht mehr im Original, sondern nur noch in der Version unter https://java.net/projects/bean-builder erhältlich.
Die Bean-Box bietet eine Arbeitsfläche, auf der die Komponenten platziert und verbunden werden können. Wird in der Bean-Box eine Komponente auf dem Bildschirm gesetzt, so liest Java aus der Bean die Informationen (die Properties genannt werden) aus und stellt sie dar. Nun können mehrere Beans miteinander verbunden werden, sodass etwa eine Komponente ein Ereignis auslöst, auf das die zweite Komponente reagiert. Alle Aktionen können direkt ohne eine Zeile Programmcode entworfen werden. Wir entwerfen zunächst im Design-Modus die Verbindungen, um sie dann im Laufzeit-Modus zu testen. Damit reiht sich die Bean-Box in die Reihe der grafischen Entwicklungsumgebungen wie etwa IBMs Visual Age ein. Ganz so komfortabel ist die Bean-Box dann aber auch nicht. Sie ist von Sun lediglich zum Testen und Anzeigen von Beans entworfen worden. Dies zeigt sich auch daran, dass beim Verlassen der Umgebung nicht einmal nach dem Speichern gefragt wird.
Die Bean-Box starten
In der BDK-Version 1.1 wechseln wir in das bdk/beanbox/bin-Verzeichnis und rufen die Skripte run.bat (Windows) oder run.sh (Unix) auf. Dann startet die Umgebung.
Eine Beispielsitzung im BDK
Das BDK ist nach dem Start in drei Fenster unterteilt. Dies sind die ToolBox, die Bean-Box und das Eigenschaften-Feld. Das ToolBox-Fenster enthält einige Beispielkomponenten, die wir verwenden können. Die Beispiele werden zur Laufzeit aus dem bdk/jars/-Verzeichnis genommen. Eigene Komponenten können wir als Jar-Dateien einfach dort hineinkopieren. Die eigentliche Bean-Box stellt die Zeichenfläche dar. Sie ist der wichtigste Teil des Pakets, weil auf ihr Komponenten platziert und getestet werden können. Wir wollen dies anhand der Komponente Molecule zeigen. Wird sie in der ToolBox angeklickt, so verwandelt sich der Cursor in einen Positionszeiger. Klicken wir in der Bean-Box auf eine Stelle, wird ein Molekül angezeigt. Es lässt sich verschieben und interaktiv in der Größe ändern. Die Eigenschaften jeder Komponente lassen sich im Property-Sheet ändern. Sie bestimmen das Aussehen und das Verhalten. Ist das Molekül aktiviert, erscheint im Eigenschaften-Dialog eine Auswahlliste mit einigen Molekülen. Eine Auswahl wirkt sich direkt in der Anzeige aus.
Hinweis: Leider können selektierte Komponenten nicht mit der Entfernen-Taste von der Oberfläche beseitigt werden. Hier ist nur der Gang über das Edit-Menü erfolgversprechend.
Das Zusammenspiel der Komponenten lässt sich testen, indem wir den Design-Modus verlassen. Dazu aktivieren wir im Menüpunkt View den ersten Punkt Disable Design Mode (der sich dann in Enable Design Mode umbenennt). Jetzt kann das Molekül mit der Maus verschoben werden.
Verknüpfungen zwischen Komponenten
Die hervorragendste Eigenschaft der grafischen Programmierung ist das visuelle Setzen von Beziehungen zwischen Komponenten. Dabei werden Ereignisse an Methoden anderer Beans gebunden. Wir wollen das mit der Komponente JellyBean und dem Molekül testen. Wenn wir nun mit der Maus auf die JellyBean klicken (also ein Mouse-Event auslösen), dann soll eine bestimmte Methode des Bean-Moleküls aufgerufen werden, sodass es sich auf dem Bildschirm dreht. Wir verbinden hier also einfach eine Ereignisquelle mit einer Ereignisbehandlung einer anderen Komponente. Die Bedienung der Bean-Box ist dabei so intuitiv, dass sie zum Spielen und Ausprobieren einlädt. Wir aktivieren die erste Schaltfläche und wählen den Menüpunkt Edit > Events > mouse > mousePressed.
Jetzt erscheint eine rote Linie, die mit der Maus bewegt wird. Nun klicken wir auf unser Molekül. Daraufhin erscheint eine Liste mit Funktionen, die bei der Mausbewegung ausgelöst werden können. Wir entscheiden uns für rotateOnX.
Beans speichern
In der Bean-Box lässt sich unter dem File-Menü die Bean in ein Jar-Archiv für ein Applet einwickeln. Der Speicherdialog schlägt standardmäßig ein Unterverzeichnis der Bean-Box vor. Zusätzlich zur serialisierten Klasse befinden sich im Archiv noch die vom BDK erstellten Metainformationen, die Quellcodes zu der Bean in einem Extra-Verzeichnis, eine HTML-Datei sowie eine Readme-Datei mit den Informationen darüber, welche Dateien zur Komponente gehören. Die HTML-Datei erhält eine Referenz auf die Klassendatei, sodass ein Applet-Viewer oder ein Browser direkt die Bean darstellen kann. Alte Browser unterstützen das Jar-Format im Applet-Tag der HTML-Datei nicht.
Die kleinste Bohne der Welt
Wir wollen uns nicht lange mit theoretischem Schnickschnack aufhalten – dafür bleibt noch genügend Zeit. Beginnen wir mit einer Komponente, die wir FirstBean nennen wollen.
package com.javatutor.insel.bean; import java.applet.Applet; import java.awt.*; public class FirstBean extends Applet { @Override public void paint( Graphics g ) { g.drawString( "Hui; so kurz?", 20, 20 ); } }
Zweifellos wird die Kürze dieser Lösung verwundern. Das kommt daher, dass sich die Beans gut in die vorhandene Technologie einfügen und schon viele Komponenten als Beans gebaut sind.
Beginnen wir nun, das schon in der Einführung als einfaches Beispiel für Komponenten identifizierte Applet in unseren Application-Builder einzubauen. Dazu muss die Softwarekomponente in einem speziellen Format vorliegen: in Jar-Dateien.
Jar-Archive für Komponenten
Eine Jar-Datei ist ein Archivformat, das Sun zum Verbund von Klassen und Mediendateien einführte. Nach dem Compilieren der Klasse FirstBean wird die Datei mit einem zusätzlichen Manifest in eine Jar-Datei gepackt. Damit der Application-Builder erkennen kann, dass es sich dabei um eine Bean handelt, erwartet er eine bestimmte Information in einer Manifest-Datei, die automatisch beim Zusammenpacken erzeugt wird. Stünden diese Informationen nicht dabei, könnte der Application-Builder die Bean nicht erkennen.
Wir wollen die Jar-Datei mit dem Dienstprogramm jar von Hand erzeugen. Zunächst legen wir eine Manifest-Datei an, die wir nach der Komponente benennen.
FirstBean.mf
Manifest-Version: 1.0 Name: FirstBean.class Java-Bean: True
Hinweis: Vor den Doppelpunkten dürfen keine Leerzeichen stehen, sonst erkennt Jar die erste Option nicht und bemängelt den fehlerhaften Header. Leider ist Jar nicht sehr fehlertolerant.
Befinden sich im Jar-Archiv mehrere Klassen, sollten ihre Dateinamen mit Return getrennt sein. Bei Paketnamen ist eine Trennung mit / vorgesehen, nicht mit dem Windows-\.
Im nächsten Schritt packen wir mit jar die Klassen sowie die Manifest-Datei zu einem Archiv zusammen. Hier müssen wir die Optionen cfm nutzen. Merken wir uns die Option im englischen Satz »Create a File with a Manifest«, übersetzt »Lege die Datei mit Manifest an« .
$ jar cfm FirstBean.jar FirstBean.mf FirstBean.class
Für eine genauere Beschreibung des Dienstprogramms jar sei auf Kapitel in der Insel verwiesen.
Wurde das Jar-Archiv erfolgreich erstellt, muss dem Application-Builder die Bean bekannt gemacht werden. Hier unterscheiden sich die Produkte der einzelnen Hersteller voneinander. Im BDK von Sun müssen alle Beans im Unterverzeichnis jars liegen, damit sie das BDK automatisch erkennt. Kopieren wir unsere Bean dort hinein. Unter Windows schreiben wir:
$ copy FirstBean.jar c:\bdk\jars
Wenn wir jetzt die Bean-Box starten, erscheint unsere Komponente mit in der Liste. Im jars-Verzeichnis stehen auch die anderen Beans.
Tipp: Eine etwas schnellere Vorgehensweise besteht darin, die Jar-Datei nicht in das jars-Verzeichnis, sondern in ein eigenes Verzeichnis zu legen. Das hat den Vorteil, dass die Bean-Box schneller startet, weil sie dann nicht die zusätzliche Bean auswerten muss, was immer Zeit kostet. Wenn wir Beans nicht benötigen, können wir sie auch verschieben oder löschen, was die Ladezeit zusätzlich erhöht. Damit in diesem Fall das eigene Jar-Archiv eingebunden wird, laden wir es unter dem Menüpunkt File > Load Jar.
Wenn wir wiederholt Beans in ein Archiv packen und keine integrierte Entwicklungsumgebung diese Arbeit erledigt, kann uns ein kleines Skript helfen. Ein Beispiel für Windows wäre:
a.bat
javac zzz.java jar cfm zzz.jar zzz.mf zzz.class copy zzz.jar c:\bdk\jars
Bean-Eigenschaften anpassen
In einem visuellen Entwicklungswerkzeug lassen sich alle ausgezeichneten Eigenschaften einer Bean anpassen. Dazu bietet die Umgebung oft ein zweigeteiltes Fenster an. Auf der einen Seite befinden sich die Eigenschaften und auf der anderen Seite die Editoren für jede Eigenschaft. Die folgende Abbildung zeigt einige Properties und deren Anzeigen.
Haben wir zum Beispiel als Eigenschaft eine Zeichenkette (wie der angezeigte Text eines Label-Objekts), können wir diese einfach in das Feld eintragen. Mit einem eigenen Property-Editor sind wir jedoch nicht auf die einfachen Datentypen beschränkt. Was ist, wenn etwa eine Komponente die Auswahl zwischen DM und Euro anbieten will? Wir können dem Benutzer nicht zumuten, dies in Zahlen einzugeben. Auch bei einem Label gibt es zum Beispiel die Möglichkeit, einen Cursor aus einer Liste auszuwählen.
Für ungewöhnliche Eigenschaften können wir einen eigenen Property-Editor definieren. Dazu kann der Editor den Anzeigebereich als Grafikfläche beschreiben.
Customizer
Reicht auch der Editor nicht aus, zum Beispiel bei einer Farbe, die wir gerne aus einem Farbkreis auswählen wollen, lässt sich zudem ein Customizer definieren, der noch einen Schritt weiter geht, denn für einen Customizer ist ein eigenes Fenster vorgesehen. Er soll mit einer einfachen Benutzerführung den internen Zustand ändern. IBM sieht für seine Beans zum Beispiel ein Hilfe-Feld vor. In WebGain wird dieser Customizer durch eine Schaltfläche mit drei Punkten angezeigt.
Property-Editoren
Für jede sichtbare Eigenschaft einer Bean gibt es einen Property-Editor, der den Wert darstellen und ihn bei einer veränderbaren Eigenschaft auch editieren kann. Bekommt die Eigenschaft den Fokus, wird automatisch der passende Editor ausgewählt. Für alle primitiven Datentypen gibt es standardmäßig Editoren, und zusätzlich kommen Auswahlmöglichkeiten für String, Color und Font hinzu. Ein Editor gilt für jeweils eine Property. Die Editoren werden an einer zentralen Stelle, dem Property-Editor-Manager, registriert und dann beim Fokus ausgewählt und aktiviert. Das ist Aufgabe der Entwicklungsumgebung.
Damit die Einstellmöglichkeit nicht auf diese einfachen Typen beschränkt bleibt, können wir für eine Eigenschaft einen eigenen Property-Editor konstruieren. Der kann eine ganz einfache Textzeile darstellen oder auch eine eindrucksvolle Multimedia-Komponente. Der Editor übernimmt die Anzeige und ist gleichzeitig das Modell (also gibt es keine Trennung von Daten und Visualisierung an dieser Stelle). Der Wert ist als Object in unserem Editor gespeichert. Außerdem muss der Editor die Schnittstelle java.beans.PropertyEditor implementieren oder alternativ die Klasse java.beans.PropertyEditorSupport erweitern, was meist einfacher ist.
BeanInfo
Durch Introspection/Reflection existiert ein leistungsfähiger Mechanismus, um die Eigenschaften und Ereignisse zur Laufzeit auszulesen. In der Regel nimmt die Entwicklungsumgebung dafür Methoden, die sich an die Namenskonvention halten. Es gibt aber noch eine zweite Möglichkeit, und die lässt sich über eine Bean-Information-Klasse nutzen. Sie bietet folgende Funktionalität:
- explizites Auflisten der freigegebenen Leistungen, die nach außen sichtbar sein sollen
- Zuordnung eines mit der Bean verwendeten Icons
- Anmeldung einer Customizer-Klasse
Sind die freigegebenen Leistungen aufgelistet, wird damit eine Untersuchung der Bean-Klassen auf die Namensgebung verhindert. Es gibt daher zur Freigabe der Eigenschaften und Ereignisse spezielle Methoden, die von uns gefüllt werden, indem wir jede Eigenschaft auflisten. Jede Methode nutzt zur Beschreibung der Leistungen so genannte Deskriptoren. Gibt es keinen Deskriptor, wird die jeweilige Eigenschaft, Methode oder das Ereignis nicht veröffentlicht. Gibt die Anfrage-Methode aus der Bean-Information-Klasse null zurück, wird für die jeweilige Eigenschaft/Event/Methode Reflection genutzt. Der Java-Code für dieses Doktor-Spielchen liegt in java.beans.Introspection. Ein kleines Beispiel, das alle get-Methoden der Klasse Point ausgibt:
BeanInfo beanInfo = Introspector.getBeanInfo( Point.class ); for ( PropertyDescriptor p : beanInfo.getPropertyDescriptors() ) System.out.println( p.getReadMethod() );
Die Ausgabe liefert vier Zeilen:
public final native java.lang.Class java.lang.Object.getClass() public java.awt.Point java.awt.Point.getLocation() public double java.awt.Point.getX() public double java.awt.Point.getY()
Beliebte Fehler
Das Programmieren von Beans ist zwar einfach, doch schleichen sich immer wieder Fehler ein. Dieser Abschnitt soll auflisten, welche Fehler für eine Bean letal sind, sodass sie von einer Umgebung nicht erkannt werden kann.
- Die Klasse muss öffentlich (public) und darf nicht abstrakt sein. Abstrakte Klassen können keine Exemplare bilden, was für eine Bean-Box aber unumgänglich ist.
- Die Klasse muss vom Klassenlader ohne Folgefehler geladen werden können. Der Vorgang wird während des Ladens durch Fehler im static-Block oder durch fehlende Klassen möglicherweise abgebrochen.
- Die Bean muss einen öffentlichen Standard-Konstruktor anbieten.
- Die Klasse muss Serializable implementieren, muss also serialisierbar sein. Das hat zur Konsequenz, dass alle Attribute, die nicht transient sind, ebenso serialisierbar sein müssen. Eine Verletzung dieser Regel liegt beispielsweise vor, wenn eine grafische Komponente eine Referenz auf ein Image-Objekt hält, denn Image-Objekte sind nicht serialisierbar. Wir müssen daher entweder ImageIcon nutzen oder eigene Methoden wie writeObject(), readObject() implementieren. Wenn schon eine Oberklasse serialisierbar ist, dann muss unsere Klasse nicht noch einmal Serializable implementieren, weil wir dann selbst automatisch instanceof Serializable durch die Oberklasse sind. Es bietet sich jedoch zum Zweck der Lesbarkeit an, implements Serializable zu schreiben, damit das auf den ersten Blick sichtbar ist.
Java Mission Control. Wow wow wow
Mehr im blog bei http://hirt.se/blog/?p=343
Pack200-Format
Jar-Dateien enthalten in der Regel eine Mischung aus Klassendateien und Ressourcen. Die Jar-Archive sind normale Zip-Archive und somit steht der bekannte Zip-Algorithmus hinter der Kompression. Das liefert eine befriedigende Kompression über alle Formate, bietet aber keine besonderen Kompressionsverfahren für gewissen Dateitypen. Eine optimale Kompression ist aber gerade bei Jar-Bezug über das Internet wünschenswert. Während JPG-Dateien von Zip auch nicht besser komprimiert werden können, verkürzen sich Klassendateien schon etwas, erreichen mit Zip aber immer noch nicht das Maximum einer möglichen Komprimierung. Das liegt daran, dass Zip eine .class-Datei wie normalen Binärcode betrachtet und den Aufbau der Java-Klassendateien eben nicht kennt. Hier kommt ein spezielles Format zum Zuge, was den Aufbau von Klassendateien berücksichtigt, das Pack200-Format. Neben dem Dienstprogramm jar bietet das JDK im bin-Verzeichnis auch entsprechende Kommandozeilenwerkzeuge pack200 und unpack200 zum Komprimieren und eben Dekomprimieren.
Beispiel: Die Eclipse-Installation bringt genug Jar-Dateien mit, an denen Pack200 ausprobiert werden kann. Unter eclipse\plugins\org.apache.ant_1.8.4.v201303080030\lib nehmen wir uns ant.jar vor, ein Jar-Archiv von 1.941.731 Bytes.
$ pack200.exe -O ant.jar.pack.gz ant.jar
Der Schalter -O optimiert noch etwas und am Ende steht eine Datei ant.jar.pack.gz von 502.721 Bytes, also eine fast viermal kleinere Version.
Das pack200-Tool arbeitet im Prinzip so, dass es eine Jar-Datei nimmt, die Einträge umsortiert, Redundanzen erkennt und in einer Art neue spezielle Mega-Klassendatei verpackt[1], die dann mit dem Standard-GZip-Verfahren komprimiert wird.[2] Pack200- Dateien tragen in der Regel die Dateiendung *.jar.pack.gz und ein Web-Server serviert sie unter dem MIME-Typ application/x-java-pack200. Bezieht ein Programm über Java Web Start oder ein Java Plug-in die Dateien, so werden diese direkt ausgepackt und wie ein Jar eingebunden.
[1] Details und Anwendung zum Verfahren gibt Die Oracle-Seite http://download.java.net/jdk8/docs/technotes/guides/jweb/networking/compression_formats.html#pack200_compression. Intern ist das Verfahren komplex und die Beschreibung über die internen Vorgänge lang, siehe http://docs.oracle.com/javase/7/docs/technotes/guides/pack200/pack-spec.html.
[2] Während jede normale Klassen-Datei mit der Hex-Kennung 0xCAFEBABE beginnt, beginnt der Container der Pack-Datei mit 0xCAFED00D (Cafe Dude) – sympathisch.
Wiederholbare Annotationen: @Repeatable
Normalerweise nutzen Entwickler Annotationen wie einmalige Modifizierer, und es ergibt keinen Sinn, etwa @Override @Override String toString() zu schreiben, genauso wenig wie es keinen Sinn ergibt final static final double PI zu deklarieren. Doch da es durchaus Meta-Daten gibt, die mehrmals auftauchen können, gibt es seit Java 8 eine Erweiterung, dass Annotationen wiederholt werden dürfen. Allerdings müssen die Annotationstypen dieser wiederholbaren Annotationen selbst mit einer besonderen Meta-Annotation @Repeatable ausgezeichnet werden. Damit ist es aber noch nicht getan, denn @Repeatable muss als Element einen Typ bekommen, der den Container angibt.
Beispiel: Der Annotationstyp für Autoren kann so aussehen:
public @interface Author { String name(); }
Soll nun die Annotation mehrfach verwendet werden, ist die Meta-Annotation nötig und mit ihr die Angabe eines Containers:
@Repeatable( Authors.class ) public @interface Author { String name(); }
Der Container ist selbst ein Annotationstyp mit einem Feld als Element. Der Typ des Feldes ist exakt der wiederholbare Annotationstyp.
public @interface Authors { Autor[] value; }
Ohne @Repeatable am Annotationstyp wird eine mehrmalige Verwendung einer Annotation zu einem Compilerfehler führen. Im Java SE 8 gibt es bisher keine Verwendung dieses Annotationstyps, also auch keine wiederholbaren Annotationen.
@Target Annotation seit Java 8
Die Meta-Annotation @java.lang.annotation.Target beschreibt, wo eine Annotation angeheftet werden kann. Ist kein ausdrückliches @Target gewählt, gilt es für alle Elemente, die Annotation kann also etwa an Klassen stehen, aber auch an lokalen Variablen. In der Regel gibt es bei @Target ein Element, und das ist von der Aufzählung java.lang.annotation.ElementType; es deklariert die folgenden Ziele:
ElementType |
Erlaubt Annotationen … |
ANNOTATION_TYPE |
nur an anderen Annotationstypen, was @Target(ANNOTATION_TYPE) somit zu einer Meta-Annotation macht. |
TYPE |
an allen Typdeklarationen, also Klassen, Schnittstellen, Annotationstypen, Aufzählungen. |
CONSTRUCTOR |
an Konstruktor-Deklarationen |
METHOD |
an Deklarationen von statischen und nicht-statischen Methoden. |
FIELD |
an Deklarationen von statischen Variablen und Objekt-Variablen. |
PARAMETER |
an Parametervariablen von Methoden. |
LOCAL_VARIABLE |
an lokalen Variablen. |
PACKAGE |
an package-Deklarationen. |
TYPE_PARAMETER |
an der Deklaration einer Typ-Variablen für generische Typ-Parameter. Neu in Java 8. Wenn es etwa heißt class List<@AnAnnotation T>. |
TYPE_USE |
an allen Stellen, wo Typen eingesetzt werden, adressiert also Typ-Annotationen. Ebenfalls neu in Java 8. So etwas wie @NonNull (keine Annotation aus der Java SE!) ist ein Beispiel |
Tabelle 1.3: ElementType bestimmt Orte, an denen Annotationen erlaubt sind.
Orte für Annotationen
Annotationen lasen sich setzen an allen Deklarationen und bei Typnutzungen (ab Java 8).
Deklarationen |
Typnutzung |
Typendeklarationen (Klassen, Schnittstellen, Aufzählungen, anderen Annotationtypen) |
new-Operator |
Eigenschaften (Konstruktoren, Methoden, Attributen) |
Typ-Anpassung |
implements-Klausel |
|
throws-Klausel bei Methoden |
Wo Annotationen möglich sind
Die Annotationen bei der Typnutzung nennen sich kurz Typ-Annotationen.
Java bringt einige Annotationstypen mit, doch die werden bisher ausschließlich für Deklarationen eingesetzt, wie das gekannte @Override. Vordefinierte Typ-Annotationen sind bisher in der Java SE nicht zu finden.
JDK 8 Developer Preview nun veröffentlicht
Weitere Details bei Mark unter http://mreinhold.org/blog/jdk8-preview
Guava 15.0 ist raus
Guava 15.0 is now available on Maven Central! You can install it using Mave… [link]“ http://feedly.com/k/187qiXt
Was sind Markierungsschnittstellen (Marker-Interfaces)?
Markierungsschnittstellen kann man als Design-Pattern ansehen. Sie tauchen zwar nicht so häufig auf, doch Java kennt mit Serializable und Remote zwei prominente Vertreter.
Es anderes Beispiel soll hergestellt werden: Eine Textverarbeitung speichert in erster Linie Text. Dazu kommen noch weitere Datentypen wie Tabellen, Grafiken oder Formeln, aber wie der Name Textverarbeitung zeigt, ist der Datentyp im Kern Text. Etwas genauer betrachtet besteht der Text aus Seiten, Absätzen, Zeilen und Zeichen.
Frage: Nehmen wir an, dass die Textverarbeitung für jedes Zeichen ein Objekt vorsieht, welches in einer langen verketteten Liste gespeichert ist. Beim Eintippen neuer Buchstaben werden neue Objekte in die Liste eingefügt und beim Löschen entfernt. Stellt die Textverarbeitung eine Seite dar, wird die Liste sequentiell abgelaufen und jedes Element auf dem Bildschirm ausgegeben. Jedes ausgegebene Zeichen bekommt eine Position und diese wird laufend angepasst.
Zu den normalen druckbaren Zeichen wie Buchstaben und Ziffern kommen Sonderzeichen hinzu. Ein Objektmodell kann dann so aussehen, dass eine Schnittstelle notwendiges für alle Zeichen vorschreibt. Nennen wir die Schnittstelle WordCharacter und geben wir ihr die Methoden getChar(), getCharWidth(), getCharHeight() und drawChar(). Als Unterklassen könnte dann OrdinaryCharacter die druckbaren Zeichen behandeln und MetaCharacter die Sonderzeichen wie Tabulator, Leerzeichen, Return. Läuft ein Algorithmus zur Visualisierung durch die Datenstruktur, besorgt er sich jedes Zeichen, fragt wie lang es ist und ob es mit dem Wort noch in die Zeile passt. Wenn ja, wird die Zeichenposition auf der x-Achse um die Breite des Zeichens erweitert und das nächste Zeichen wird ausgegeben.
Ein Leerzeichen nimmt unter den Sonderzeichen eine besondere Stelle ein. Das liegt auch daran, dass der Leerraum unter Umständen noch erweitert wird, etwa beim Blocksatz. Dann kommt zu jeder Breite eines Leerzeichens noch zusätzlicher Weißraum dazu.
Wie wird die Textverarbeitung normale Zeichen und Leerzeichen verarbeiten, wenn eine Zeile ausgeben wird? Da Leerzeichen nichts auf dem Bildschirm ausgehen, sollen sie besonders behandelt werden.
Eine Lösung: Wenn in der Datenstruktur die Zeichen der Reihe nach vorliegen, wird man in einer Fallunterscheidung feststellen können, ob es sich um ein Leerzeichen handelt oder um anderes Zeichen.
für alle Zeichen einer Zeile Character c ist das aktuelle Element ist c.getChar() != ' ' dann verarbeite das Element weiter
Problem: Falls es nun weitere Elemente gibt, die eine ähnliche Sonderbehandlung haben wie das Leerzeichen, wird die Fallunterscheidung immer länger werden. Das wird sie auch, denn es gibt neben dem Standard-Leerzeichen noch weitere Zeichen, die für die Weißraum zuständig sind. Dazu zählt der Tabulator oder das nicht-trennende Leerzeichen. Wenn diese Fallunterscheidung auch noch an anderen Stellen eingesetzt wird, etwa beim Export der Daten in HTML, PDF, müssen die Entwickler darauf achten, Änderungen an der if-Anweisung auch konsistent an den anderen Stellen anzuwenden.
Pattern: Unsere bisherige Unterscheidung war nur auf Grund des Zeichencodes. Interessant ist, wenn das Leerzeichen noch einen weiteren Typ besitzt, denn wenn ein Weißraum-Objekt etwa den Typ WhitespaceChracter haben würde, ließe sich wiederum instanceof einsetzen, um herauszufinden, ob ein Zeichen-Objekt ein Leerzeichen ist oder nicht. Eine Fallunterscheidung mit vielen if-Anfragen fäll raus und es bleibt eine einfache instanceof-Abfrage. Alle Weißraum-Objekte legen wir dann so aus, dass sie einen speziellen Typ haben. Dazu kann ein Leerzeichen-Objekt eine Schnittstelle implementieren, die keine Funktionen vorschreibt.
Eine leere Schnittstelle nennt sich Marker-Interface oder Tag-Interface. Sie markiert ein Objekt mit einem Typ, der anschließend mit instanceof überprüft werden kann. In Java. Es gibt ein Java Marker-Interfaces an einigen Stellen. Die interessanteste Schnittstelle ist java.io.Serializable. Sie markiert alle Objekte, die über die Standardserialisierung automatisch geschrieben werden können. Eine Klasse, die nicht Serializabe ist, die aber trotzdem geschrieben werden soll, wird bei dem Serialisierungs-Vorgang einen Ausnahme melden.
Ein zweites Beispiel ist die Schnittstelle Remote. Sie wird benötigt, wenn Objekte über RMI verteilt angeboten werden sollen. Nur Objekte, die Remote implementieren werden RMI-fähig sein. Eine weitere Schnittstelle ist Clonable. Sie ermöglichst erst das Klonen von Objekten mit einer clone()-Funktion.
Seit Java 5 sind Annotationen eine Alternative zu Markierungsschnittstellen und daher seltener zu finden.
Das oberste Stack-Element duplizieren
Die Klasse Stack besitzt zwar die Basisfunktionalität, die ein Stapel besitzen sollte, aber auch nicht mehr. Hin und wieder wünschen wir uns aber eine Funktion, die das oberste Stack-Element dupliziert, kurz dup(). Allerdings ist das schwerer als gedacht, und das erklärt auch, warum es so eine Methode nicht gibt.
Bei der Implementierung treten zwei Fragen auf, mit denen zwei völlig unterschiedliche Lösungsansätze verbunden sind. Da die Klasse Stack wie die anderen Datenstrukturen auf Objekte ausgelegt ist, müssen wir uns darüber Klarheit verschaffen, wie das obere Objekt dupliziert werden soll. Soll eine Kopie der Objekt-Referenz neu auf den Stapel gelegt werden oder etwa das gesamte Objekt geklont werden?
Die einfache Lösung
Die einfachste Lösung besteht darin, das oberste Objekt einfach mittels der schon vorhandenen Stack-Methoden push() und peek() draufzulegen. Nehmen wir an, wir haben eine Unterklasse DupStack, dann sieht die erste Variante zum Clonen so aus:
void dup() /* throws EmptyStackException */ { push( peek() ); }
peek() gibt aber lediglich eine Referenz auf das Objekt zurück. Und das anschließende push() speichert diese Referenz dann auf dem Stapel. Nehmen wir an, wir haben zwei StringBuffer-Objekte auf dem Stapel. Wenn wir nun dup() aufrufen und den String ändern, der oben auf dem Stapel liegt, so ändern wir automatisch das zweite Element gleich mit. Dies ist aber nicht unbedingt beabsichtigt, und wir müssen uns Gedanken über eine alternative Lösung machen. Wir sehen, dass dup() in der Klasse Stack fehlt, weil seine Implementierung davon abhängt, ob eine Referenz- oder eine Wertsemantik für Kellerelemente gewünscht ist.
Die kompliziertere Lösung mit Klonen
Um das oberste Stack-Element zu kopieren, bietet sich die clone()-Methode von Object an. All die Objekte, die sich klonen lassen, und das sind längst nicht alle, implementieren das Interface Cloneable. Nun ließe sich einfach folgern: Wenn das zu duplizierende Objekt ein Exemplar von Cloneable ist, dann können wir einfach die clone()-Methoden aufrufen und das zurückgegebene Objekt mittels push() auf den Stapel bringen.
void dup2() throws CloneNotSupportedException { try { Object top = peek(); if ( top instanceof Cloneable ) push( top.clone() ); } catch ( EmptyStackException e ) { } }
Beziehungsweise
void dup3() throws CloneNotSupportedException /*, EmptyStackException */ { push( peek().clone() ); }
Dies funktioniert für die meisten Objekte, allerdings nicht für Objekte der Klasse Object. Denn clone() der Klasse Object ist protected – wir dürfen also von außen nicht dran, nur eine Unterklasse und die Klasse selbst. Hier haben wir also zwei Probleme.
- Leider lässt sich nur mit Aufwand überprüfen, ob das Objekt auf dem Stapel auch wirklich ein pures Object ist, denn alle Objekte sind instanceof Object. Glücklicherweise gibt es kaum eine Anwendung, wo reine Object-Elemente gesichert werden müssen.
- Was machen wir mit Objekten, die nicht klonbar sind? Leider gibt es für diese Frage keine direkte Antwort. Eine universelle Stack-Klasse mit einer uneingeschränkten dup()-Methode gibt es nicht. Wir müssen als Stack-Benutzer festlegen, dass das oberste Element Cloneable ist, um zumindest eine eigene Implementierung nutzen zu können. Oder wir bleiben dabei, bei nicht klonbaren Objekten doch nur die Referenz zu duplizieren. Das wäre zumindest für eineindeutige Objekte mit Wertsemantik die ideale Lösung.
True Type Fonts in Swing/AWT-Anwendungen
Grafische Oberflächen stellen selbstverständlich wie Drucker Zeichensätze dar. Doch der Weg von der Datei bis zur Darstellung ist lang und führt unweigerlich über die Firma Adobe, die erstmalig die standardisierte Zeichendefinition PostScript öffentlich machte. Genauer gesagt, definiert PostScript noch etwas mehr, doch das soll uns hier nicht interessieren. Die erste kommerzielle Zeichensatzrevolution begann 1985, als der Drucker LaserWriter von Apple das Adobe-Format PostScript rastern konnte. Die Definition eines Zeichensatzes lag bis zu dieser Zeit nur in Bitmaps vor, doch die PostScript-Zeichensätze wie auch die TrueType-Zeichensätze, um die es später gehen soll, lagen als Punktbeschreibung vor. Die Rasterung übersetzte diese Punkte in eine Bitmap, die dann entweder auf dem Bildschirm oder auf dem Drucker ausgegeben wurde. Durch die Punktbeschreibung waren also nicht mehr größenabhängige Beschreibungen vorhanden, sondern die Zeichen (auch Glyphs genannt) wurden durch Linien und Kurven in kubischen Bézier-Kurven beschrieben.
Die Visualisierung der Zeichensätze machte Microsoft und Apple Sorgen, weil Adobe mehrere Definitionen der PostScript-Zeichensätze pflegte, darunter Type 1 (PS-1) und Type 3 (PS-3). Type 1 nutzt so genannte Hints (Hinweise), um auch bei unterschiedlichen Größen und grafischen Oberflächen optimale Darstellungen zuzulassen. Diese Definition war jedoch geheim. Zeichensätze des Type 3 sehen zwar auf dem Papier gut aus, nicht aber auf dem Bildschirm mit niedriger Auflösung – hier fehlen die Informationen aus den Hints. Microsoft und Apple wollten nun ihre Zeichensatzausgabe nicht der Firma Adobe überlassen (die natürlich einen Type-1-Rasterer im Programm hatte), sondern definierten ihre eigene Font-Technologie, die nicht mehr auf Bézier-Kurven, sondern auf quadratischen B-Splines basierte. Apple machte dabei den Anfang mit Royal, welches später in TrueType (TT) umgetauft wurde. Dies war sechs Jahre nach den PostScript Fonts. Der einzige Hersteller, der dennoch bei PostScript-Type 1-Zeichensätzen geblieben ist, ist IBM mit dem Betriebssystem OS/2. Daneben nutzte auch NeXtStep diese Zeichensatzdefinitionen, doch das System hallte nicht lange nach.
Nachdem Apple den Anfang mit TT gemacht hatte und es 1991 in MacOS integrierte, übernahm auch Microsoft, wo ein bis dahin wenig lauffähiger PostScript-Clone (»TrueImage«) zum Einsatz gekommen war, die Technologie für Windows 3.1. Adobe erkannte früh die Konsequenz dieser Allianz und öffnete die Spezifikation für PostScript-Type-1-Zeichensätze im März 1990. Mitte des Jahres lieferte Adobe zusätzlich den Adobe Type Manager (ATM) aus, der Type-1-(aber keine Type-3-)PostScript-Zeichensätze für den Bildschirm und für nicht PostScript-fähige Drucker darstellte. Heutzutage existieren beide Definitionen immer noch parallel, und für Drucker ist die Frage, welche nun besser ist, nicht zu beantworten. Moderne Drucker haben auch ein eigenes TrueType-Raster im ROM eingebaut. In Zukunft wird die Unterscheidung wohl auch unwichtiger werden, da Microsoft die »offene« OpenType-Spezifikation (auch »TrueType Open Version 2« genannt) nach vorne bringt. Der Zeichensatz PS-1 oder TrueType wird hier in einer OpenType-Datei gekapselt und dem Rasterer übergeben und berechnet. Dabei übernimmt Adobe, wo eine Zusammenarbeit mit Microsoft unterstützt wird, die PS-1-Rasterung, und Microsoft die TT-Rasterung. In Zukunft möchten Microsoft und Adobe Zeichensätze im OpenType unterstützen und deren Verbreitung fördern.
TTF in Java nutzen
Eine Einschränkung mit den gegeben vordefinierten Standard-Zeichensätzen (Dialog, DialogInput, Monospaced, Serif, SansSerif, Symbol) ist, dass dies zu wenig sind. Doch die Font Kasse bietet die statisch Methode createFont() an, die zu einen Eingabestrom auf ein TrueType Zeichensatz das entsprechende Font-Objekt zurückgibt.
Font f = Font.createFont( Font.TRUETYPE_FONT, new FileInputStream("f.ttf") );
Der erste Parameter ist die fest vorgeschriebene Konstante Font.TRUETYPE_FONT, andere Parameter sind nicht definiert und führen zu einer IllegalArgumentException("font format not recognized"). Der zweite Parameter ist ein Eingabestrom zu der Binärdatei mit den Zeichensatzinformationen. Die Daten werden ausgelesen und zu einem Font Objekt verarbeitet. Da die Daten intern über einen gepufferten Datenstrom in eine temporäre Datei geschrieben wird, ist eine eigene Pufferung über einen BufferedInputStream nur doppelter Overhead. Waren die Beschreibungsinformationen in der Datei ungültig, so erzeugt die Fontklasse eine FontFormatException("Unable to create font – bad font data"). Dateifehler fallen hier nicht drunter und werden extra über eine IOException angezeigt. Der Datenstrom wird anschließend nicht wieder geschlossen.
Wir wundern uns vielleicht an dieser Stelle, dass die Methode createFont() von der Arbeitsweise mit dem Konstruktor ähnlich sein müsste, aber der Parameterliste die Attribute fehlen. Das liegt daran, dass die Methode automatisch einen Zeichensatz der Größe 1 im Stil PLAIN erzeugt. Um daher einen größeren Zeichensatz zu erzeugen, müssen wie ein zweites Font Objekt anlegen. Dies geschieht am einfachsten mit der Methode deriveFont().
font = f.deriveFont( 20f );
Der Parameter ist ein float und kein double.
AWTEventMulticaster
Löst die Bean AWT-Ereignisse aus, kann sie dafür java.awt.AWTEventMulticaster nutzen. Diese Klasse ist für effizientes Multicast-Benachrichtigen bei AWT-Ereignissen gedacht. Genau genommen verbindet sie dazu nur zwei EventListener miteinander, sodass eine Verkettung entsteht.
protected ActionListener listeners; public void addActionListener( ActionListener l ) { listeners = AWTEventMulticaster.add( l, listeners ); } public void removeActionListener( ActionListener l ) { listeners = AWTEventMulticaster.remove( l, listeners ); }
Da AWTEventMulticaster alle möglichen AWT-Listener implementiert, können wir die Methode actionPerformed(), die die Schnittstelle ActionListener vorschreibt, aufrufen. Wenn wir ACTION_PERFORMED-Nachrichten damit generieren, schreiben wir Folgendes in unsere Bean-Klasse:
protected void fireActionEvent () { if ( listeners != null ) listeners.actionPerformed( new ActionEvent( this, ActionEvent.ACTION_PERFORMED, null) ); }
Die angemeldeten Listener bekommen auf diese Weise ein ActionEvent geliefert. Der letzte Parameter im Konstruktor, der hier mit null belegt ist, kann zusätzlich eine Referenz übermitteln.
class java.awt.event.AWTEventMulticaster
implements ComponentListener, ContainerListener, FocusListener, KeyListener, MouseListener, MouseMotionListener, WindowListener, ActionListener, ItemListener, AdjustmentListener, TextListener, InputMethodListener, HierarchyListener, HierarchyBoundsListener
- static ActionListener add( ActionListener a, ActionListener b )
Verbindet Listener a und b und liefert ein neues ActionListener-Objekt zurück. - static ActionListener remove( ActionListener l, ActionListener oldl )
Entfernt Listener oldl von l und liefert den neuen Multicast-Listener zurück.
class java.awt.event.ActionEvent
extends AWTEvent
- ActionEvent( Object source, int id, String command )
Erzeugt ein ActionEvent mit einer Quelle, die das Ereignis ausgelöst hat, einem Identifizierer und einem Kommando.
HttpServletRequest und HttpServletResponse und die Header
Sendet der HTTP-Client eine Anfrage an den Server, so sendet er gleichzeitig einige Informationen über sich mit. Sie nennen sich Header und bezeichnen Schlüssel-Werte-Paare, die durch einen Doppelpunkt getrennt sind. Ein Webbrowser kann zum Beispiel Folgendes formulieren:
GET /seminare/index.html HTTP/1.0 Accept-Language: de
Der Browser sendet hier den Header Accept-Language mit dem Wert de. So kann der Server unter Auswertung dieser Parameter optimal reagieren, zum Beispiel bei der Präferenz der Sprache eine lokalisierte Webseite liefern. Um an die Header zu gelangen, müssen wir das HttpServletRequest-Objekt lesen und die Header erfragen.
Header auslesen
Zum Lesen der Header in einem Servlet bieten sich zwei Lösungen an: Wenn wir einen speziellen Header erfragen wollen, dann holen wir mit getHeader() auf dem HttpServletRequest den passenden Wert zum Schlüssel. Sind wir an allen Schlüsseln interessiert, dann besorgt uns getHeaderNames() eine Enumeration. Die können wir dann durchlaufen und die Werte wiederum mit getHeader() auslesen. Falls ein Schlüssel nicht existiert, liefert die Methode null. Ähnlich wie bei getParameter() können hier auch mehrere Einträge existieren, die mit getHeaders() abgerufen werden können.
<% java.util.Enumeration headerNames = request.getHeaderNames(); while ( headerNames.hasMoreElements() ) { String headerNameKey = (String) headerNames.nextElement(); String headerNameValue = request.getHeader( headerNameKey ); %> <%= headerNameKey %>: <%= headerNameValue %> <p> <% } %>
Das Servlet erzeugt für eine Anfrage etwa folgende Ausgabe:
accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */* accept-language: de accept-encoding: gzip, deflate user-agent: Mozilla/4.0 (compatible; MSIE 9.0; Windows NT 5.0) host: localhost:8080 connection: Keep-Alive cookie: JSESSIONID=EB9D8DFAB0D0AA1B38D292507983B6B1
Der Anfragetyp (GET, POST und so weiter) wird hier ebenso wenig angezeigt wie der Remote-Host. Dieser findet sich nicht im Header und muss mit anderen Funktionen erfragt werden.
Hilfsfunktion im Umgang mit Headern
Wieder gibt es für oft benutzte Header Abkürzungen.
- getMethod() liefert eine Zeichenkette wie GET oder POST.
- Die Methode getRequestURI() liefert die URI der Anfrageseite.
- getProtocol() liefert das Protokoll von der Statuszeile, also heutzutage entweder HTTP/1.0 oder HTTP/1.1.
- getCookies() liefert den Inhalt des Cookie-Headers (dazu später mehr).
- getAuthType() und getRemoteUser() zerteilen die Information im Authorization-Feld in Komponenten.
- getDateHeader() und getIntHeader() sind wieder Hilfsmethoden.
Übersicht der Browser-Header
Hier eine Übersicht über die üblichen Header, von denen wir manche schon aus dem Beispiel und auch vom Server kennen:
- Accept. Der vom Browser bevorzugte MIME-Typ.
- Accept-Charset. Der vom Browser bevorzugte Zeichensatz.
- Accept-Encoding. Die Kodierung, die der Browser verarbeiten kann, wie etwa gzip oder compress. Unser Servlet-Programm sollte vor dem Komprimieren testen, ob der Browser überhaupt komprimierte Dateien verarbeiten kann.
- Accept-Language. Die Sprache, die der Browser bevorzugt anzeigt. Mehr als ein Eintrag, wenn der Browser mehr als eine Sprache spricht.
- Authorization. Information über Autorisierung, die normalerweise eine Antwort auf die WWW-Authenticate-Anfrage des Servers ist.
- Connection. Informiert, ob persistente Verbindungen genutzt werden. Persistente Übertragungen übermitteln in einer TCP/IP-Verbindung mehrere Dateien, etwa eine HTML-Datei und mehrere Grafiken. Wenn der Wert von Connection »Keep-Alive« heißt, dann lassen sich mit einer Netzwerkverbindung mehrere Seitenteile übermitteln. Wenn die Request-Zeile die http-Version 1.1 anzeigt, sind Keep-Alive-Verbindungen Standard. Unsere Aufgabe bei diesen Verbindungen ist es, den Header ContentLength in die Antwort zu setzen. Server-abhängig wird hier teilweise schon automatisch in einen Puffer geschrieben und die Größe gesetzt. Dies muss aber nicht so sein, daher bietet es sich an, die Informationen in einen ByteArrayOutputStream zu schreiben, um später die Länge und den Inhalt abzufragen.
- Content-Length. Die Länge des Bytestroms. Hier zählt der Browser die Bytes und informiert den Server, wie viele Daten noch kommen.
- Cookie. Cookie-Information, die der Browser automatisch mitschickt.
- From. Ein optionaler Header, der oft von Webrobotern gesetzt wird. Bei Browsern nicht üblich.
- Host. Rechnername und Host, wie in der Original-URL angegeben.
- If-Modified-Since. Liefert ein neues Server-Dokument, wenn die im Header angegebene Zeit auf ein neueres Dokument verweist. Ist das Browser-Dokument aktueller, gibt der Server den Antwortcode 304 mit der Nachricht »Not Modified« zurück.
- Pragma. Gibt Informationen über das automatische Neuladen der Seiten. Der Wert no-cache zeigt an, dass der Server immer eine neue Seite liefern soll, auch wenn der Proxy eine Kopie hält.
- Referer. Die URL mit dem Verweis, der auf die aktuelle Seite gezeigt hat.
- User-Agent. Der Browsertyp. Praktisch, wenn unser Servlet JavaScript-Code einbettet, der vom Browser abhängig ist.
- UA-Pixels, UA-Color, UA-OS, UA-CPU. Von Microsoft eingeführte proprietäre Header für den Internet Explorer, die Bildschirmgröße, Farbtiefe, Betriebssystem und CPU-Typ anzeigen.
Header, die der Server setzt
Bisher kennen wir von der Klasse HttpServletResponse die Methode setHeader() für beliebige Header.
Beispiel: Setze den Header pragma, damit vom Browser keine Daten im Cache gehalten werden:
response.setHeader( "pragma", "no-cache" );
Mit dieser Aufforderung soll der Browser die Seite jedes Mal neu laden. Das ist bei dynamischen Seiten besonders wichtig, da sie bei jedem Aufruf neu generiert werden und sich Werte ändern können, wie es zum Beispiel bei Warenkorbsystemen der Fall ist. Da wir uns als Applikationsentwickler nicht immer mit dem Namen der Header herumärgern wollen, bietet die Bibliothek einige Spezialfunktionen an.
Beispiel: Für den Header Content-Type gibt es die spezielle Methode setContentType():
response.setHeader( "Content-Type", "text/html"); response.setContentType( "text/html" );
Daneben gibt es setContentLength(), die den Header Content-Length setzt. Diese Länge muss nicht gesetzt werden und wird automatisch berechnet. Falsche Längen könnten zu Ausnahmesituationen führen. Der Gebrauch ist jedoch nützlich, wenn vorher die gesamte Webseite in einem StringBuffer sb gesammelt und in einem Rutsch übertragen wird. Dann können wir setContentLength(sb.length()) aufrufen.
Um einen Datums-Header zu setzen, existiert setDateHeader(String, long). Das Argument ist eine beliebige Zeichenkette, die mit einem Datumswert verbunden wird. Das long gibt die Millisekunden seit dem 1.1.1970 an. Die erzeugte Ausgabe schreibt einen UTC-String. Eine weitere Hilfsfunktion ist setIntHeader(), die Zahlenwerte mit Schlüsseln in den Header schreibt. Hier übernimmt die Methode die Konvertierung von String in eine Ganzzahl.
Neben diesen setXXX()-Methoden, die möglicherweise gesetzte Header überschreiben, lässt sich mit containsHeader(String) abfragen, ob Wertepaare schon gesetzt sind. Neben den setXXX()-Methoden gibt es auch entsprechende addXXX()-Methoden, die die Werte nicht überschreiben, sondern hinzufügen. Für Cookies existiert eine zusätzliche Methode namens addCookie(), die einen Cookie im Header setzt.
Mit Locking Dateien sperren
Damit eine Datei gegen konkurrierenden parallelen Zugriff geschützt ist, lässt sie sich über Locking absichern. Um einen Lock für Dateien zu erwerben bietet die Java-API ein FileLock-Objekt. So ein FileLock bekommt ein Programm von der Methode lock() eines FileChannels – ein FileChannel wiederum kommt von getChannel(), einer Methode, die FileInputStream, FileOutputStream oder RandomAccessFile anbieten.
Beispiel: Öffne eine Datei, erzeuge exklusiven Zugriff, und schreibe Daten:
FileOutputStream fos = new FileOutputStream( file );
try ( FileLock fileLock = fos.getChannel().tryLock() ) {
fos.write( … );
}
Hinweis: Die übliche Schreibweise OutputStream fos funktioniert natürlich nicht, da ein allgemeiner OutputStream keine getChannel()-Methode bietet.
Die lock(…)-Methode liefert als Ergebnis ein FileLock-Objekt. Das wiederum bietet einige Methoden, wobei für uns nur release() bzw. close() interessant sind, die den Lock wieder freigeben. FileLock implementiert die Schnittstelle AutoCloseable, sodass ein FileLock auch auch try-mit-Ressourcen verwendet werden kann, wie im Beispiel geschehen.
Um zu testen, ob eine gegebene Datei gelockt ist, lässt sich tryLock() verwenden – etwa mit der folgenden statischen Hilfsmethode:
public static boolean isLocked( String filename ) {
try ( RandomAccessFile raf = new RandomAccessFile( filename, "rw" ); FileLock lock = raf.getChannel().tryLock() ) {
// Nothing to do here
}
catch ( IOException e ) {
return false;
}
return true;
}
Die Methoden tryLock(…) und lock(…) liefern FileLock-Objekt und diese Ressource muss immer korrekt geschlossen werden.
Hinweis: Unter Unix-Systemen gibt es kein eindeutig vorgeschriebenes Verfahren zum File-Locking[1], sodass Oracle das Sperren bisher nur so umsetzt, dass zwei Java-Programme sich gegenseitig nicht in die Quere kommen, es aber sein kann, dass ein anderes Unix-Programm diesen Lock nicht respektiert. So kann unter Unix eine Datei von mehreren Seiten gelesen werden, selbst wenn ein Java-Programm sie aktuell beschreibt. Auch kann eine Datei auf dem Dateisystem gelöscht werden, selbst wenn das Java-Programm sie noch offen hält. Das Windows-Betriebssystem unterstützt hingegen Locks. Wenn ein Prozess keinen Lock auf die Datei besitzt, kann der Prozess die Datei auch nicht lesen.
[1] Zum Beispiel mit dem Alleskönner fcntl() aus dem POSIX-Standard oder flock() von 4.2 BSD.