IntelliJ IDEA 14, tolle neue Features

Alle Details unter https://www.jetbrains.com/idea/whatsnew/. Auf die Schnelle:

  • built-in decompiler
  • Show Referring Objects im Debugger
  • evaluate to operator expressions im Debugger
  • infers the@NotNull, @Nullable and @Contract
  • Scala plugin comes with theChange Signature refactoring, reworked SBT integration, faster performance, brand new project configuration model, and many more
  • enhancements and new features introduced in Android Studio Beta, including support for Google Wear andTV.
  • Advanced coding assistance for Thymeleaf
  • visual diagrams forSpring Integration
  • standard Test Runner für Gradle task
  • The Scene Builder is now built into the Editor.
  • support for GlassFish 4.1, TomEE 1.7.1, WildFly 9.0.0 and tcServer 3.0.1.
  • better log viewer for Git and Mercurial
  • tools for SQL developers have been improved

Turtle-Grafik

Die Turtle-Grafik (engl. turtle, zu Deutsch Schildkröte) ist eine Idee von Seynour Papert, der damit ein Lehrmodell für den Unterricht schaffen wollte. Populär wurde der Turtle sicherlich durch die „Kindersprache“ LOGO. Aber auch anderswo wurde mit dem Turtle viel gearbeitet. Und so erhielt er viele Namen. Eingeführt in der Oberstufe durch einen Roboter (Robi oder so) bzw. den Hamster Nikki.

Das Turtle-Grundprogramm

Um den (das?) Turtle zu steuern, brauchen wir lediglich ein paar Unterprogramme:

  • void turnright(int winkel)
  • void turnleft(int winkel)
  • void forwd(float step)
  • void back(float step)

Die Implementierung dieser grundlegenden Befehle ist sehr einfach. Wir werden dazu den Winkel und die Position als lokale Variablen einführen und darauf zurückgreifen. Die Unterprogramme werden die Variablen angle, x, y‚ oldx, oldY nutzen:

import java.awt.AWTEvent;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.event.WindowEvent;

@SuppressWarnings( "serial" )
public class Turtle extends Frame
{
 private double angle = 0, x = 300, y = 300, oldX = 300, oldY = 300;
 private Graphics graphics;

 // Initialisiert Turtle-Grafik und bringt Frame auf den Schirm
 public Turtle()
 {
 setTitle( "Turtle-Grafi" );
 setSize( 600, 600 );
 enableEvents( AWTEvent.WINDOW_EVENT_MASK );
 setVisible( true );
 }

 public void setGraphics( Graphics graphics )
 {
 this.graphics = graphics;
 }

 // Methoden, die den Stift bewegen

 public void turnright( double degree )
 {
 if ( (angle += degree) > 360 )
 angle %= 360;
 }

 public void turnleft( double degree )
 {
 if ( (angle -= degree) < 0 )
 angle %= 360;
 }

 public void forwd( double step )
 {
 back( -step );
 }

 public void back( double step )
 {
 oldX = x;
 oldY = y;

 x -= step * Math.sin( Math.toRadians( angle ) );
 y += step * Math.cos( Math.toRadians( angle ) );

 graphics.drawLine( (int) x, (int) y, (int) oldX, (int) oldY );
 }

 // Events vom Schließen des Fensters abfangen

 @Override
 protected void processWindowEvent( WindowEvent e )
 {
 if ( e.getID() == WindowEvent.WINDOW_CLOSING )
 System.exit( 0 );

 super.processWindowEvent( e );
 }
}

Mit dieser kleinen Basisklasse ist schon eine Menge machbar, wie es uns der folgende Abschnitt uns zeigen wird.

Dreiecke und Kurven

Zum Einstieg in die Turtle-Grafik eignen sich rekursive Grundstrukturen. Dazu bieten sich Dreiecke und Kurven besonders an.

Dreiecke

