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.

Hashwerte von Wrapper-Objekten mit neuen Methoden ab Java 8

Der Hashwert eines Objekts bildet den Zustand auf eine kompakte Ganzzahl ab. Haben zwei Objekte ungleiche Hashwerte, so müssen auch die Objekte ungleich sein (mindest, wenn die Berechnung korrekt ist). Zur Bestimmung des Hashwertes deklariert jede Klasse über die Oberklasse java.lang.Object die Methode int hashCode(). Alle Wrapper-Klassen überschreiben diese Methode. Zudem kommen in Java 8 statische Methoden hinzu, sodass sich leicht der Hashwert berechnen lässt, ohne extra ein Wrapper-Objekte zu bilden.

Klasse

Klassenmethode

Objektmethode

Boolean

static int hashCode(boolean value)

int hashCode()

Byte

static int hashCode(byte value)

int hashCode()

Short

static int hashCode(short value)

int hashCode()

Integer

static int hashCode(int value)

int hashCode()

Long

static int hashCode(long value)

int hashCode()

Float

static int hashCode(float value)

int hashCode()

Double

static int hashCode(double value)

int hashCode()

Character

static int hashCode(char value)

int hashCode()

Abbildung 4 Statische Mehtoden hashCode(…) und Objektmethoden im Vergleich

 

Um den Hashwert eines ganzen Objekts zu errechnen, müssen folglich alle einzelnen Hashwerte berechnet werden und diese dann zu einer Ganzzahl verknüpft werden. Schematisch sieht das so aus:

int h1 = WrapperClass.hashCode( value1 );

int h2 = WrapperClass.hashCode( value2 );

int h3 = WrapperClass.hashCode( value3 );

Eclipse nutzt zur Verknüpfung der Hashwerte folgendes Muster, welches eine guter Ausgangspunkt ist:

int result = h1;

result = 31 * result + h2;

result = 31 * result + h3;

LinkedHashMap und LRU-Implementierungen

Da die Reihenfolge der eingefügten Elemente bei einem Assoziatspeicher verloren geht, gibt es mit LinkedHashMap eine Mischung, also ein schneller Assoziativspeicher mit gleichzeitiger Speicherung der Reihenfolge der Objekte. Die Bauart vom Klassename LinkedHashMap macht schon deutlich, dass es eine Map ist, und die Reihenfolge der Objekte liefert ein Iterator; es gibt keine listenähnliche Schnittstelle mit get(int). LinkedHashMap ist für Assoziativspeicher das, was LinkedHashSet für HashSet ist.

Im Gegensatz zur normalen HashMap ruft LinkedHashMap immer genau dann die besondere Methode boolean removeEldestEntry(Map.Entry<K,V> eldest) auf, wenn intern ein Element der Sammlung hinzugenommen wird. Die Standardimplementierung dieser Methode liefert immer false, was bedeutet, dass das älteste Element nicht gelöscht werden soll, wen ein neues hinzukommt. Doch bietet das JDK die Methode aus Absicht protected an, denn sie kann von uns überschrieben werden, um eine Datenstruktur aufzubauen, die eine maximal Anzahl Elemente hat. So sieht das aus:

package com.tutego.insel.util.map;

import java.util.*;

public class LRUMap<K,V> extends LinkedHashMap<K, V> {
  private final int capacity;

  public LRUMap( int capacity ) {
    super( capacity, 0.75f, true );
    this.capacity = capacity;
  }

  @Override
  protected boolean removeEldestEntry( Map.Entry<K, V> eldest ) {
    return size() > capacity;
  }
}

LinkedHashSet bietet eine vergleichbare Methode removeEldestEntry(…) nicht. Wer dies benötigt, muss eine eigene Mengenklasse auf der Basis von LinkedHashMap realisieren.

Doch erst mal keine privaten Interface-Methoden

So schreibt Brian Goetz:

