Inselraus: Zeichensatz eines Swing-Labels ändern

Der gesetzte Text wird im zugewiesenen Zeichensatz des Swing-Look-and-Feels angezeigt. Um diesen zu ändern, müssen wir ein neues Font-Objekt erzeugen. Auf zwei Arten lässt sich dieser Font setzen: global für alle JLabel-Elemente oder lokal nur für dieses eine. Die erste Lösung arbeitet über das UIDefaults-Objekt, das die Einstellungen wie Zeichensätze und Farben für alle Swing-Elemente verwaltet:

UIDefaults uiDefaults = UIManager.getDefaults();
uiDefaults.put( "Label.font",
                ((Font)uiDefaults.get("Label.font")).deriveFont(30f) );

Unter dem Schlüssel »Label.font« legen wir ein neues Font-Objekt ab und überschreiben die alte Definition. Den neuen Font mit der Größe 30 leiten wir mit deriveFont(float) vom alten ab, sodass wir den Zeichensatz »erben«.

Die zweite Lösung kann darin bestehen, den Font direkt mit der setFont(Font)-Methode zu setzen:

JLabel l = new JLabel( "Lebe immer First-Class, sonst tun es deine Erben!" );
l.setFont( new Font("Serif", Font.BOLD, 30) );

Einen speziellen Konstruktor, der ein Font-Objekt als Argument annimmt und dieses verwendet, gibt es nicht.

Inselraus: Dynamisches Layout während einer Größenänderung

Wird ein Fenster vergrößert, dann kann während der Größenänderung der Inhalt sofort neu ausgerichtet und gezeichnet werden oder auch nicht. Wird er nicht dynamisch angepasst, dann sieht der Benutzer diese Anpassung erst nach dem Loslassen der Maus, wenn die Größenänderung abgeschlossen wurde. Dieses dynamische Vergrößern lässt sich im Toolkit-Objekt einstellen, und zwar über Toolkit.getDefaultToolkit().setDynamicLayout(true). Nicht jedes Toolkit unterstützt allerdings diese Fähigkeit! Ob es das tut, verrät Toolkit.getDefaultToolkit().getDesktopProperty("awt.dynamicLayoutSupported").

Inselraus: Selektionen einer Tabelle

In einer JTable können auf unterschiedliche Art und Weise Zellen selektiert werden: zum einen nur in einer Zeile oder Spalte, zum anderen kann auch ein ganzer Block oder können auch beliebige Zellen selektiert werden. Die Art der Selektion bestimmen Konstanten in ListSelectionModel. So wird SINGLE_SELECTION nur die Selektion einer einzigen Zelle zulassen.

Beispiel: In einer JTable soll entweder ein ununterbrochener Block Zeilen oder Spalten ausgewählt werden dürfen:

table.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );

Mit Methoden lassen sich im Programm alle Elemente einer Spalte oder Zeile selektieren. Die Selektier-Erlaubnis geben zunächst zwei Methoden:

  • table.setColumnSelectionAllowed( boolean )
  • table.setRowSelectionAllowed( boolean )

Die automatische Selektion von Spalten gelingt mit der JTable-Methode setColumnSelectionInterval(int, int), weitere Bereiche lassen sich mit addColumnSelectionInterval(int, int) hinzufügen und mit removeColumnSelectionInterval(int, int) entfernen. Das Gleiche gilt für die Methoden, die »Row« im Methodennamen tragen.

Beispiel: Selektiere in einer JTable table die Spalte 0 komplett:

table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( false );
table.setColumnSelectionInterval( 0, 0 );

Selektiere in einer Tabelle nur die Zelle 38, 5:

table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION  );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( true );
table.changeSelection( 38, 5, false, false );

Als Selektionsmodus reicht SINGLE_SELECTION aus, MULTIPLE_INTERVAL_SELECTION wäre aber auch in Ordnung. Beide Selektionen sind zusammen in der Form nicht möglich. Bei einer Einzelselektion wird die Zelle nur umrandet, aber nicht wie beim Standard-Metal-Look-and-Feel blau ausgefüllt.

Die Methode selectAll() selektiert alle Elemente, und clearSelection() löscht alle Selektionen.