Das erste darzustellende Objekt wird ein Dreieck sein. Im Dreieck werden weitere Dreiecke erscheinen, was durch Rekursion ein leicht lösbares Problem ist. Die Dreiecke bestehen dabei aus wiederum drei Dreiecken mit der halben Seitenlänge, die in den Spitzen sitzen. Schauen wir uns einmal das Listing an:

import java.awt.Graphics;

final class Dreiecke extends Turtle
{
 public static void main( String args[] )
 {
 new Dreiecke();
 }

 void tri( float len, int deep )
 {
 if ( deep != 0 ) {
 for ( int i = 1; i < 4; i++ ) {
 forwd( len );
 turnright( 120 );         // (a)
 tri( len / 2, deep - 1 ); // (b)
 }
 }
 }

 public void paint( Graphics g )
 {
 setGraphics( g );
 tri( 100, 2 );
 }
}

Werden die die Zeilen (a) und (b) vertauscht, so erscheinen die kleineren Dreiecke in die größeren geklappt.

Mit

tri(len/2, deep-1); back(len); turnright(120);

in der for-Schleife erhalten wir einen weiteren Effekt, nach dem Zeichnen einer Seite wird der Turtle rückwärts gesetzt. Somit ist nicht das innere des Dreieckes gefüllt, sondern die Seiten wandern nach außen.

Peano-Kurve

Bisher wurden von uns nur gleichseitige Dreiecke untersucht. Die Peano-Kurve ist ebenfalls aus einem Dreieck zusammengesetzt, jedoch ist hier die Grundfigur gleichseitig und rechtwinklig. In einem Dreieck passen zwei weitere. Auffallend ist, dass sich Peano-Kurven immer schneiden, andere Kurven machen dies — wie wir sehen werden — nicht immer. Die Peano-Kurve ist das beste Beispiel einer flächendeckenden Kurve.

import java.awt.*;

final class PeanoKurve extends Turtle
{
 final static double SQRT2 = Math.sqrt( 2 );

 public static void main( String args[] )
 {
 new PeanoKurve();
 }

 void peano( double len, int deep )
 {
 if ( deep != 0 ) {
 forwd( len );
 turnleft( 135 );
 peano( len / SQRT2, deep - 1 );

 forwd( len / SQRT2 );
 turnleft( 90 );
 peano( len / SQRT2, deep - 1 );

 forwd( len / SQRT2 );
 turnleft( 135 );
 }
 }

 public void paint( Graphics g )
 {
 setGraphics( g );
 peano( 200, 5 );
 }
}

Koch-Kurve

Das Idee einer Koch-Kurve ist einfach: Ersetzte jede gezeichnete Strecke durch eine Grundfigur. Diese Grundfigur setzt sich aus einer kleineren Grundfigur zusammen, usw. Im Gegensatz zu den anderen Programmen, wird nicht auf jeder Schachtlungstiefe gezeichnet, sondern nur auf der Untersten. Das bedeutet, nur die kleinen Strukturen kommen auf den Schirm. Zur Verdeutlichung das folgende Programm:

import java.awt.*;

final class KochKurve extends Turtle
{
 public static void main( String args[] )
 {
 new KochKurve();
 }

 void koch( double len, int deep )
 {
 if ( deep != 0 ) {
 koch( len / 3, deep - 1 );
 turnleft( 60 );
 koch( len / 3, deep - 1 );
 turnright( 120 );
 koch( len / 3, deep - 1 );
 turnleft( 60 );
 koch( len / 3, deep - 1 );
 }
 else // deep == 0
 forwd( len );
 }

 public void paint( Graphics g )
 {
 setGraphics( g );
 koch( 200, 3 );
 }
}

Die immer gern gesehene Schneeflocke erhalten wir durch zusammenfügen der drei Seiten, die durch die Funktion koch() einzeln gezeichnet wurden. Die paint(…)–Methode erweitern wir durch eine kleine Schleife.

public void paint( Graphics g )
{
 setGraphics(g);

 for (int i=0; i < 3; i++) {
 koch(100, 2);
 turnright(120);
 }
}

Hilbert-Kurven

