Antipattern: throw new Exception()

Durch Zufall bin ich bei Heise über http://www.heise.de/developer/artikel/Von-C-nach-Java-Teil-2-Files-I-O-und-eine-Entwicklungsumgebung-1326652.html gestoßen. Eine Implementierung stößt mir auf:

public CFind(String s) throws Exception {
super(s);
if (!isDirectory()) throw new Exception(s+" is NOT a directory!");
for (File f:listFiles()) {
if (f.isDirectory()) {
System.out.println(f.getAbsolutePath());
new CFind(f.getAbsolutePath());
}
}

Es ist keine gute Idee eine direkte Instanz von Exception zu bauen aus auszulösen. Wenn, sollten es immer Unterklasse von Exception sein. In diesem Fall ist das Argument falsch, sodass man ein IllegarArgumentException auslösen könnte. Als zweites gilt throws Exception in der Methodensignatur zu vermeiden – das zwingt Nutzer zur catch(Exception e)-Keule, ganz schlecht, weil dann auch alle RuntimeExceptions aufgefangen werden.

c’t Artikel über Java 7: “Was lange währt …” – eine kurze Nachkritik

In der c’t hat Michael Tamm in der Ausgabe 17 (August) einen Beitrag über Java 7 geschrieben. Der ist ganz gut gelungen und gibt einen netten Einblick in die Neuerungen von Java 7 (präzisiertes rethrow hat er irgendwie verschwiegen, weiß nicht warum).

Ein paar Kleinigkeiten kann man noch anfügen:

  • Bei switch mit Strings: “Für jeden case-Zweig wird die in der switch-Anweisung aufgeführte String-Variable mit Hilfe ihrer Methode equals() mit der Konstanten hinter case verglichen”. Hier könnte man meinen, dass eine switch-Anweisung zu einer if-else-Kaskade mit einer linearen Laufzeit führt. Das stimmt aber nicht. equals() kommt erst relativ spät dazu. Erst gibt es einen switch auf dem Hashwert, wenn der passt, kommt equals(), um mit Hashwert-Kollisionen umzugehen.
  • “… dass man beim Anlegen von Objekten mit generischen Parametern diese auf der rechten Seiten der Zuweisung nicht mehr wiederholen muss…”. Das könnte man so lesen, dass <> ausschließlich bei Zuweisungen oder Initialisierungen gültig ist. Doch <> ist flexibler; es kann durchaus return new ArrayList<>(); heißen. (Dass der Begriff “generischer Parameter” unsauber ist, ist eine andere Sache … )
  • Die Einleitung vom multi-catch beginnt mit “… Seit Java 1.0 gibt es geprüfte (checked exceptions), die immer behandelt oder in der Methodensignatur aufgeführt werden müssen, sowie die ungeprüfte Ausnahmen […].“ Ob multi-catch nun checked oder unchecked Exception fängt ist egal, daher ist die Information an diese Stelle eigentlich überflüssig.
  • Beim Thema try-mit-Ressourcen “Die VM schließt alle hinter dem try in runden Klammern aufgeführten Ressourcen[…]”. Irgendwie macht alles schon die JVM, aber man könnte den Eindruck bekommen, hier ist Magie im Spiel. Das stimmt aber nicht, denn der Compiler erzeugt nur etwas, was wir hätten auch schreiben können, nur haben wir mit dem neuen try weniger Schreibarbeit. Die JVM weiß nicht, ob der tolle Schließ-Code von uns kommt oder nicht.
  • “Das Pendant zur alten Klasse File ist die neue Klasse Path”. Pendant? Klingt, als ob die gleich sind, ist aber absolut nicht so. Wenn das ein Pendant, also eine Kopie wäre, warum dann NIO.2?

Die beschriebenen Dinge sind nur missverständlich, aber leider gibt es auch zwei dickere Falschaussagen:

  • “Mit den in Java 7 eingeführten Typannotationen lassen sich nun auch Typen näher beschreiben”. Es folgt das Beispiel “@NonNull Set<@NonNull Edges> edges;” und er verweist auf den Link von JSR-308 (http://types.cs.washington.edu/jsr308/). Wäre nett, ist aber Blödsinn! Es gibt in Java 7 keine Änderung. Das verlinkten Dokument sagt: “Type annotations are planned to be part of the Java language and are supported by Oracle’s OpenJDK (for JDK 7, as of build M4).” Das JST-308 nicht von Java 7 ist hatte ich auch schon vor einem halben Jahr geschrieben: http://www.tutego.de/blog/javainsel/2011/02/java-se-7-developer-preview-release-verfgbar/, aber ich erwarte nicht, dass Herr Tamm mein Blog liest.
  • “[…] Nimbus Look & Feel […] ist standardmäßig aktiv”. Hätte Michael nur ein Swing-Programm gestartet, wäre ihm aufgefallen, das immer noch Metal aktiv ist und nicht Nimbus. (Grund: Inkompatibilitäten http://blogs.oracle.com/henrik/entry/nimbus_look-and-feel_in_jdk_7). Das SwingSet Demo wählt nur automatisch Nimbus aus.

App Engine 1.5.3 Update

Siehe http://googleappengine.blogspot.com/2011/08/app-engine-153-sdk-released.html, http://code.google.com/p/googleappengine/wiki/SdkForJavaReleaseNotes:

  • Blobstore API – We’ve removed the limits on the size of blob uploads. You can now upload files of any size, allowing your app to serve images, video, or anything your internet connection can handle.
  • Index retrieval – We’ve added the ability for you to programmatically retrieve the list of indexes you’ve currently defined in the datastore, as well as their statuses.
  • Datastore Admin – You can now enable the Datastore Admin function from the Admin Console. This will allow Java users to make use of this functionality, like deleting all entities of a certain kind, without having to upload a Python version of their application. And for Python developers, you no longer need to enable this in your app.yaml file.
  • HRD Migration Trusted Testers – We are seeking early adopters to try out an improved HRD migration tool that requires a read-only period relative to your datastore write rate (as opposed to your datastore size, which is how the current version behaves). Please see the release notes for more information.
  • Download app – Using the AppCfg download_app command, you can download any files that were uploaded from your war directory when you last updated the app version.

Sehr sehr cool: Swing-Anwendungen im Browser

Schaut euch

an.

Interessant für Anwender mobiler Endgeräte, die kein Swing können (Android) oder dürfen (iPad).

Die HTML-Anwendung besteht aus einem bisschen JavaScript, die Interaktionen zum Server schickt, also Mausklicks, Tastendrücke, usw. Dabei entstehen Events wie

MM_984_567_MM_986_567_

die an http://icedrobot.de:9091/EventReceiver per POST gesendet werden. Die Swing-Anwendung (die werden dafür richtig viel Speicher brauchen denke ich, das kommt vielleicht noch mal raus…) läuft nur auf dem Server und der reagiert mit geänderten Bildschirmausschnitten, die wieder auf den Brower Canvas gezeichnet werden. Ist so wie ein remote VNC-Server (http://code.google.com/p/jsvnc/), schöne Sache das. HTML5 wir lieben dich Smile

 

“Schön” im Sourcecode der Kommentar:

//Exclude WebKit for now, as it will trigger a memory leak.

//TODO: should be version dependent, as its fixed in Chrome 14

Interessantes Mathe/Logik-Buch – Fehler in der Didaktik einsetzen

Attila Furdek ist Mathelehrer und hat ein interessantes Buch veröffentlicht: http://www.amazon.de/Fehler-Beschw%C3%B6rer-Typische-Fehler-L%C3%B6sen-Mathematikaufgaben/dp/3831121109 (http://www.bod.de/index.php?id=296&objk_id=51857). Ich finde den Ansatz toll: Er stellt eine Aufgabe vor und suggeriert richtige Lösungen, die aber falsch sind. (Bei Amazon kann man sich ein paar Sachen anschauen.) Der Lernende muss zeigen, warum der Lösungsweg falsch ist. Mit gefällt die Idee gut. In meinen Java-Kursen mache ich am Anfang etwas ähnliches: ich gebe syntaktisch falschen Programmcode vor (Klammern fehlen, Variablendeklarationen fehlen, Initialisierungen fehlen, Semikolon fehlt, usw.), und die Lernenden müssen ohne Compilermeldungen die Fehler finden. Wer die meisten Fehler findet, ist Bug-König. Danach wird aufgelöst und die die Fehler mit dem Compiler abgeglichen, um die Meldungen des Compilers zu lernen und mit den Fehlertypen in Verbindung zu bringen.

Aneinanderreihung von Comparatoren

Oftmals ist das Ordnungskriterium aus mehreren Bedingungen zusammengesetzt, wie die Sortierung in einem Telefonbuch zeigt. Erst gibt es eine Sortierung nach dem Nachnamen, dann folgt der Vorname. Um diese mit einem Compartor-Objekt zu lösen, müssen entweder alle Einzelvergleiche in ein neues Compartor-Objekt verpackt werden, oder einzelne Comparatoren zu einem „Super“-Comparator zusammengebunden werden – die zweite Lösung ist natürlich schöner, denn das erhöht die Wiederverwendbarkeit, denn einzelne Comparatoren können dann leicht für andere Zusammenhänge genutzt werden.

Comparatoren in eine Vergleichskette setzen

Am Anfang steht ein besonderer Comparator, der sich aus mehreren Comparatoren zusammensetzt. Immer dann, wenn ein Teil-Compartor bei zwei Objekten aussagt, dass sie gleich sind (der Vergleich liefert 0 ist), so soll der nächste Comparator die Endscheidung fällen – kann er das auch nicht, weil das Ergebnis wieder 0 ist, geht es zum nächsten Vergleicher.

Den Programmcode wollen wir einen neue Hilfsklasse ComparatorChain setzen:

package com.tutego.insel.util;

import java.util.*;

/**
 * A {@link Comparator} that puts one or more {@code Comparator}s in a sequence.
 * If a {@code Comparator} returns zero the next {@code Comparator} is taken.
 */
public class ComparatorChain<E> implements Comparator<E>
{
  private List<Comparator<E>> comparatorChain = new ArrayList<Comparator<E>>();
  
  /**
   * Construct a new comparator chain from the given {@code Comparator}s.
   * The argument is not allowed to be {@code null}.
   * @param comparators Sequence of {@code Comparator}s
   */
  @SafeVarargs  // ab Java 7
  public ComparatorChain( Comparator<E>... comparators )
  {
    if ( comparators == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    Collections.addAll( comparatorChain, comparators );
  }

  /**
   * Adds a {@link Comparator} to the end of the chain.
   * The argument is not allowed to be {@code null}.
   * @param comparator {@code Comparator} to add
   */
  public void addComparator( Comparator<E> comparator )
  {
    if ( comparator == null )
      throw new IllegalArgumentException( "Argument is not allowed to be null" );

    comparatorChain.add( comparator );
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int compare( E o1, E o2 )
  {
    if ( comparatorChain.isEmpty() )
      throw new UnsupportedOperationException(
                  "Unable to compare without a Comparator in the chain" );

    for ( Comparator<E> comparator : comparatorChain )
    {
      int order = comparator.compare( o1, o2 );
      if ( order != 0 )
        return order;
    }

    return 0;
  }
}

Die ComparatorChain können wir auf zwei Weisen mit den Comparator-Gliedern füttern: einmal zu Initialisierungszeit im Konstruktor, und dann später noch über die addComparator()-Methode. Beim Weg über den Konstruktor ist ab Java 7 die Annotation @SafeVarargs zu nutzen, da sonst die Kombination eines Varargs und Generics auf der Nutzerseite zu einer Warnung führt.

Ist kein Comparator intern in der Liste, wird das compare() eine Ausnahme auslösen. Der erste Comparator in der Liste ist auch das Vergleichsobjekt was zuerst gefragt wird. Liefert er ein Ergebnis ungleich 0 liefert das die Rückgabe der compare()-Methode. Ein Ergebnis gleich 0 führt zur Anfrage des nächstes Comparators in der Liste.

Wir wollen diese ComparatorChain für ein Beispiel nutzen, dass eine Liste nach Nach- und Vornamen sortiert.

package com.tutego.insel.util;

import java.util.*;

public class ComparatorChainDemo
{
  public static class Person
  {
    public String firstname, lastname;

    public Person( String firstname, String lastname )
    {
      this.firstname = firstname;
      this.lastname  = lastname;
    }

    @Override public String toString()
    {
      return firstname + " " + lastname;
    }
  }

  public final static Comparator<Person>
    PERSON_FIRSTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.firstname.compareTo( p2.firstname );
      }
    };

  public final static Comparator<Person>
    PERSON_LASTNAME_COMPARATOR = new Comparator<Person>() {
      @Override public int compare( Person p1, Person p2 ) {
        return p1.lastname.compareTo( p2.lastname );
      }
    };

  public static void main( String[] args )
  {
    List<Person> persons = Arrays.asList(
      new Person( "Onkel", "Ogar" ), new Person( "Olga", "Ogar" ),
      new Person( "Peter", "Lustig" ), new Person( "Lara", "Lustig" ) );

    Collections.sort( persons, PERSON_LASTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, PERSON_FIRSTNAME_COMPARATOR );
    System.out.println( persons );

    Collections.sort( persons, new ComparatorChain<Person>(
        PERSON_LASTNAME_COMPARATOR, PERSON_FIRSTNAME_COMPARATOR ) );
    System.out.println( persons );
  }
}

Die Ausgabe ist:

[Peter Lustig, Lara Lustig, Onkel Ogar, Olga Ogar]

[Lara Lustig, Olga Ogar, Onkel Ogar, Peter Lustig]

[Lara Lustig, Peter Lustig, Olga Ogar, Onkel Ogar]

Bug des Tages im Java-Compiler: case

package insel;

public class Insel {

    public static void main(String[] args) {

        switch (1) {
            case (1):
        }
        switch ("") {
            case (""):
        }

    }
}

Eclipse und NetBeans kommen damit zurecht, nicht aber javac. Der Fehler ist bekannt und für das nächste Update gefixt.

An exception has occurred in the compiler (1.7.0). Please file a bug at the Java Developer Connection (http://java.sun.com/webapps/bugreport)  after checking the Bug Parade for duplicates. Include your program and the following diagnostic in your report.  Thank you.
java.lang.NullPointerException
    at com.sun.tools.javac.comp.Lower.visitStringSwitch(Lower.java:3456)
    at com.sun.tools.javac.comp.Lower.visitSwitch(Lower.java:3357)
    at com.sun.tools.javac.tree.JCTree$JCSwitch.accept(JCTree.java:959)
    at com.sun.tools.javac.tree.TreeTranslator.translate(TreeTranslator.java:58)
    at com.sun.tools.javac.comp.Lower.translate(Lower.java:2160)

NIO.2 Umstellung. Mein Fazit (nach einem Tag)

Um zu testen, wie sich die neue NIO.2-Bibliothek so in der Praxis macht, habe ich unsere tutego-Software auf NIO.2 gebracht. Als erstes habe ich File durch Path/Paths/Files-Aufrufe ersetzt. Dabei sind mir schon ein paar Stellen aufgefallen, die ich gerne noch verbessert sehen würde für >= Java 8.

  1. Von den File-Konstrukturen gibt es: File(File parent, String child) und File(String parent, String child). Es ist praktisch, dass der erste Teil entweder File oder String sein kann. Ich habe bei mir eine Menge von Aufrufen der Art new File(path, filename). Mal ist path ein String, mal ein File (in der Regel File). Bei der Konvertierung zu Path wird es ungemütlich, denn es gibt nur get(String first, String… more) aber kein get(Path first, String… more). Also läuft es auf ein path.resove(child) raus, was ich aber nicht so schön finde wie ein get(path, child). Aber alles Geschmacksache.
  2. File wird öfter als Parametertyp akzeptiert als Path. So muss ich schreiben:
    PrintWriter out = new PrintWriter( testimonalsPath.toFile() );
    Schöner wäre ein Konstruktur PrintWriter(Path).
  3. Die Methode getFileName() liefert keinen String, sondern ein Path-Objekt nur mit dem Dateinamen. Daher führt folgendes nicht zum Ziel: path.getFileName().endsWith(".xml"). Und path.getFileName().toString()endsWith(".xml") ist etwas lang.
  4. Files.readAllBytes() ist zwar schön, aber Files.readAllChars(Path,CharSet) wäre auch nett.

Inselupdate: Ein Wort zu Microsoft, Java und zu J++, J#

In der Anfangszeit verursachte Microsoft einigen Wirbel um Java. Mit Visual J++ (gesprochen „Jay Plus Plus“) bot Microsoft schon früh einen eigenen Java-Compiler (Teil vom Microsoft Development Kit) und mit der Microsoft Java Virtual Machine (MSJVM) eine eigene schnelle Laufzeitumgebung. Das Problem war nur, dass Dinge wie RMI und JNI am absichtlich fehlten[1] – JNI wurde 1998 nachgereicht. Entgegen alle Standards führte der J++-Compiler neue Schlüsselwörter multicast und delegate ein. Weiterhin fügte Microsoft einige neue Methoden und Eigenschaften hinzu, zum Beispiel J/Direct, um der plattformunabhängigen Programmiersprache den Windows-Stempel zu verpassen. Mit J/Direct konnten Programmierer aus Java heraus direkt auf Funktionen aus dem Win32-API zugreifen und damit reine Windows-Programme in Java programmieren. Durch Integration von DirectX soll die Internet-Programmiersprache Java multimediafähig gemacht werden. Das führte natürlich zu dem Problem, dass Applikationen, die mit J++ erstellt wurden, nicht zwangsläufig auf anderen Plattformen lauffähig sind waren. Sun klagte gegen Microsoft.

Da es Sun in der Vergangenheit finanziell nicht besonders gut ging, pumpte Microsoft im April 2004 satte 1,6 Milliarden US$ in die Firma. Microsoft erkaufte sich damit das Ende der Kartellprobleme und Patentstreitigkeiten. Dass es bis zu dieser Einigung nicht einfach gewesen war, zeigen Aussagen von Microsoft-Projektleiter Ben Slivka über das JDK beziehungsweise die Java Foundation Classes, man müsse sie »bei jeder sich bietenden Gelegenheit anpissen« (»pissing on at every opportunity«).[2]

Im Januar 2004 beendete die Arbeit an Microsoft J++, denn die Energie floss in das .NET-Framework und der .NET-Sprachen. Am Anfang gab es mit J# eine Java-Version, die Java-Programme auf der Microsoft .NET-Laufzeitumgebungen CLR ausführt, doch Anfang 2007 wurde auch J# eingestellt. Das freie IKVM.NET (http://www.ikvm.net/) ist eine JVM für .NET und kommt mit einem Übersetzter von Java-Bytecode nach .NET-Bytecode, was es möglich macht, Java-Programme unter .NET zu nutzen. Das ist praktisch, denn für Java gibt es eine riesige Anzahl von Programmen, die somit auch für .NET-Entwickler zugänglich sind.

Microsoft hat sich aus der Java-Entwicklung nahezu vollständig zurückgezogen. Es gibt zum Beispiel noch den Microsoft JDBC Driver for SQL Server und Microsoft unterstützt eine API für Office-Dokumente. Das Verhältnis ist heute auch deutlich entspannter und vielleicht gratuliert Microsoft wie es auch Linux zum 20 Geburtstag gratuliert hat[3] irgendwann einmal Oracle.


[1] http://www.microsoft.com/presspass/legal/charles.mspx

[2] Würden wir nicht gerade im westlichen Kulturkreis leben, wäre diese Geste auch nicht zwangsläufig unappetitlich. Im alten Mesopotamien steht »pissing on« für »anbeten«. Da jedoch die E-Mail nicht aus dem Zweistromland kam, bleibt die wahre Bedeutung wohl unserer Fantasie überlassen.

[3] http://www.youtube.com/watch?v=ZA2kqAIOoZM

Performante Ereignisbearbeitung durch LMAX Architektur

Wer performante Systeme entwickelt, sucht immer nach neuen Ideen. Martin Fowler beschreibt in seinem Blog  http://martinfowler.com/articles/lmax.html die Architekturform LMAX einer Trading-Anwendung. Auch die PDF http://disruptor.googlecode.com/files/Disruptor-1.0.pdf beschreibt die Architektur und geht Fragen auf den Grund wie: Was kosten Locks, wo ist das Problem bei Queues, …? Mehr Infos gibt auch der Blog http://blogs.lmax.com/ und der Herz der Implementierung ist quelloffen: http://code.google.com/p/disruptor/.