Inselraus: Tabellenkopf von Swing-Tabellen

Diese Verschiebung von Spalten kann über das Programm erfolgen (mit moveColumn(int column, int targetColumn) von JTable) oder über den Benutzer per Drag & Drop. JTable delegiert diese Methoden an ein spezielles TableColumnModel und implementiert den Code nicht selbst.

Der Kopf (engl. header) einer JTable ist ein JTableHeader-Objekt, das von der JTable mit getTableHeader() erfragt werden kann. Dieses JTableHeader-Objekt ist für die Anordnung und Verschiebung der Spalten verantwortlich.

Beispiel: In der JTable table sollen die Spalten nicht mehr vom Benutzer verschoben werden können. Er soll auch die Breite nicht mehr ändern dürfen:

table.getTableHeader().setReorderingAllowed( false );
table.getTableHeader().setResizingAllowed( false );

Hier wird deutlich, dass ein JTableHeader die Steuerung der Ausgabe und der Benutzerinteraktion übernimmt, dass aber die Informationen selbst in TableColumn liegen.

Inselraus: Spalteninformationen von JTable-Tabellen

Alle Zelleninformationen der Tabelle stecken im Model einer JTable. Informationen über die Spalten stehen allerdings nicht im TableModel, sondern in Objekten vom Typ TableColumn. Jede Spalte bekommt ein eigenes TableColumn-Objekt, und eine Sammlung der Objekte bildet das TableColumnModel, das wie das TableModel ein Datencontainer der JTable ist.

Beispiel: Zähle alle TableColumn-Objekte einer JTable table auf.

for ( Enumeration<?> e = table.getColumnModel().getColumns(); e.hasMoreElements(); )
  System.out.println( (TableColumn)e.nextElement() );

Vom Spaltenmodell der Tabelle bezieht getColumns()eine Enumeration von TableColumn-Objekten. Soll ein ganz bestimmtes TableColumn-Objekt untersucht werden, kann auch die Methode getColumn(Object identifier) genutzt werden.

Liegt ein TableColumn-Objekt vor, lässt sich von diesem die aktuelle minimale und maximale Breite setzen.

Beispiel: Ändere die Breite der ersten Spalte auf 100 Pixel:

table.getColumnModel().getColumn( 0 ).setPreferredWidth( 100 );

AUTO_RESIZE

Verändert der Anwender die Breite einer Spalte, ändert er entweder die Gesamtbreite einer Tabelle, oder er ändert automatisch die Breite der anderen Spalten, um die Gesamtbreite nicht zu verändern. Hier gibt es für die JTable unterschiedliche Möglichkeiten, die eine Methode setAutoResizeMode(int mode) bestimmt. Erlaubte Modi sind Konstanten aus JTable und AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_ LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS. Sinnvoll sind drei von ihnen:

  • AUTO_RESIZE_SUBSEQUENT_COLUMNS: Der Standard. Verändert gleichmäßig die Breiten aller rechts liegenden Spalten.
  • AUTO_RESIZE_NEXT_COLUMN: Ändert nur die Breite der nachfolgenden Spalte.
  • AUTO_RESIZE_OFF: Ändert die Größe der gesamten Tabelle. Ist nur sinnvoll, wenn die JTable in einer JScrollPane liegt.

Inselraus: Größe und Umrandung von JTable-Zellen

Jede Zelle hat eine bestimmte Größe, die durch den Zellinhalt vorgegeben ist. Zusätzlich liegt zwischen zwei Zellen immer etwas Freiraum. Dieser lässt sich mit getIntercellSpacing() erfragen und mit setIntercellSpacing(…) setzen:

 table.setIntercellSpacing( new Dimension(gapWidth, gapHeight) ); 

Soll die Zelle rechts und links zum Beispiel 2 Pixel Freiraum bekommen, ist gapWidth auf 4 zu setzen, denn das Dimension-Objekt gibt immer den gesamten vertikalen und horizontalen Abstand zwischen den Zellen an.