Mit der Hilbert-Kurve wollen wir das Kapitel der Kurven beenden. Das Grundprinzip wurde deutlich und die wichtigsten Namen genannt — Koch, Peano und Hilbert. In der Hilbert—Kurve wird nun in den Ecken die Funktion wieder rekursiv aufgerufen, gezeichnet wird in jeder Schachtelungstiefe. Hier gilt es sich zu merken: Die Kurve hat weder Berührungspunkte noch Schnittpunkte.

import java.awt.*;

final class HilbertKurve extends Turtle
{
 public static void main( String args[] )
 {
 new HilbertKurve();
 }

 void hilbert( double len, int deep, int direction )
 {
 if ( deep > 0 ) {
 turnleft( direction * 90 );
 hilbert( len, deep - 1, -direction );

 forwd( len );
 turnright( direction * 90 );
 hilbert( len, deep - 1, direction );

 forwd( len );
 hilbert( len, deep - 1, direction );

 turnright( direction * 90 );
 forwd( len );
 hilbert( len, deep - 1, -direction );

 turnleft( direction * 90 );
 }
 }

 public void paint( Graphics g )
 {
 setGraphics( g );
 hilbert( 50, 4, 1 );
 }
}

Ist die Variable direction gerade, so beginnt die Hilbert-Kurve mit einer Links-‚ andernfalls mit einer Rechtsdrehung. Das Programm enthält noch einen Fehler, welchen?

Die ersten Bäume

In diesem Abschnitt wollen wir uns mit Bäumen beschäftigen. Sie sind ein ebenso wie die Kurven ein gutes Beispiel der Turtle-Grafik.

Ein simpler Baum

Beginnen wir mit einem einfachen Baum, wo noch nicht Viel nötig ist. Hier sind einfach ein paar Turtle-Befehle hintereinander gereiht worden.

public void tree( float len )
{
 turnleft( 45 );
 forwd( len );
 back( len );
 turnright( 90 );
 forwd( len );
 back( len );
 turnleft( 45 );
}

Rekursion bringt’s

Nun kann es mit der Baumgenerierung so richtig losgehen. Ein Baum ist ein einfaches Geflecht aus Ästen. Das Gute daran: jeder Ast hat weitere Äste. Das hört sich natürlich schon nach Rekursion an. Wir deklarieren ein Unterprogramm tree()‚ dass eine V-förmige Grundstruktur zeichnet. Beim Zeichnen jedes weiteren Astes wird wieder tree() aufgerufen, bis die Länge eines Astes auf unter 10 schrumpft.

import java.awt.*;

final class Tree extends Turtle
{
 public static void main( String args[] )
 {
 new Tree();
 }

 void tree( double len )
 {
 if ( len > 10 ) {
 turnleft( 45 );
 forwd( len );
 tree( len / 2 );

 back( len );
 turnright( 90 );
 forwd( len );
 tree( len / 2 );

 back( len );
 turnleft( 45 );
 }
 }

 public void paint( Graphics g )
 {
 setGraphics( g );
 tree( 150 );
 }
}

Anstatt mit einer Zweiglänge abzubrechen, kann auch nach Erreichen einer bestimmten Schachtelungstiefe — also die Anzahl rekursiver Aufrufe — abgebrochen werden. Dies wollen wir jetzt einmal versuchen. Zu der Länge eines Astes soll die Schachtelungstiefe und der Zeichenwinkel hinzukommen.

Um etwas Spannung in den Algorithmus zu bekommen, lassen wir den Winkel immer etwas größer werden. Das Ganze sieht dann so aus:

void tree2( float len, int deep, int angle )
{
 if ( deep != 0 ) {
 turnleft( angle );
 forwd( len );
 tree2( len / 2, deep - 1, angle + 3 );

 back( len );
 turnright( 2 * angle );
 forwd( len );
 tree2( len / 2, deep - 1, angle + 3 );

 back( len );
 turnleft( angle );
 }
}