> We would like to pull back two small features from the JSR-335 feature plan:
>
>  - private methods in interfaces
>  - "package modifier" for package-private visibility
>
> The primary reason is resourcing; cutting some small and inessential
> features made room for deeper work on more important things like type
> inference (on which we've made some big improvements lately!)  Private
> methods are also an incomplete feature; we'd like the full set of
> visibilities, and limiting to public/private was already a compromise based
> on what we thought we could get done in the timeframe we had.  But it would
> still be a rough edge that protected/package were missing.
>
> The second feature, while trivial (though nothing is really trivial), loses
> a lot of justification without at least a move towards the full set of
> accessibilities.  As it stands, it is pretty far afield of lambda, nothing
> else depends on it, and not doing it now does not preclude doing it later.
> (The only remaining connection to lambda is accelerating the death of the
> phrase "default visibility" to avoid confusion with default methods.)
>

Die nächsten beiden Tage werden für Java 8 spannend, denn …

… am 31.01.2013 muss das JDK 8 http://openjdk.java.net/projects/jdk8/milestones#Feature_Complete sein. Date & Time hat es noch geschafft. Dann müssen wir den M6 bekommen, das laut Vorgaben enthält:

101 Generalized Target-Type Inference

103 Parallel Array Sorting

104 Annotations on Java Types

109 Enhance Core Libraries with Lambda

115 AEAD CipherSuites

118 Access to Parameter Names at Runtime

119 javax.lang.model Implementation Backed by Core Reflection

120 Repeating Annotations

126 Lambda Expressions & Virtual Extension Methods

135 Base64 Encoding & Decoding

138 Autoconf-Based Build System

139 Enhance javac to Improve Build Speed

140 Limited doPrivileged

142 Reduce Cache Contention on Specified Fields

143 Improve Contended Locking

147 Reduce Class Metadata Footprint

148 Small VM

149 Reduce Core-Library Memory Usage

150 Date & Time API

160 Lambda-Form Representation for Method Handles

161 Compact Profiles

162 Prepare for Modularization

164 Leverage CPU Instructions for AES Cryptography

165 Compiler Control

166 Overhaul JKS-JCEKS-PKCS12 Keystores

170 JDBC 4.2

172 DocLint

173 Retire Some Rarely-Used GC Combinations

So wie ich das überblicke, sind die meisten Punkte realisiert.

Java 8 und JSR-308, was ist Stand der Dinge?

Die Idee bei JSR-308: Annotationen an allem möglichen Typen dranmachen (daher auch der Name “Type Annotations”). Z.B. so:

Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;

Mehr auch unter http://jcp.org/aboutJava/communityprocess/ec-public/materials/2012-01-1011/jsr308-201201.pdf oder in der Spec http://types.cs.washington.edu/jsr308/specification/java-annotation-design.html.

Mit dem aktuellen JDK 8 können zwar Annotationen deklariert werden, die für neue “Orte” stehen (http://download.java.net/jdk8/docs/api/java/lang/annotation/ElementType.html hat seit 1.8 TYPE_PARAMETER und TYPE_USE), aber sonst ist mit Standardcompiler nicht viel los. Testet man oberes Beispiel, gibt es nur Fehler:

@Target(value= ElementType.TYPE_USE)
@interface NonNull { }

@Target(value= ElementType.TYPE_USE)
@interface NonEmpty { }

@Target(value= ElementType.TYPE_USE)
@interface Readonly { }

class Document {}

class Main {
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
}

Dann rappelt es nur:
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: illegal start of type
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚;‘ expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚;‘ expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: ‚(‚ expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;
C:\Users\Christian\Documents\NetBeansProjects\App\src\app\Main.java:53: error: <identifier> expected
    Map<@NonNull String, @NonEmpty List<@Readonly Document>> files;

Wenn man JSR-308 wirklich nutzen möchte, greift man zur experimentelle Version http://openjdk.java.net/projects/type-annotations/. Wenn Oracle hier alles für rund hält, wandert das in das normale OpenJDK 8 Projekt. Das ist wie mit Lambda und dem OpenJDK 8.

Wenn man dann einen funktionierenden Compiler und eine Unterstützung hat, die Annotationen auszulesen, kann ein Checker diverse Sachen testen. Interessant sind @NonNull-Dinger oder auch Test für Immutibility. Hier ist http://types.cs.washington.edu/jsr308/ interessant, ein Checker-Framework, das in den Compiler integriert wird.

JSR-308 ist schon ewig im Gespräch, 2007 (!) hatte ich das schon im Blog: http://www.tutego.de/blog/javainsel/2007/05/erste-implementierung-fur-jsr-308/

Erste Libs springen auf Java 8 auf

So etwa http://www.jdbi.org/.

JDBI is a SQL convenience library for Java.

Beispiel von der Seite in herkömmlicher Notation:

DataSource ds = JdbcConnectionPool.create("jdbc:h2:mem:test",
                                          "username",
                                          "password");
DBI dbi = new DBI(ds);
Handle h = dbi.open();
h.execute("create table something (id int primary key, name varchar(100))");

h.execute("insert into something (id, name) values (?, ?)", 1, "Brian");

String name = h.createQuery("select name from something where id = :id")
                    .bind("id", 1)
                    .map(StringMapper.FIRST)
                    .first();
                    
assertThat(name, equalTo("Brian"));

h.close();

http://skife.org/jdbi/2012/12/10/some-jdbi3.html schreibt nun, dass JDBI 3 Lambda-Ausdrücke nutzen wird und gibt folgendes Beispiel an:

Set<Something> things = jdbi.withHandle(h -> {
    h.execute("insert into something (id, name) values (?, ?)", 1, "Brian");
    h.execute("insert into something (id, name) values (?, ?)", 2, "Steven");

    return h.query("select id, name from something")
            .map(rs -> new Something(rs.getInt(1), rs.getString(2)))
            .into(new HashSet<Something>());
});

assertThat(things).isEqualTo(ImmutableSet.of(new Something(1, "Brian"),
                                             new Something(2, "Steven")));

Das geht sicherlich noch etwas kürzer, warten wir’s ab.

Mit welchen Java-Open-Source Libs Java besser lernen?

Die Apache Commons Lang Lib finde ich für den Einstieg gut geeignet. (Guava ist echt hart für jmd. der gerade seine Java-Gewässer erkundet; ist was für nach dem ersten Hügel.) Nicht zu vergessen die Sourcen zu den Java-Libs selbst. Oder vorbereiten auf Java 8, in etwa mit einem Blick auf die Date-Time-API JSR-310 (SourceForge.net: threeten) wirft.
Was sonst noch?
* log4j bwz. diverse Nachfolger.
* FreeMarker
* Apache Commons CLI oder Alternativen
Dann noch spezielle Libs, wenn man in diversen Technologien einsteigt
* Code aus SwingX (sehr gut, wenn man Swing-Entwicklung macht)
* Tag-Libs
* JavaFX-Extentions
* Eclipse-Plugins
Sonst kann man noch die Java Certification Exams durchgehen für ein besseres Verständnis der Sprache an sich.
Und meine RTFLib jrtf hat noch ein paar Issues offen