Die Gesamtgröße einer Zelle ist dann die der Margin-Zeile + Zellhöhe beziehungsweise Margin-Spalte + Zellbreite. Da jedoch setIntercellSpacing(…) die Höhe einer Zeile nicht automatisch anpasst, muss sie ausdrücklich gesetzt werden:

 table.setRowHeight( table.getRowHeight() + gapHeight ); 

Zusätzlich zur Margin erhöht eine Linie den Abstand zwischen den Zellen. Auch dieses Raster (engl. grid) lässt sich modifizieren:

Code

Funktion

table.setShowGrid( false );

Schaltet die Umrandung aus.

table.setShowGrid( false );
table.setShowVerticalLines( true );

Zeigt nur vertikale Linien.

table.setGridColor( Color.GRAY );

Die Umrandung wird grau.

Rastermodifizierung

NetBeans 7.3 final und aus der Beta-Phase

Details und Download hier: http://netbeans.org/community/releases/73/index.html ,http://wiki.netbeans.org/NewAndNoteworthyNB73. Im Java-Bereich hat sich gar nicht so viel getan, eher im Umfeld der Web-Entwicklung mit HTML5. Das zeigt irgendwie für mich klar, wohin die Reise in der Entwicklung geht. Ich hätte es schön gefunden, wenn die neuen Sprachfeatures von Java 8 schon integriert würde, aber hier muss man auf die nächste Version warten. Bei Eclipse passiert gefühlt nicht so viel, IntelliJ und NB legen gutes Tempo vor.

Spaß mit Generics und funktionalen Schnittstellen

Funktionale Schnittstellen müssen auf genau eine zu implementierende Methode hinauslaufen, auch wenn aus Oberschnittstellen mehrere Operationen vorgeschrieben werden, die aber durch Einsatz von Generics auf eine Operation verdichten:

interface I<S, T extends CharSequence> {

  void len( S text );

  void len( T text );

}

interface FI extends I<String, String> { }

FI ist unsere funktionale Schnittstelle, da die Signatur der Methode eindeutig ist: len(String).

Rückgabetypen und Typ-Inferenz bei Lambda-Deklarationen

Typinferenz spielt bei Lambda-Ausdrücken eine große Rolle – das gilt insbesondere für die Rückgabetypen, die überhaupt nicht in der Deklaration auftauchen. Bei unserem Beispiel

Comparator<String> c = (String s1, String s2) -> { return s1.trim().compareTo( s2.trim() ); };

ist String als Parametertyp der Comparator-Methode ausdrücklich gegeben, aber int taucht nicht auf.

Mitunter muss dem Compiler etwas geholfen werden: Nehmen wir die funktionale Schnittstelle Supplier<T>, die eine Methode T get() deklariert, für ein Beispiel. Die Zuweisung

Supplier<Long> two = () -> 2; // Compilerfehler

ist nicht korrekt und führt zum Compilerfehler „incompatible types: bad return type in lambda expression“. 2 ist ein Literal vom Typ int und der Compiler kann es nicht an Long anpassen. Wir müssen schreiben:

Supplier<Long> two = () -> 2L;

oder

Supplier<Long> two = () -> (long) 2;

Bei Lambda-Ausdrücken gelten keine wirklich neuen Regeln im Vergleich zu Methodenrückgaben, denn auch

public static Long two() { return 2; } // Compilerfehler

wird vom Compiler angemeckert. Doch weil Generics bei funktionalen Schnittstellen viel häufiger sind, treten diese Besonderheiten öfters zu Tage auf als bei Methodendeklarationen.

Rekursive Lambda-Ausdrücke

Lambda-Ausdrücke können auf sich selbst verweisen, doch da ein this zur Selbstreferenz nicht möglich ist, ist ein kleiner Umweg nötig. Erstes muss eine Objekt- oder Klassenvariable deklariert werden, zweitens dann dieser Variablen ein Lambda-Ausdruck zugewiesen werden und dann kann drittens dieser Lambda-Ausdruck auf diese Variable zugreifen und einen rekursiven Aufruf starten. Für den Klassiker der Fakultät sieht das so aus:

public class RecursiveFactLambda {

public static IntFunction<Integer> fact = n -> (n == 0) ? 1 : n * fact.apply(n-1);

public static void main( String[] args ) {

   System.out.println( fact.apply( 5 ) ); // 120

}

}

IntFunction ist eine funktionale Schnittstelle mit den der Operation T apply(int i), und T ist ein generischer Rückgabetyp, den wir hier mit Integer belegt haben.

fact hätte genauso gut als normale Methode deklariert werden können – großartige Vorteile bietet die Schreibweise mit Lambda-Ausdrücken nicht. Zumal jetzt auch der Begriff „anonyme Funktion“ nicht mehr so richtig schlüssig ist, da der Lambda-Ausdruck ja doch einen Namen hat, nämlich fact.

Annotation @FunctionalInterface

Zwar eignet sich jede Schnittstelle mit einer abstrakten Methode als funktionale Schnittstelle und damit für einen Lambda-Ausdruck, doch nicht jede Schnittstelle, die im Moment nur eine abstrakte Methode deklariert, soll auch für Lambda-Ausdrücke verwendet werden. Der Compiler kann das jedoch nicht wissen, ob sich vielleicht Schnittstellen weiterentwickeln, und daher gibt es zur Dokumentation die Annotation FunctionalInterface im java.lang-Paket.

Beispiel: Eine eigene funktionale Schnittstelle soll FunctionalInterface markieren:

@FunctionalInterface

interface MyFunctionalInterface {

void foo();

}

Der Compiler prüft, ob die Schnittstelle mit einer solchen Annotation tatsächlich nur exakt eine abstrakte Methode enthält und löst einen Fehler aus, wenn dem nicht so ist. Aus Kompatibilitätsgründen erzwingt der Compiler nicht diese Annotation bei funktionalen Schnittstellen, um inneren Klassen von alten Schnittstellen einfach in Lambda-Ausdrücke umzuschreiben zu können. Die Annotation ist also keine Voraussetzung für die Nutzung in einem Lambda-Ausdruck und dient bisher nur der Dokumentation.

Die Umgebung der Lambda-Ausdrücke

Ein Lambda-Ausdruck „sieht“ seine Umgebung genauso wie der Code, der vor oder nach dem Lambda-Ausdruck steht. Insbesondere hat ein Lambda-Ausdruck vollständigen Zugriff auf alle Eigenschaften der Klasse, genauso wie auch der einschließende äußere Block sie hat. Es gibt keinen besonderen Namensraum (nur neue und vielleicht überdeckte Variablen durch die Parameter), und das ist einer der grundlegenden Unterschiede zwischen Lambda-Ausdrücken und inneren Klassen, bei denen this und super eine etwas andere Bedeutung haben.

Namensräume

Deklariert eine innere anonyme Klasse in der Methode Variablen, so ist der Satz immer „neu“, beziehungsweise die neuen Variablen überschatten vorhandene lokale Variablen aus dem äußeren Kontext. Die Variable compareIgnoreCase kann im Rumpf von compare(…) zum Beispiel problemlos neu deklariert werden:

boolean compareIgnoreCase = true;

Comparator<String> c = new Comparator<String>() {

@Override public int compare( String s1, String s2 ) {

boolean compareIgnoreCase = false; // völlig ok

return …

}

};

In einem Lambda-Ausdruck ist das nicht möglich und folgendes führt zu einer Compilermeldung „variable compareIgnoreCase ist already defined“.

boolean compareIgnoreCase = true;

Comparator<String> c = (s1, s2) -> {

boolean compareIgnoreCase = false; // Compilerfehler

return …

}

this-Referenz

Lambda-Ausdrücke unterscheiden sich von inneren (anonymen) Klassen auch in dem, worauf die this-Referenz verweist: Bei Lambda-Ausdrücke zeigt this auf das Objekt, in dem der Lambda-Ausdruck eingebettet ist, bei inneren Klassen referenziert this die inneren Klasse.

class Application {

Application() {

Runnable run1 = () -> { System.out.println( this.getClass().getName() ); };

Runnable run2 = new Runnable() {

@Override public void run() { System.out.println( this.getClass().getName()); } };

run1.run(); // app.Application

run2.run(); // app.Application$1

}

public static void main( String[] args ) {

new Application();

}

}