Und wir erhalten mit dem Unterprogramm und der Zeile tree2( 100, 6, 20 ) einen kleinen Baum — doch irgendetwas fehlt! Es ist der Stamm. Um ihn anzufügen bedarf es lediglich kleiner Änderungen.

void tree3( double len, int deep )
{
 if ( deep != 0 ) {
 forwd( len );
 turnleft( 45 );
 tree3( len / 1.5, deep - 1 );

 turnright( 90 );
 tree3( len / 1.5, deep - 1 );

 turnleft( 45 );
 back( len );
 }
}

Doch auch mit Stamm wirkt der Baum zu künstlich. Um dem etwas entgegenzuwirken, wollen wir den Abknickwinkel und ein Verkürzungsverhältnis mit einbauen. Zudem soll der rechte und linke Winkel ein anderer sein. Sie sollen dem Unterprogramm übergeben werden. Hier das Ergebnis:

void tree4( double len, int deep, int angleL, int angleR, double lenL, double lenR )
{
 if ( deep != 0 ) {
 forwd( len );
 turnleft( angleL );
 tree4( len * lenL, deep - 1, angleL, angleR, lenL, lenR );

 turnright( angleL + angleR );
 tree4( len * lenR, deep - 1, angleL, angleR, lenL, lenR );

 turnleft( angleR );
 back( len );
 }
}

Hier können wir viele Parameter variieren und bekommen eine Vielzahl von Bäumen. Mit tree(100, 6, 30, -5, 0.5, 0.75) generieren wir einen Baum im Wind.

Mehr Formenvielfalt

Leider lässt auch dieser Baum seine Herkunft nicht verleugnen. Die Äste sind noch zu gleichmäßig. Um dem ein Ende zu setzen, werden Zufallswerte eingesetzt, damit ein Baum öfters einmal anders aussieht. Um Zufallszahlen mit Hilfe von Funktionen der Java-Bibliothek nutzen zu können, ist das Mathe-Paket einzubinden. Die Funktion rand() wird dann Zufallszahlen zwischen Null und Eins liefern. Sie können als Multiplikatoren zu der Länge oder dem Winkel genommen werden. Die Bilder bekommen dadurch eine ganz andere Wirkung.

Eine ganz andere Möglichkeit ist das Abweichen vom V-förmigen Grundmuster. Es ist aus verschiedenen Gründen sinnvoll, von dieser Form abzuweichen, und eine asymmetrische Figur zu wählen. Die Symmetrie wird insofern gebrochen, dass nicht auf jedes V ein weites folgt, die linke Seite bleibt etwas „unterentwickelt“. Ziel ist ein harmonischeres Bild, und eine natürlichere Form.

void tree5( double len, int deep, int angle, double factor )
{
 if ( deep != 0 ) {
 turnleft( angle );
 forwd( len );
 tree5( len * factor, deep - 1, angle, factor );

 back( len );
 turnright( angle * 2 );
 forwd( len );
 turnright( angle );
 forwd( len );
 tree5( len * factor, deep - 1, angle, factor );

 back( len );
 turnleft( angle * 2 );
 forwd( len );
 turnleft( angle );
 forwd( len );
 tree5( len * factor, deep - 1, angle, factor );

 back( len );
 turnright( angle * 2 );
 forwd( len );
 tree5( len * factor, deep - 1, angle, factor );

 back( len );
 turnleft( angle );
 back( len );
 turnright( angle );
 back( len );
 turnleft( angle );
 }
}

Ein Beispielaufruf: tree5( 50, 4, 30, 0.6 ),

L-Systeme

Bisher war die Umsetzung von Wachstumsprozessen ein aufwändiges Unterfangen. Anstatt Zwei rechts, zwei links zu diktieren, ist es wünschenswert eine Sprache zur Formulierung von Wachstumprozessen einzuführen. Immer ein Programm vor Augen zu haben welches mit Schlüsselwörtern wie forwd(), left()ist auch vom mathematischen Gesichtspunkt her nicht sinnvoll. Im Jahre 1968 führte der Biologe Aristid Lindenmayer die sogenannten L-Svsteme (Lindenmayer-Systeme) zur Beschreibung von Wachstumsprozessen ein. Dieses System entpuppt sich bei näherem Hinschauen als eine kontextfreie Grammatik (CFG). Wie bei der Grammatik so üblich besteht das L-System aus einem Tupel, bestehend aus einem Alphabet V = {a1, …, an}, Produktionsabbildungen P: V → V* wobei V* die Menge aller bildbaren Zeichenketten beschreibt. Mit einer Zeichenkette werden nun die Wachstumsprozesse beschrieben. Dazu werden Turtle-Kürzel eingeführt, die in folgender Tabelle aufgelistet sind.

Zeichen: Reaktion
F: um Schrittweite l nach vorne
f: hebe Schwanz und zeichne nicht
+: Winkel delta gegen den Uhrzeigersinn
-: Winkel delta im Uhrzeigersinn

Tabelle: Befehle des Automaten

Die Konstanten l und d sind Länge und Winkel.Die Zustände können als Tupel verwaltet werden. Die Einträge: x bzw. y ist die Koordinate, alpha der Winkel. Der Start ist mit (0,0,0) festgesetzt. Nach Rechts wird also losgelaufen. Die oben aufgelisteten Operationen angewandt, verändern das Tupel wie in der folgenden Tabelle angegeben.

Befehl: Zustand geht über in
F: (x + l * cos(alpha) , y + l * sin(alpha), alpha)
f: (x + l * cos(alpha), y + l * sin(alpha), alpha)
+: (x, y. alpha + delta)
-: (x, y. alpha – delta)

Tabelle: Änderung des Zustandes

Das L-System wird auch unter dem Namen Graftal geführt. Gelegentlich tauchen auch die Ziffern 0 und 1 auf, um keinen bzw. einen Schritt in die vorgegebene Richtung zu gehen. Eckige Klammern werden ebenso verwendet wie kennengelernt. Beispiel: 11[11]1. Gehe zwei Schritte nach vorne, zeichne dann einen Ast (die Verzweigung) von 2 Einheiten Länge, kehre zum Verzweigungspunkt zurück und ergänze den Stamm um eine Einheit. Einige bekannte Kurven sollen nun in L-System-Notation verdeutlicht werden.

Koch

Wir erinnern uns da sicherlich noch an die Koch-Kurve, bestehend aus 4 Strecken. Der Winkel der Strecken betrug immer 60 Grad, sodass wir in unserem System delta = 60° setzen können. Der Erzeugungs-String ist dann F+F–F+F. Die Abarbeitung folgt von links nach rechts, wie ein normaler mathematischer Ausdruck. Die Interpretation dieses Strings: Der Turtle geht einen Schritt der Länge l vor, dreht sich dann um 60 Grad nach links, geht wieder einen Schritt voraus, dreht sich zweimal um 60 Grad (also dann um 120 Grad) nach links, geht anschließend einen Schritt vor, um dann nach einer erneuten Drehung und Schritt voran zum Ende zu kommen. In der oben geschriebenen Schreibweise hätten wir also eine Produktionsregel, die in der Informatik die Schreibweise F → F+F–F+F bekäme.

Sierpinski-Pfeilspitze

Die gesamte Information über den Aufbau eines Objektes fasst man nun in einem Axiom zusammen. Somit wird die Beschreibung eines Objektes sehr kurz und kann in einer Tabelle leicht beschrieben werden.

Axiom: L
Produktionsregeln: L → +R-L-R+
R → -L+R+L
delta: 60°

Man sieht an diesem Beispiel, wie günstig es ist, mehrere Variablen einzuführen, um das Bild etwas übersichtlicher zu gestalten.

Achtung! Obwohl es nach einer Endlos-Verschachtelung aussieht — L ruft R auf und wieder umgekehrt — läuft das Programm trotzdem zu Ende. Es ist vielmehr dem Programmierer überlassen eine Abbruchbedingung zu implementieren. So beispielsweise das Abbrechen bei einer bestimmten Schachtlungstiefe oder Stammlänge.