Das Programm nutzt this einmal im Lambda-Ausdruck und einmal in der inneren Klasse. Im Fall vom Lambda-Ausdruck bezieht sich ausdrücklich auf das Application-Exemplar, was sich im Klassenamen niederschlägt. Bei der inneren Klasse bekommen wir den Anhang $1, weil es sich um ein anderes Exemplar handelt.

Base64-Kodierung (unter Java 8)

Für die Übertragung von Binärdaten hat sich im Internet die Base64-Kodierung durchgesetzt, die zum Beispiel bei E-Mail-Anhängen und SOAP-Nachrichten zu finden ist. Auch bei der HTTP-Authentifizierung Basic Authentication kommt Base64 zum Tragen, denn die Konkatenation von Benutzername + „:“ + Passwort wird über Base64 codiert und so zum Server gesendet – der Sicherheitsgewinn ist natürlich null.

Die Base64-Kodierung wird im RFC 4648[1] beschriebene. Drei Bytes (24 Bit) werden in vier Base64-kodierte Zeichen (vier Zeichen mit jeweils sechs repräsentativen Bits) umgesetzt. Die Konsequenz dieser Umformung ist, dass Binärdaten rund 33  % größer werden. Die Base64-Zeichen bestehen aus den Buchstaben des lateinischen Alphabets, den Ziffern 0 bis 9 sowie (im Normalfall) »+«, »/« und »=«.

Das JDK liefert seit Java 8 direkte Unterstützung für diese Base64-Umsetzung mit der Klasse java.util.BASE64 aus. Zwei inneren Klassen Base64.Decoder bzw. Base64.Encoder kümmern sich um die Umwandlung. Zur Erzeugung der Exemplare gibt es statische Methoden in BASE64, und zwar nicht nur zwei, sieben. Der Grund ist, dass es neben der Standard-Konvertierung „Base“ noch MIME und URL/Dateiname-sicher gibt:

· getEncoder() und getDecoder() liefern Exemplare vom Typ Base64.Encoder und Base64.Decoder bzw. für den normalen Basic-Typ.

· getEncoder(int lineLength, byte[] lineSeparator), getMimeEncoder() und getMimeDecoder() liefern Encoder/Decoder für MIME-Nachrichten, bei der Zeilen mit einem „\r“ getrennt sind.

· getUrlEncoder() und getUrlDecoder() nutzt zur Kodierung nur Zeichen, die für URL und Dateinamen gültig sind, und ersetzt „+“ durch „-“ und „/“ durch „_“.

Beispiel

Das folgende Beispiel erzeugt zuerst ein Byte-Feld mit Zufallszahlen. Die Base64-Klasse kodiert das Byte-Feld in einen String, der auf dem Bildschirm ausgegeben wird. Nachdem der String wieder zurückkodiert wurde, werden die Byte-Felder verglichen und liefern natürlich true:

byte[] bytes1 = SecureRandom.getSeed( 20 );

// byte[] -> String

String s = Base64.getEncoder().encodeToString( bytes1 );

System.out.println( s ); // z.B. TVST9v+JMk/vVUOSENmIcriXFLo=

// String -> byte[]

byte[] bytes2 = Base64.getDecoder().decode( s );

System.out.println( Arrays.equals(bytes1, bytes2) ); // true

Wer nicht mit Java 8 arbeiten kann, aber mit älteren Versionen vom Oracle JDK, der kann BASE64Encoder/BASE64Decoder aus dem nicht-öffentlichen Paket sun.misc nutzen.[2] Wem das nicht ganz geheuer ist, der kann javax.mail.internet.MimeUtility von der JavaMail-API nutzen[3] oder unter http://jakarta.apache.org/commons/codec/ die Commons Codec-Bibliothek beziehen.


[1] http://tools.ietf.org/html/rfc4648

[2] Siehe dazu http://java.sun.com/products/jdk/faq/faq-sun-packages.html. Bisher existieren sie aber seit über zehn Jahren, und wer Oracles Philosophie kennt, der weiß, dass die Abwärtskompatibilität oberste Priorität hat.