Drachen-Kurve

Axiom: D
Produktionsregeln: D → -D++E
E → D–E+
delta: 60°

Bäume und Büsche

Nachdem wir das Grundsystem kennengelernt haben, dürfte es nicht schwerfallen, fraktale Gewächse zu entwickeln. Die Frage ist hier nur: Wie kann man eine Struktur, die sich verzweigt in einer Zeichenkette darstellen, die das L—System letztendlich verwendet? Notwendig dazu ist die Einführung zweier Symbole: [ und ]. Gelangt der Turtle bei der Interpretation der Zeichenkette an eine eckige Klammer, so muss er die Position und die Richtung des Turtles sichern und später wieder restaurieren. Nun hier ein Beispiel für eine krautartige Pflanze:

Axiom: F
Produktionsregeln: F —> F[+F]F[—F]F
delta: 25,7°

und noch ein Kraut:

Axiom: B
Produktionsregeln: F → FB
B → F[+B]F[—B]+B
delta: 25,7°

Bisher waren die vorgestellten Systeme immer deterministisch, das heißt es gab ein absehbares Ende und eine voraussehbare Form. Wenn der Zufall in ein L-System einzieht, so nennt man dies nicht-deterministisch Systeme stochastisch. Doch wenn der Zufall einfließt ist eine Aussage über das Wachstum schwierig. Dennoch ist es wichtig zu bestimmen wie groß die Wahrscheinlichkeit sein soll, dass ein Ereignis eintritt. Wenn der Baum z.B. nach rechts driften soll, so kann man Umgangssprachlich sagen: In 2 von drei Fällen gehe nach rechts. Um dies auszudrücken erweitern wir die Schreibweise ein wenig, und fügen die Wahrscheinlichkeit mit an. Hier das Beispiel von Kraut Nr. 3. (Wahrscheinlichkeit wurde
mit R abgekürzt.)

Axiom: F
Produktionsregeln: F → F[+F]F[—F]FR 1/3
F → F[+F]FR 1/3
F → F[—F]FR 1/3
delta: 25,7°

Die Anzahl der Produktionsregeln bestimmt also immer den Nenner des Bruches.

Etwas entfernt von der Pflanzen soll abschließend das L-System einer zufälligen Koch-Kurve aufgezeigt werden:

Axiom: F
Produktionsregeln: F → F-F++F—FR 0.5
F → F+F–F+FR 0.5
delta: 60°

Inselupdate: Wie wo was dynamisch binden

Es gibt bei Methoden von konkreten Klasse, abstrakte Klassen und Schnittstellen Unterschiede, wo der Aufruf letztendlich landet. Nehmen wir folgende Methode an:

void f( T t ) {
  t.m();
}

Fordert die Methode ein Argument vom Typ T und ruft auf dem Parameter t die Methode m() auf, so können wir folgendes festhalten:

· Ist T eine finale Klasse, so wird immer die Methode m() von T aufgerufen, da es keine Unterklassen geben kann, die m() überschreiben.

· Ist T eine nicht-finale Klasse und m() eine finale Methode, wird genau m() aufgerufen, weil kein Unterklasse m() überschreiben kann.

· Ist T eine nicht-finale Klasse und m() keine finale Methode, so könnten Unterklassen von T m() überschreiben und t.m() würde dann dynamisch die überschriebene Methode aufrufen.

· Ist T eine abstrakte Klasse und m() eine abstrakte Methode, so wird in jedem Fall eine Realisierung von m() in einer Unterklasse aufgerufen.

· Ist T eine Schnittstelle, und m() keine Default-Implementierung, so wird in jedem Fall eine Implementierung m() einer implementierenden Klasse aufgerufen.

· Ist T eine Schnittstelle, und m() eine Default-Implementierung, so kann t.m() bei der Default-Implementierung landen, oder bei einer überschriebenen Version einer implementierenden Klasse.

JBoss Tools 4.2 freigegeben

Es gibt viele Neuerungen, siehe http://tools.jboss.org/documentation/whatsnew/jbosstools/4.2.0.Final.html. Vieles dreht sich um Cordova. Die Hibernate Tools unterstützen JPA 2.1 und Hibernate 4.3. AngularJS wird erstmalig über einen Wizard zum Aufbau von HTML5-Anwendungen unterstützt sowie ist ein AngularJS Eclipse Plugin integriert. Auch node.js erkennt der JS-Editor bei der Vervollständigung, für JS-Funktionen gibt es nun auch eine API-Dokumentation aus der JS-ECMA-Spezifikation. HTML Preview ist ein WYSIWYG Plugin für HTML pages

Log4j 2.1 freigegeben

Ralph Goers hat die Freigabe von Apache Log4j 2.1 bekanntgegeben: https://mail-archives.apache.org/mod_mbox/www-announce/201410.mbox/%3CBC551B1B-6257-4DB1-B640-0858A400FC68@apache.org%3E.

Neuerungen sind unter anderem:

o LOG4J2-868:  Add ShutdownCallbackRegistry interface for customizable shutdown callback handling.
This is particularly
       useful for application servers that wish to integrate with Log4j 2. 
o LOG4J2-589:  Supported filtering on custom log levels in configuration. 
o LOG4J2-856:  Documentation: add sections on the JUL Adapter, IO Streams and NoSQL Appenders
to the Maven and Ivy page. 
o LOG4J2-848:  Add a Java lookup to provide nicely formatted runtime version information.

o LOG4J2-809:  Move reflection utility class to API's private utility classes. 
o LOG4J2-833:  Documentation: added Runtime Dependencies link to left nav-bar on site. 
o LOG4J2-816:  Documentation: added section on XInclude to user manual Configuration page.

o LOG4J2-547:  Add the Log4j IOStreams component. 
o LOG4J2-431:  Added Memory-Mapped File Appender. Thanks to Claude Mamo. 
o LOG4J2-827:  Support use of TypeConverter classes through the standard Plugin system. 
o LOG4J2-825:  Add simple validation constraint annotations for the Plugin system. 
o LOG4J2-428:  Implement a GELF layout. Thanks to Mark Paluch. 
o LOG4J2-608:  Add java.util.logging implementation based on log4j-api. See log4j-jul documentation
for more details. 
o LOG4J2-793:  Add support for custom SLF4J Markers in log4j-slf4j-impl module. 
o LOG4J2-771:  Add lookup for application main arguments. 
o LOG4J2-787:  Add lookup for JVM arguments. 

Doppelklammer-Initialisierung

Da anonyme Klassen keinen Namen haben, muss für Konstruktoren ein anderer Weg gefunden werden. Hier helfen Exemplarinitialisierungsblöcke, also Blöcke in geschweiften Klammern.

Exemplarinitialisierer gibt es ja eigentlich gar nicht im Bytecode, sondern der Compiler setzt den Programmcode automatisch in jeden Konstruktor. Obwohl anonyme Klassen keinen direkten Konstruktor haben können, gelangt doch über den Exemplarinitialisierer Programmcode in den Konstruktor der Bytecode-Datei.

Dazu ein Beispiel: Die anonyme Klasse ist eine Unterklasse von Point und initialisiert im Konstruktor einen Punkt mit Zufallskoordinaten. Aus diesem speziellen Punkt-Objekt lesen wir dann die Koordinaten wieder aus:

java.awt.Point p = new java.awt.Point() {

{

x = (int)(Math.random() * 1000); y = (int)(Math.random() * 1000);

}

};

System.out.println( p.getLocation() ); // java.awt.Point[…

System.out.println( new java.awt.Point( -1, 0 ) {{

y = (int)(Math.random() * 1000);

}}.getLocation() ); // java.awt.Point[x=-1,y=…]

Sprachlichkeit

Wegen der beiden geschweiften Klammen heißt diese Variante auch Doppelklammer-Initialisierung (engl. Double Brace Initialization).