[3] http://www.rgagnon.com/javadetails/java-0598.html gibt ein Beispiel. Die JavaMail-API ist Teil von Java EE 5 und muss sonst für das Java SE als Bibliothek hinzugenommen werden.

Statische ausprogrammierte Methoden in Schnittstellen

In der Regel deklariert eine Schnittstelle Operationen, also abstrakte Objektmethoden, die eine Klasse später implementieren muss. Die in Klassen implementiere Schnittstellenmethode kann später wieder überschrieben werden, nimmt also ganz normal an der dynamischen Bindung teil. Einen Objektzustand kann die Schnittstelle nicht deklarieren, denn Objektvariablen sind in Schnittstellen tabu – jede deklarierte Variable ist automatisch statisch, also eine Klassenvariable.

Ab Java 8 lassen sich in Schnittstellen statische Methoden unterbringen und als Utility-Methoden neben Konstanten stellen. Als statische Methoden werden sie nicht dynamisch gebunden und der Aufruf ist ausschließlich über den Namen der Schnittstelle möglich. (Bei statischen Methoden von Klassen ist im Prinzip auch der Zugriff über eine Referenz erlaubt, wenn auch unerwünscht.)

Beispiel:

interface Buyable {
int MAX_PRICE = 10_000_000;

static boolean isValidPrice( double price ) { return price >= 0 && price < MAX_PRICE; }
double price();
}

Von außen ist dann der Aufruf Buyable.isValidPrice(123) möglich.

Alle deklarieren Eigenschaften sind immer implizit public, sodass dieser Sichtbarkeitsmodifizierer redundant ist.

Fassen wir die erlaubten Eigenschaften einer Schnittstelle zusammen:

 

Attribut

Methode

Objekt-

Nein, nicht erlaubt

Ja, üblicherweise abstrakt

Statische(s)

Ja, als Konstante

Ja, immer mit Implementierung

Erlaubte Eigenschaften einer Schnittstelle

 

Design: Eine Schnittstelle mit nur statischen Methoden ist ein Zeichen für ein Designproblem und sollte durch eine finale Klasse mit privaten Konstruktor ersetzt werden. Schnittstellen sind immer als Vorgaben zum Implementieren gedacht und wenn nur statische Methoden vorgekommen, erfüllt die Schnittstelle nicht ihren Zweck, dass sie Vorgaben macht, die unterschiedlich umgesetzt werden können.

Kalender-Exemplare bauen über den Calendar.Builder

Java 8 führte in Calendar die neue statische innere Klasse Builder ein, mit der sich leicht Calendar-Exemplare mit gesetzten Feldern aufbauen lassen. Die allgemeine Schreibweise ist wie folgt:

Calendar cal = new Calendar.Builder().setXXX( … ).setXXX( … ).setXXX( … ).build();

Zum Setzen von Feldern gibt es setXXX(…)-Methoden, am Ende folgt ein Aufruf von build(), der ein fertiges Calendar-Objekt liefert.

static class java.util.Calendar.Builder

§ Calendar.Builder setDate(int year, int month, int dayOfMonth)

§ Calendar.Builder set(int field, int value)

§ Calendar.Builder setFields(int… fieldValuePairs)

§ Calendar.Builder setInstant(Date instant)

§ Calendar.Builder setInstant(long instant)

§ Calendar.Builder setTimeOfDay(int hourOfDay, int minute, int second)

§ Calendar.Builder setTimeOfDay(int hourOfDay, int minute, int second, int millis)

§ Calendar.Builder setWeekDate(int weekYear, int weekOfYear, int dayOfWeek)

§ Calendar.Builder setTimeZone(TimeZone zone)

Etwas weniger gebräuchliche Mehtoden sind weiterhin setCalendarType(String type) – was Rückgaben von Calendar.getAvailableCalendarTypes() erlaubt und alternativ zu „gregory“ auch “gregorian“ bzw. “ iso8601“ –, setLenient(boolean lenient), setLocale(Locale locale) und setWeekDefinition(int firstDayOfWeek, int minimalDaysInFirstWeek).

Inselraus: Kalender-Typen getCalendarType()/getAvailableCalendarTypes()