Die Doppelklammer-Initialisierung ist kompakt, wenn etwas Datenstrukturen oder hierarchische Objekte initialisiert werden sollen.

Beispiel *

Im Folgenden Beispiel erwartet appendText(…) ein Objekt vom Typ HashMap, was durch den Trick direkt initialisiert wird:

String s = new DateTimeFormatterBuilder()

.appendText( ChronoField.AMPM_OF_DAY,

new HashMap<Long, String>() {{ put(0L, “früh”);put(1L,”spät” ); }} )

.toFormatter().format( LocalTime.now() );

System.out.println( s );

Im nächsten Beispiel bauen wir eine geschachtelte Map, das ist ein Assoziativspeicher, der wieder einen anderen Assoziativspeicher enthält:

Map<String,Object> map = new HashMap<String,Object>() {{

put( “name”, “Chris” );

put( “address”, new HashMap<String,Object>() {{

put( “street”, “Feenallee 1″ );

put( “city”, “Elefenberg” );

}} );

}};

Warnung

Die Doppelklammerinitialisierung ist nicht ganz „billig“, da eine Unterklasse aufgebaut wird, also neuer Bytecode generiert wird. Zudem hält die innere Klasse eine Referenz auf die äußere Klasse fest. Des Weiteren kann es Probleme mit equals(…) geben, da wir mit der Doppelklammerinitialisierung eine Unterklasse schaffen, die vielleicht mit equals(…) nicht mehr gültig vergleichen werden kann, denn die Class-Objekte sind jetzt nicht mehr identisch. Das spricht in der Summe eher gegen diese Konstruktion.

Sich selbst mit this übergeben

Möchte sich ein Objekt A einem anderen Objekt B mitteilen, damit B das andere Objekt A „kennt“, so funktioniert das gut mit der this-Referenz. Demonstrieren wir das an einem „Ich bin dein Vater“-Beispiel: Zwei Klasen Luke und Darth repräsentieren zwei Personen, wobei Luke ein Attribut dad für seinen Vater hat:

LukeAndDarth.java, Teil 1
class Luke {
  Darth dad;
}

class Darth {
  void revealTruthTo( Luke son ) {
    son.dad = this;
  }
}

Spannend ist die Methode revealTruthTo(Luke), denn sie setzt beim übergebenen Luke-Objekt das dad-Attribut mit der this-Referenz. Damit kennt Luke seinen Vater, getestet in folgender Klasse:

LukeAndDarth.java, Teil 2
public class LukeAndDarth {
  public static void main( String[] args ) {
    Luke luke = new Luke();
    Darth darth = new Darth();
    System.out.println( luke.dad ); // null
    darth.revealTruthTo( luke );
    System.out.println( luke.dad ); // Darth@15db9742
  }
}

Hinweis: In statischen Methoden steht die this-Referenz nicht zur Verführung, da wir uns in Klassenmethoden nicht auf ein konkretes Objekt beziehen.

toString() für equals-Vergleiche?

Einige kreative Programmierer nutzen die toString()-Repräsentation für Objektvergleiche. Etwas der Richtung: Wenn wir zwei Point-Objekte p und q haben, und p.toString().equals(q.toString()) ist, dann sind beide Punkte eben gleich. Doch ist es hochgradig gefährlich sich auf die Rückgabe von toString() zu verlassen aus mehreren Gründen: Offensichtlich ist, dass toString() nicht unbedingt überschrieben sein muss. Zweitens muss toString() nicht unbedingt alle Elemente repräsentieren und die Ausgabe könnte abgekürzt sein. Drittens können natürlich Objekte equals-gleich sein, auch wenn ihre String-Repräsentation nicht gleich ist, was etwa bei URL-Objekten der Fall ist. Der einzige erlaubte Fall für so eine Konstruktion wäre String/StringBuilder/StringBuffer/CharSequence, wo es ausdrücklich um Zeichenketten geht.

Habe ich Gründe vergessen?