Welcher Kalendertyp ein konkreter Kalender repräsentiert, ermittelt die Calendar-Objektmethode getCalendarType(); die Rückgabe ist ein String und lautet bei dem typischen Gregorianischen Kalender „gregory“, könnte aber auch „buddhist“ oder „japanese“ heißen. Welche Kalendertypen Java unterstützt, liefert die statische Methode Calendar.getAvailableCalendarTypes() als Set<String>. Im Moment sind es genau die drei genannten.

Würde geloggt werden?

Das Logging-Framwork versucht so schnell wie möglich zu entscheiden, ob eine Nachricht bei einem eingestellten Log-Levels geloggt werden soll oder nicht. Ist die Stufe in der Produktion zum Beispiel auf WARNING, sind INFO-Meldungen zu ignorieren. Problematisch aus Performance-Sicht sind zum Beispiel aufwändig aufgebaute Log-Nachrichten, die dann sowieso nicht geloggt werden. Der Plus-Operator bei Strings gehört nicht zu den beachtlichen Zeitfressern, doch ein

log.info( "Open file: " + filename );

führt zur Laufzeit immer zu einer String-Konkatenation, egal, ob die erzeugte Nachricht später geloggt wird oder nicht.

JUL bietet zur Umgehung des Problems zwei Lösungen. Als erstes bietet die Logger-Klasse eine Testmethode boolean isLoggable(Level level), über die ein schneller Test durchgeführt werden kann:

if ( log.isLoggable(Level.INFO) )

  log.info( "Open file: " + filename );

Natürlich kann info(…) nicht wissen, dass es auf jeden Fall loggen soll, daher findet der Test noch einmal statt. Eine allgemeine Überprüfung für alle Logging-Ausgaben bietet sich daher nicht an, sondern nur dann, wenn eine aufwändige Operation im Logging-Fall ausgeführt werden soll.

Die zweite Möglichkeit ist neu in Java 8. Sie nutzt Objekte vom Typ Supplier, die eine Implementierung enthalten, also etwa die Konkatenation. Im Prinzip hätte Oracle das auch schon vor Java 8 integrieren können, doch erst Lambda-Ausdrücke führen zu einer kompakten Schreibweise. Das sieht zum Beispiel so aus:

log.info( () -> { "Open file: " + filename } );

Die Default-Falle

Insbesondere bei Kodierungen und zeitgebundenen Eigenschaften müssen sich Entwickler zu jeder Zeit bewusst sein, welche Einstellung gerade verwendet wird. Neulinge greifen oft auf Default-Einstellungen zurück und String-Parsing mit Scanner und Ausgaben mit Formatter funktionieren in der Entwicklung, doch spätestens wenn die Software halb um den Globus wandert, läuft nichts mehr, weil die Default-Werte plötzlich anders sind.

Wenn Konstruktoren oder Methoden es nicht explizit verlangen, greift das JDK auf Standardwerte unter anderen für

· Zeilenendezeichen

· Zeichenkodierung

· Sprache (Locale)

· Zeitzone (TimeZone)

zurück.

Ein Beispiel: Der Konstruktor Scanner(File) öffnet eine Datei zum Lesen und konvertiert die Bytes in Unicodes mit einem Konverter, den die Default-Zeichenkodierung bestimmt. Wird aus dem Scanner eine Zahl gelesen, etwa mit nextDouble(), greift die voreingestellte Default-Locale, die dem Scanner sagt, ob Dezimalzahlen mit „,“ oder „.“ interpretiert werden muss. Verarbeitet ein Java-Programm die gleiche Textdatei einmal in den USA und Deutschland, ist das Ergebnis unterschiedlich und in der Regel sollte das nicht so sein.

Default-Werte sind eine gute Sache, allerdings sollten Entwickler sich bewusst sein, an welchen Stellen das JDK auf sie zurückgreift, um keine Überraschungen zu erleben. Es lohnt sich, immer konkrete Belegungen anzugeben, auch wenn als Argument zum Beispiel Locale.getDefault() steht. Das dokumentiert das gewollte Nutzen der Default-Werte.