Archiv der Kategorie: Insel

Multiply-Accumulate, fma(…)

Die mathematische Operation a × b + c  kommt in Berechnungen oft vor, sodass Prozessoren heute diese Berechung optimiert – das heißt schneller und mit besserer Genauigkeit – durchführen können. In Java 9 gibt es bei Math und StrictMath die neue Methode fma(…) für die “fused multiply-accumulate“-Funktion aus dem IEEE 754 Standard.

class java.lang.Math
class java.lang.StrictMath

  • static double fma(double a, double b, double c)
  • static float fma(float a, float b, float c)
    Führt a × b + c mit dem HALF_EVEN durch. Gegenüber einem einfachen a * b + c berücksichtigt fma(…) die Besonderheiten Unendlich und NaN.

Erweiterung von @Deprecated

In Java 9 wurde der Annotationstyp @Deprecated um zwei Eigenschaften erweitert:

  • String since() default „“. Dokumentiert die Version, seit der das Element veraltet ist
  • boolean forRemoval() default false. Zeigt an, dass das Element in Zukunft gelöscht werden soll.

Beispiel

Ein Beispiel aus der Thread-Klasse:

@Deprecated(since="1.2", forRemoval=true)

public final synchronized void stop(Throwable obj) {

  throw new UnsupportedOperationException();

}

Klassenladername und getPlatformClassLoader

Da es mehrere Klassenlader gibt und diese für das Debuggen leicht zu unterscheiden sein sollen, können Sie Namen tragen. Sie lassen sich im Konstruktor vom URLClassLoader setzen. Die Basisklasse ClassLoader bietet eine Methode getName() zum Erfragen des Namens.

ClassLoader bootstrapLoader = ClassLoader.getSystemClassLoader().getParent();

System.out.println( bootstrapLoader.getName() );                     // platform

System.out.println( ClassLoader.getPlatformClassLoader().getName() );// platform

System.out.println( T.class.getClassLoader().getName() );            // app

Die Methode getName() ist neu in Java 9 sowie auch die ClassLoader-Methode getPlatformClassLoader().

Der Paketname mit Class#getPackageName()

Seit Java 9 gibt es die Methode getPackageName(), die ausschließlich den Paketnamen liefert. Das funktioniert auch auf Class-Objekten von Arrays und primitiven Datentypen.

Beispiel: Alle folgenden Ausgaben sind java.lang.

System.out.println( System.class.getPackageName() );       // java.lang

System.out.println( Thread.State.class.getPackageName() ); // java.lang

System.out.println( byte.class.getPackageName() );         // java.lang

System.out.println( byte[].class.getPackageName() );       // java.lang

 

Beispiel: Finde heraus, ob zwei Klassen im gleichen Paket liegen:

static boolean isSameClassPackage( Class<?> c1, Class<?> c2 ) {

  return   ( c1.getClassLoader() == c2.getClassLoader() )

        && ( c1.getPackageName().equals( c2.getPackageName() ) );

}

Private Attribute auslesen/ändern und der Typ AccessibleObject

Wenn es der Sicherheitsmanager zulässt, kann ein Programm auch private- oder protected-Attribute ändern und Methoden/Konstruktoren eingeschränkter Sichtbarkeit aufrufen. Die Schlüsselfigur in diesem Spiel ist die Oberklasse java.lang.reflect.AccessibleObject, die den Klassen Field und Excecutable (und damit Constructor und Method) die Methode setAccessible(boolean) vererbt. Ist das Argument true und lässt der Sicherheitsmanager die Operation zu, lässt sich auf jedes Element (also Konstruktor, Attribut oder Methode) ungleich der Sichtbarkeitseinstellungen zugreifen:

public class ReadPrivate {
 
   @SuppressWarnings( "unused" )
   private String privateKey = "Schnuppelhase";
 
   public static void main( String[] args ) throws Exception {
     ReadPrivate key = new ReadPrivate();
     Class<?> c = key.getClass();
     java.lang.reflect.Field field = c.getDeclaredField( "privateKey" );
     field.setAccessible( true );
     System.out.println( field.get(key) ); // Schnuppelhase
     field.set( key, "Schnuckibutzihasidrachelchen");
     System.out.println( field.get(key) ); // Schnuckibutzihasidrachelchen
   }
 }

class java.lang.reflect.AccessibleObject
implements AnnotatedElement

  • void setAccessible(boolean flag)
    Nachfolgede Abfragen sollten die Java-Sichtbarkeiten ignorieren. Falls das nicht erlaubt ist, gibt es eine InaccessibleObjectException.
  • final boolean trySetAccessible()
    Wie setAccessible(true), nur löst die Methode beim nicht gewährten Zugriff keine InaccessibleObjectException aus, sondern liefert false. Neu in Java 9.

Warnung: Mit dieser Technik lässt sich viel Unsinn anrichten. Es gibt Dinge, die in der Laufzeitumgebung einfach fest sein müssen. Dazu zählen einmal angelegte Strings oder Wrapper-Objekte. Strings sind immutable, weil sie intern in einem privaten char-Feld gehalten werden und es keine Modifikationsmöglichkeiten gibt. Auch Wrapper-Objekte sind, wenn sie einmal mit einem Konstruktor angelegt wurden, nicht über öffentliche Methoden veränderbar. Sie anschließend per Reflection zu modifizieren, bringt große Unordnung, insbesondere bei den gecachten Integer/Long-Wrapper-Objekten, die die statischen valueOf(…)-Methoden liefern.

Schwache Referenzen und Cleaner

Die Referenzen auf Objekte, die wir im Alltag um uns herum haben, heißen starke Referenzen, weil die automatische Speicherbereinigung niemals ein benutztes Objekt freigeben würde. Neben den starken Referenzen gibt es jedoch auch schwache Referenzen, die es dem GC erlaubt, die Objekte zu entfernen. Was erst einmal verrückt klingt wird dann interessant, wenn es um die Implementierung von Caching-Datenstrukturen geht; ist das Objekt im Cache, ist das schön und der Zugriff schnell – ist das Objekt nicht im Cache, dann ist das auch in Ordnung, und der Zugriff dauert etwas länger. Wir können schwache Referenzen also gut verwenden, um im Cache liegende Objekte aufzubauen, die die automatische Speicherbereinigung wegräumen darf, wenn es im Speicher knapp wird.

Schwache Referenzen interagieren also in einer einfachen Weise mit der automatischen Speicherbereinigung und dafür gibt es im java.base-Modul im Paket java.lang.ref ein paar Typen. Am Wichtigsten sind die Behälter (engl. reference object gennant), die wie ein Optional ein Objekt referenzieren, das aber plötzlich verschwunden sein kann:

  • SoftReference<T>. Ein Behälter für softly reachable Objekte. Die Objekte werden vom GC spät freigegeben, wenn es kurz vor einem OutOutMemoryError
  • WeakReference<T>. Ein Behälter für weakly reachable Objekte. Die Objekte werden vom GC schon relativ früh beim ersten GC freigegeben.
  • PhantomReference<T>. Ein Behälter, der immer leer ist, aber dazu dient mitzubekommen, wenn der GC sich von einem Objekt trennt.
  • Reference<T>. Abstakte Basisklasse von PhantomReference, SoftReference, WeakReference.

Die Behälter selbst werden vom GC nicht entfernt, sodass eine ReferenceQueue<T> ein Abfragen erlaubt, um festzustellen, welche Reference-Behälter leer sind und z. B. aus einer Datenstruktur entfernt werden können – leere Behälter sind nutzlos und können nicht wieder recycelt werden.

Ein neuer Typ ab Java 9 im Paket ist Cleaner, der eine Alternative zur Finalizierung ist. Beim Cleaner lässt sich eine Operation (vom Typ Cleaner.Cleanable) anmelden, die immer dann aufgerufen wird, wenn die automatische Speicherbereinigung zuschlägt und das Objekt nicht mehr erreichbar ist. Intern greift die Klasse auf PhantomReference zurück.

Beispiel: Lege einen Punkt an, registriere einen Cleaner und rege danach den GC an. Eine Konsolenausgabe „Punkt ist weg!“ ist wahrscheinlich:

Point p = new Point( 1, 2 );

Cleaner.create().register( p, () -> System.out.println( "Punkt ist weg!" ) );

p = null;

byte[] bytes = new byte[ (int) Runtime.getRuntime().freeMemory() ];

Auf keinen Fall darf die Aufräumoperation p wieder referenzieren.

 

Deserialisierung absichern mit einem ObjectInputFilter

Alles über readObject() einzulesen was dem ObjectInputStream gegeben wird ist ein Sicherheitsrisiko. Ab Java 8 Update 121[1] – und folglich auch Java 9 – lässt sich ein Filter bei der Deserialisierung setzen, der mit Informationen über die

  • deserialisierte Klasse,
  • Anzahl Objektreferenzen,
  • Array-Längen,
  • aktuelle Tiefe im Objektgraph und
  • Größe des serialisierten Objekts in Bytes

versorgt wird, und sein OK geben muss wenn es zu keiner InvalidClassException während readObject() kommen soll.

Ein Filter ist vom Typ ObjectInputFilter und wird auf einem ObjectInputStream über setObjectInputFilter(ObjectInputFilter filter) gesetzt; den aktuell gesetzten Filter liefert getObjectInputFilter(). Die Deklaration der funktionalen Schnittstelle ObjectInputFilter enthält eine Methode checkInput(…), zwei Aufzählungstypen und eine innere Klasse.

@FunctionalInterface

interface ObjectInputFilter {




  Status checkInput( FilterInfo filterInfo );




  interface FilterInfo {

    Class<?> serialClass();

    long arrayLength();

    long depth();

    long references();

    long streamBytes();

  }




  enum Status {

    UNDECIDED, ALLOWED, REJECTED;

  }




  final class Config {

    public static ObjectInputFilter getSerialFilter() {…}

    public static void setSerialFilter(ObjectInputFilter filter) {…}

    public static ObjectInputFilter createFilter(String pattern) {…}

  }

}

Programmieren wir ein kleines Beispiel mit dieser API. Beginnen wir damit, einen java.awt.Point, ein byte-Array, eine Swing-Komponente und ein leeres org.w3c.dom.Document in ein ByteArrayOutputStream zu serialisieren.

ByteArrayOutputStream bos = new ByteArrayOutputStream();

try ( ObjectOutputStream oos = new ObjectOutputStream( bos ) ) {

  oos.writeObject( new Point(10, 20) );

  oos.writeObject( new byte[1000] );

  oos.writeObject( new JLabel() );

  oos.writeObject( DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument() );

}

Im nächsten Schritt können wir einen ObjectInputStream auf das erzeugte Byte-Array anwenden und Deserialisieren. Zusätzlich setzen wir einen Filter, dessen Implementierung aus drei Teilen besteht:

  1. Testen, ob ein prozessweiter Filter existiert, der eine Entscheidung schon gefallen hat,
  2. Loggen aller Informationen aus dem FilterInfo-Objekt,
  3. Alle serialisierten XML-Dokumente blockieren.

Zum Code:

ObjectInputStream ois = new ObjectInputStream(

                          new ByteArrayInputStream( bos.toByteArray() ) );

ois.setObjectInputFilter( filterInfo -> {




  ObjectInputFilter serialFilter = ObjectInputFilter.Config.getSerialFilter();

  if ( serialFilter != null ) {

    ObjectInputFilter.Status status = serialFilter.checkInput( filterInfo );

    if ( status != ObjectInputFilter.Status.UNDECIDED )

      return status;

  }




  System.out.printf( "depth=%s, references=%s, serialClass=%s, arrayLength=%s, streamBytes=%s%n",

                     filterInfo.depth(), filterInfo.references(), 

                     filterInfo.serialClass(), filterInfo.arrayLength(), filterInfo.streamBytes() );




  if ( Document.class.isAssignableFrom( filterInfo.serialClass() ) )

    return Status.REJECTED;




  return Status.ALLOWED;

} );

Als letzte versuchen wir die vier geschriebenen Objekte einzulesen.

for ( int i = 0; i < 4; i++ )

  System.out.printf( "%d. gelesenes Objekt: %s%n%n", i + 1, ois.readObject() );

Beim letzten Versuch kommt es zu einem Fehler, wie die Ausgabe zeigt:

depth=1, references=1, serialClass=class java.awt.Point, arrayLength=-1, streamBytes=43
  1. gelesenes Objekt: java.awt.Point[x=10,y=20]
depth=1, references=2, serialClass=class [B, arrayLength=-1, streamBytes=70

depth=1, references=2, serialClass=class [B, arrayLength=1000, streamBytes=74
  1. gelesenes Objekt: [B@45dd4eda
depth=1, references=3, serialClass=null, arrayLength=-1, streamBytes=1311

Exception in thread "main" java.io.InvalidClassException: filter status: REJECTED

       at java.base/java.io.ObjectInputStream.filterCheck(ObjectInputStream.java:1276)

…

       ... 10 more

Die API-Dokumentation der Typen erklärten weitere paar Details, z. B. was die Bedeutung von Status.UNDECIDED ist, und wie die System-Property jdk.serialFilter mit einer textuellen Filter-Beschreibung belegt werden kann.

[1]       http://www.oracle.com/technetwork/java/javase/8u121-relnotes-3315208.html

Die Klassen OutputStream und InputStream

Die abstrakte Basisklasse OutputStream

Wenn wir uns den OutputStream anschauen, dann sehen wir auf den ersten Blick, dass hier alle wesentlichen Operationen rund um das Schreiben versammelt sind. Der Clou bei allen Datenströmen ist, dass spezielle Unterklassen wissen, wie sie genau die vorgeschriebene Funktionalität implementieren. Das heißt, dass ein konkreter Datenstrom, der in Dateien oder in eine Netzwerkverbindung schreibt, weiß, wie Bytes in Dateien oder ins Netzwerk kommen. Und an der Stelle ist Java mit seiner Plattformunabhängigkeit am Ende, denn auf einer so tiefen Ebene können nur native Methoden die Bytes schreiben.

abstract class java.io.OutputStream
implements Closeable, Flushable

  • abstractvoidwrite(intb)throwsIOException
    Schreibt ein einzelnes Byte in den Datenstrom.
  • voidwrite(byte[]b)throwsIOException
    Schreibt die Bytes aus dem Array in den Strom.
  • voidwrite(byte[]b,intoff,intlen)throwsIOException
    Schreibt Teile des Byte-Feldes, nämlich len Byte ab der Position off, in den Ausgabestrom.
  • voidclose()throwsIOException
    Schließt den Datenstrom. Einzige Methode aus Closeable.
  • voidflush()throwsIOException
    Schreibt noch im Puffer gehaltene Daten. Einzige Methode aus der Schnittstelle Flushable.

Die IOException ist keine RuntimeException und muss behandelt werden.

Beispiel: Die Klasse ByteArrayOutputStream ist eine Unterklasse von OutputStream und speichert intern alle Daten in einem byte-Array. Schreiben wir ein paar Daten mit den drei gegeben Methoden hinein:

byte[] bytes = { 'O', 'N', 'A', 'L', 'D' };

//                0    1    2    3    4

ByteArrayOutputStream out = new ByteArrayOutputStream();

try {

  out.write( 'D' );          // schreibe D

  out.write( bytes );        // schreibe ONALD

  out.write( bytes, 1, 2 );  // schreibe NA

  System.out.println( out.toString( StandardCharsets.ISO_8859_1.name() )  );

}

catch ( IOException e ) {

  e.printStackTrace();

}

Über konkrete und abstrakte Methoden *

Zwei Eigenschaften lassen sich an den Methoden vom OutputStream ablesen: zum einen, dass nur Bytes geschrieben werden, und zum anderen, dass nicht wirklich alle Methoden abstract sind und von Unterklassen für konkrete Ausgabeströme überschrieben werden müssen. Nur write(int) ist abstrakt und elementar. Das ist trickreich, denn tatsächlich lassen sich die Methoden, die ein Byte-Array schreiben, auf die Methode abbilden, die ein einzelnes Byte schreibt. Wir werfen einen Blick in den Quellcode der Bibliothek:

java/lang/OutputStream,java, Ausschnitt

public abstract void write(int b) throws IOException;
 
 public void write(byte[] b) throws IOException {
   write(b, 0, b.length);
 }
 
 public void write(byte b[], int off, int len) throws IOException {
   if (b == null) {
     throw new NullPointerException();
   } else if ((off < 0) || (off > b.length) || (len < 0) ||
              ((off + len) > b.length) || ((off + len) < 0)) {
     throw new IndexOutOfBoundsException();
   } else if (len == 0) {
     return;
   }
   for (int i = 0 ; i < len ; i++) {
     write(b[off + i]);
   }
 }

An beiden Implementierungen ist zu erkennen, dass sie die Arbeit sehr bequem an andere Methoden verschieben. Doch diese Implementierung ist nicht optimal! Stellen wir uns vor, ein Dateiausgabestrom überschreibt nur die eine abstrakte Methode, die nötig ist. Und nehmen wir weiterhin an, dass unser Programm nun immer ganze Byte-Felder schreibt, etwa eine 5-MiB-Datei, die im Speicher steht. Dann werden für jedes Byte im Byte-Array in einer Schleife alle Bytes der Reihe nach an eine vermutlich native Methode übergeben. Wenn es so implementiert wäre, könnten wir die Geschwindigkeit des Mediums überhaupt nicht nutzen, zumal jedes Dateisystem Funktionen bereitstellt, mit denen sich ganze Blöcke übertragen lassen. Glück-licherweise sieht die Implementierung nicht so aus, da wir in dem Modell vergessen haben, dass die Unterklasse zwar die abstrakte Methode implementieren muss, aber immer noch andere Methoden überschreiben kann. Ein Blick auf die Unterklasse FileOutputStream bestätigt dies.

Hinweis: Ruft eine Oberklasse eine abstrakte Methode auf, die spätger die Unterklasse implementiert, ist das ein Entwurfsmuster mit dem Namen Schablonenmuster oder auf Englisch template pattern.

Gleichzeitig stellt sich die Frage, wie ein OutputStream, der die Eigenschaften für alle erdenklichen Ausgabeströme vorschreibt, wissen kann, wie ein spezieller Ausgabestrom etwa mit close() geschlossen wird oder seine gepufferten Bytes mit flush() schreibt – die Methoden müssten doch auch abstrakt sein! Das weiß OutputStream natürlich nicht, aber die Entwickler haben sich dazu entschlossen, eine leere Implementierung anzugeben. Der Vorteil besteht darin, dass Unterklassen nicht verpflichtet werden, immer die Methoden zu überschreiben, auch wenn sie sie gar nicht nutzen wollen.

Ein Datenschlucker *

Damit wir sehen können, wie alle Unterklassen prinzipiell mit OutputStream umgehen, wollen wir eine Klasse entwerfen, die alle ihre gesendeten Daten verwirft. Die Klasse ist mit dem Unix-Device /dev/null vergleichbar. Die Implementierung muss lediglich die abstrakte Methode write(int) überschreiben.

 public final class NullOutputStream extends java.io.OutputStream {

  @Override public void write( int b ) { /* Empty */ }
 }

Da close() und flush() ohnehin schon mit einem leeren Block implementiert sind, brauchen wir sie nicht noch einmal zu überschreiben.

Die abstrakte Basisklasse InputStream

Das Gegenstück zu OutputStream ist InputStream; jeder binäre Eingabestrom wird durch die abstrakte Klasse InputStream repräsentiert. Die Konsoleneingabe System.in ist vom Typ InputStream. Die Klasse bietet mehrere readXXX(…)-Methoden und ist auch ein wenig komplexer als OutputStream.

abstract class java.io.InputStream
implements Closeable

  • intavailable()throwsIOException
    Gibt die Anzahl der verfügbaren Zeichen im Datenstrom zurück, die sofort ohne Blockierung gelesen werden können.
  • abstractintread()throwsIOException
    Liest ein Byte aus dem Datenstrom und liefert ihn zurück. Die Rückgabe ist -1, wenn der Datenstrom keine Daten mehr liefern kann. Der Rückgabetyp ist int, weil -1 (0xFFFFFFFF) das Ende des Datenstroms anzeigt, und ein -1 als byte (das wäre 0xFF) nicht von einem normalen Datum unterschieden werden könnte.
  • intread(byte[]b)throwsIOException
    Liest bis zu length Bytes aus dem Datenstrom und setzt sie in das Array b. Die tatsächliche Länge der gelesenen Bytes wird zurückgegeben und muss nicht b.length sein, es können auch weniger Bytes gelesen werden. In der OutputStream einfach als return read(b, 0, b.length); implementiert.
  • intread(byte[]b,intoff,intlen)throwsIOException
    Liest den Datenstrom aus und setzt die Daten in das Byte-Array b, an der Stelle off Zudem begrenzt len die maximale Anzahl der zu lesenden Bytes. Intern ruft die Methode zunächst read() auf und wenn es zu einer Ausnahme kommt, endet auch damit unsere Methode mit einer Ausnahme. Es folgenen wiederholten Aufrufe von read(), die dann enden, wenn read() die Rückgabe -1 liefert. Falls es zu einer Ausnahme kommt, wird diese aufgefangen und nicht gemeldet.
  • intreadNBytes(byte[]b,intoff,intlen)throwsIOException
    Versucht len viele Bytes aus dem Datenstrom zu lesen und in das Byte-Array zu setzen. Im Gegensatz zu read(byte[], int, int) übernimmt readNBytes(…) mehrere Anläufe len viele Daten zu beziehen. Dabei greift es auf read(byte[], int, int) zurück. Neue Methode in Java 9.
  • byte[] readAllBytes() throws IOException
    Liest alle verbleibenden Daten aus den Datenstrom und liefert ein Array mit diesen Bytes aus Rückgabe. Neue Methode in Java 9.
  • long transferTo(OutputStream out) throws IOException
    Liest alle Bytes aus dem Datenstrom aus und schreibt sie in out. Wenn es zu einer Ausnahme kommt, wird empfohlen, die Datenströme beide zu schließen. Neue Methode in Java 9.
  • longskip(longn)throwsIOException
    Überspringt eine Anzahl von Zeichen. Die Rückgabe gibt die tatsächlich gesprungenen Bytes zurück, was nicht mit n identisch sein muss.
  • booleanmarkSupported()
    Gibt einen Wahrheitswert zurück, der besagt, ob der Datenstrom das Merken und Zurücksetzen von Positionen gestattet. Diese Markierung ist ein Zeiger, der auf bestimmte Stellen in der Eingabedatei zeigen kann.
  • voidmark(intreadlimit)
    Merkt sich eine Position im Datenstrom.
  • voidreset()throwsIOException
    Springt wieder zu der Position zurück, die mit mark() gesetzt wurde.
  • voidclose()throwsIOException
    Schließt den Datenstrom. Operation aus der Schnittstelle Closeable.

Auffällig ist, dass bis auf mark(int) und markSupported() alle Methoden im Fehlerfall eine IOException auslösen.

Hinweis: available() liefert die Anzahl der Bytes, die ohne Blockierung gelesen werden können. („Blockieren“ bedeutet, dass die Methode nicht sofort zurückkehrt, sondern erst wartet, bis neue Daten vorhanden sind.) Die Rückgabe von available() sagt nichts darüber aus, wie viele Zeichen der InputStream insgesamt hergibt. Während aber bei FileInputStream die Methode available() üblicherweise doch die Dateilänge liefert, ist dies bei den Netzwerk-Streams im Allgemeinen nicht der Fall.

Von char in int: vom Zeichen zur Zahl

Wenn Zeichen aus Benutzereingaben stammen stellt sich die Anforderung, diese in die Zahl zu konvertieren. Aus der Ziffer ‚5‘ soll das der numerische Wert 5 folgen. Nach alter Hacker-Tradition war die Lösung immer die, eine ‚0‘ abzuziehen. Die ASCII-Null ‚0‘ hat den char-Wert 48, ‚1‘ dann 49, bis ‚9‘ schließlich 57 erreicht. So ist logischerweise ‚5‘ – ‚0‘ = 53 – 48 = 5. Die Lösung hat einen Nachteil, dass sie nur für ASCII-Ziffern funktioniert.

Eine ordentliche Java-Lösung besteht zum Beispiel darin, ein char in ein String zu konvertieren und diesen über eine Integer-Methode zu konvertieren, etwa so:

char c = '5';

int  i = Integer.parseInt( String.valueOf(c) );  // 5

Die parseInt(…)-Methode ist voll internationalisiert und konvertiert ebenso Dezimalzahlen aus anderen Schriftsystemen, etwa Hindi/Sanskrit:

System.out.println( Integer.parseInt( "५" ) );   // 5

Das funktioniert, ist jedoch für einzelne Zeichen nicht besonders effizient in Schleifen. Es gibt zwei andere Möglichkeiten, mit statischen Methoden aus der Klasse Character.

getNumericValue(…)-Methode

Die Character-Methode getNumericValue(char) liefert den numerischen Wert einer Ziffer zurück; natürlich arbeitet die Methode wieder internationalisiert:

int i = Character.getNumericValue( '5' );

System.out.println( i );                         // 5

System.out.println( Integer.parseInt( "५" ) );   // 5

Die Methode ist viel leistungsfähiger, denn sie kennt den tatsächlichen „Wert“ aller Unicode-Zeichen. Zum Beispiel auch der römischen Ziffern Ⅰ, Ⅱ, Ⅲ, Ⅳ, Ⅴ, Ⅵ, Ⅶ, Ⅷ, Ⅸ, Ⅹ, Ⅺ, Ⅻ, Ⅼ, Ⅽ, Ⅾ, Ⅿ, …, die im Unicode-Alphabet ab ‚\u2160‘ stehen:

System.out.println( Character.getNumericValue( '\u216f' ) ); // 1000

XXXdigit(…)-Methoden

Die Character-Klasse besitzt ebenso eine Umwandlungsmethode für Ziffern bezüglich einer beliebigen Basis, das auch in die andere Richtung.

final class java.lang.Character
implements Serializable, Comparable<Character>

  • staticintdigit(charch,intradix)
    Liefert den numerischen Wert, den das Zeichen ch unter der Basis radix Beispielsweise ist Character.digit(‚f‘, 16) gleich 15. Erlaubt ist jedes Zahlensystem mit einer Basis zwischen Character.MIN_RADIX (2) und Character.MAX_RADIX (36). Ist keine Umwandlung möglich, beträgt der Rückgabewert –1.
  • staticcharforDigit(intdigit,intradix)
    Konvertiert einen numerischen Wert in ein Zeichen. Beispielsweise ist forDigit(6, 8) gleich „6“ und Character.forDigit(12, 16) ist „c“.

Beispiel: Konvertiere eine Zeichenkette mit Ziffern in eine Ganzzahl.

 char[] chars = { '3', '4', '0' };
 int result = 0;
 for ( char c : chars ) {
   result = result * 10 + Character.digit( c, 10 );
   System.out.println( result );
 }

Die Ausgabe ist 3, 34 und 340.

 

Aufgebaute Exemplare mitzählen

Ein Konstruktor soll die Anzahl der erzeugten Objekte mitzählt; eine statische Methode liefert später die bis dahin gebauten Exemplare:

public class Rollercoaster {




  private static int numberOfInstances;




  {

    numberOfInstances++;

  }




  public static int getNumberOfInstances() {

    return numberOfInstances;

  }




  public static void main( String[] args ) {

    new Rollercoaster();

    new Rollercoaster();




    System.out.println( Rollercoaster.getNumberOfInstances() ); // 2

  }

}

Die statische Variable numberOfInstances wird bei jedem neuen Exemplar über den Konstruktor hochgesetzt. Direkt ausgeschrieben ist der Konstruktor nicht, sondern es findet ein Exemplarinitialisierer Anwendung, da der Compiler den Code automatisch in jeden Konstruktor kopiert. Das hat den Vorteil, dass Entwickler später problemlos neue Konstuktoren für den Rollercoaster hinzufügen können, ohne das Inkrement der statischen Variable immer im Hinterkopf behalten zu müssen.

Hinweis: Bei nebenläufigen Zugriffen auf statische Variablen kann es zu Problemen kommen. Deshalb müssen wir spezielle Synchronisationsmechanismen nutzen – die das Beispiel allerdings nicht verwendet. Statische Variablen können auch schnell zu Speicherproblemen führen, da Objektreferenzen sehr lange gehalten werden. Der Einsatz muss wohldurchdacht sein.

Java SE Embedded Compact Profiles

Hat ein System beschränkten Speicher, wird es knapp für das gesamte Java SE. Daher gibt es Teilmengen der Java SE, genannt Profiles, die nur knapp über 11 MiB benötigen. Im Moment gibt es drei Profile, also drei Teilmengen der Java SE, die jeweils immer etwas mehr können. compact1 ist die kleinste Teilmenge, wo zum Beispiel die Kernklassen enthalten sind, compact2 nimmt die API für Datenbankverbindungen mit auf, compact3 kann dann XML-Verarbeitung und die komplette Java SE grafische Oberflächen darstellen oder Web-Services deklarieren.

Jede Klasse ist einem Profil zugeordnet, was der Compiler statisch prüfen kann – solange der Klassenname nicht dynamisch bestimmt wird. So lässt sich sicherstellen, dass nur Typen referenziert werden, die auch in einem Profil sind.

Oracle Java Standard Edition Embedded (Oracle Java SE Embedded)

Oracle unterstützt mit der Oracle Java Standard Edition Embedded (Oracle Java SE Embedded)[1] Profile. Allerdings laden Entwickler keine einfache Laufzeiumgebung herunter, sondern ein Werkzeug JRECreate, mit dem eine Laufzeitumgebung mit unseren eigenen Paketen für ein bestimmtes System generiert wird. Drei Laufzeitumgebungen sind wählbar: eine Minimal-JVM, Client JVM (schnelle Reaktionsfähigkeit), Server JVM (verbraucht mehr Hauptspeicher). Sehr fein kann dann bestimmt werden, ob JavaFX mit dabei sein soll, oder welche Sprachübersetzungen mit ausgeliefert werden. Am Ende steht ein Verzeichnis mit bin und JRE, lib und Java-Archiven, Konfigurationen und – das darf bei Oracle auf keinen Fall fehlen – Copyright- und Lizenz-Dokumenten.

[1]      http://www.oracle.com/technetwork/java/embedded/embedded-se/downloads/index.html

Boolean nach Ganzzahl konvertieren

Der primitive Typ boolean lässt sich nicht über eine Typumwandlung in einen anderen primitiven Typ konvertieren. Doch in der Praxis kommt es vor, dass true auf 1 und false auf 0 abgebildet werden muss; der übliche Weg ist:

int val = aBoolean ? 1 : 0;

Exotischer ist:

int val = Boolean.compare( aBoolean, false );

Noch exotischer folgendes:

int val = 1 & Boolean.hashCode( true ) >> 1;

Nachträgliches Implementieren von Schnittstellen

Implementiert eine Klasse eine bestimmte Schnittstelle nicht, so kann sich auch nicht am dynamischen Binden über diese Schnittstelle teilnehmen, auch wenn sie eine Methoden hat, über die eine Schnittstelle abstrahiert. Besitzt zum Beispiel die nicht-finale Klasse FIFA eine öffentliche Methode price(), implementiert aber Buyable mit einer gleich benannten Methoden nicht, so lässt sich zu einem Trick greifen, sodass eine Implementierung geschaffen wird, die die existierende Methode aus der Klasse und die der Schnittstelle in die Typhierarchie bringt.

class FIFA {
  public double price() { … }
}

interface Buyable {
   double price();
 }

class FIFAisBuyable extends FIFA implements Buyable { }

Eine neue Unterklasse FIFAisBuyable erbt von der Klasse FIFA und implementiert die Schnittstelle Buyable, sodass der Compiler die existierende price()-Methode mit Vorgabe der Schnittstelle vereinigt. Nun lässt sich FIFAisBuyable als Buyable nutzen und dahinter steckt die Implementierung von FIFA. Als Unterklasse bleiben auch alle sichtbaren Eigenschaften der Oberklasse erhalten.

Setzen des Java Klassenpfades

Wo die JVM die Klassen findet muss ihr mitgeteilt werden, und das ist in der Praxis elementar für die Auslieferung, auch englisch Deloyment genannt. Java wartet mit dem Laden der Klassen so lange, bis sie benötigt werden. Es gibt zum Beispiel Programmabläufe nur zu besonderen Bedingungen und wenn dann erst spät ein neuer Typ referenziert wird, der nicht vorhanden ist, fällt dieser Fehler erst sehr spät auf. Der JVM müssen folglich nicht nur die Quellen für Klassen und Ressourcen der eigenen Applikation mitgeteilt werden, sondern alle vom Programm referenzierten Typen aus zum Beispiel quelloffenen und kommerziellen Bibliotheken.

Sollen in einem Java-Projekt Dateien aus einem Verzeichnis oder einem externen Java-Archiv geholt werden, so ist der übliche Weg, diese Verzeichnisse oder Archive in einem Klassenpfad anzugeben. Diese Angabe ist für alle SDK-Werkzeuge notwendig – am Häufigsten ist sie beim Compiler und bei der Laufzeitumgebung zu sehen.

Schalter -classpath

Die Suchorte lassen sich flexibel angeben, wobei die erste Variante einem SDK-Werkzeug über den Schalter -classpath (kurz -cp) die Klassendateien bzw. Archive liefert:

$ java -classpath classpath1;classpath2 MainClass

Der Klassenpfad enthält Wurzelverzeichnisse der Pakete und JAR-Dateien, also Archive von Klassendateien und Ressourcen.

Beispiel: Nimm ein Java-Archiv library.jar im aktuellen Verzeichnis, die Ressourcen unter dem bin-Verzeichnis und alle JAR-Dateien im Verzeichnis lib in den Klassenpfad mit auf:

$ java -cp "library.jar;bin/.;lib/*" com.tutego.MainClass

Unter Windows ist der Trenner ein Semikolon, unter Unix ein Doppelpunkt! Das Sternchen steht für alle JAR-Dateien, es ist kein üblicher Wildcard, wie z.B. parser*.jar.[1] Sehen Kommandozeilen der Betriebssysteme ein *, beginnen sie in der Regel eine eigenen Verarbeitung; daher muss die gesamte Pfadangabe in doppelten Anführungszeichen stehen.

Umgebungsvariable CLASSPATH

Eine Alternative zum Schalter -cp ist das Setzen der Umgebungsvariablen CLASSPATH mit einer Zeichenfolge, die Pfadangaben spezifiziert:

$ SET CLASSPATH=classpath1;classpath2
$ java MainClass

Problematisch ist der globale Charakter der Variablen, sodass lokale -cp-Angaben besser sind. Außerdem „überschreiben“ die -cp-Optionen die Einträge in CLASSPATH. Zu guter Letzt: ist weder CLASSPATH noch eine -cp-Option gesetzt, besteht der Klassenpfad für die JVM nur aus dem aktuellen Verzeichnis, also „.“.

[1]      Weitere Details unter https://docs.oracle.com/javase/8/docs/technotes/tools/windows/classpath.html.

Klassenlader (Class Loader) und Klassenpfad

Ein Klassenlader ist dafür verantwortlich, eine Klasse zu laden. Aus der Datenquelle (im Allgemeinen einer Datei) liefert der Klassenlader ein Byte-Array mit den Informationen, die im zweiten Schritt dazu verwendet werden, die Klasse ins Laufzeitsystem einzubringen; das ist Linking. Es gibt vordefinierte Klassenlader und die Möglichkeit, eigene Klassenlader zu schreiben, um etwa verschlüsselte und komprimierte .class-Dateien aus Datenbanken zu laden.

Klassenladen auf Abruf

Nehmen wir zu Beginn ein einfaches Programm mit zwei Klassen:

class Person {
   static String NOW = java.time.LocalDateTime.now().toString();
   public static void main( String[] args ) {
     Dog wuffi = new Dog();
   }
 }
 
 class Dog {
   Person master;
 }

Wenn die Laufzeitumgebung das Programm Person startet, muss sie eine Reihe von Klassen laden. Das tut sie dynamisch zur Laufzeit. Sofort wird klar, dass es zumindest Person sein muss. Wenn aber die statische main(String[])-Methode aufgerufen wird, muss auch Dog geladen sein. Und da beim Laden einer Klasse auch die statischen Variablen initialisiert werden, wird auch die Klasse LocalDateTime geladen.

Zwei weitere Dinge werden nach einiger Überlegung deutlich:

  • Wenn Dog geladen wird, bezieht es sich auf Person. Da Person aber schon geladen ist, muss es nicht noch einmal geladen werden.
  • Unsichtbar stecken noch andere referenzierte Klassen dahinter, die nicht direkt sichtbar sind. So wird zum Beispiel Object geladen, da implizit in der Klassendeklaration von Person steht: class Person extends Object. Auch String muss geladen werden, weil String einmal in der Signatur von main(String[]) vorkommt und es der Typ von now Intern ziehen die Typen viele weitere Typen nach sich. String implementiert Serializable, CharSequence und Comparable, also müssen diese drei Schnittstellen auch geladen werden. Und so geht das weiter, je nach dem, welche Programmpfade abgelaufen werden. Wichtig ist aber zu verstehen, dass diese Klassendateien so spät wie möglich geladen werden.

Im Beispiel mit den Klassen Person und Dog lädt die Laufzeitumgebung selbstständig die Klassen (implizites Klassenladen). Klassen lassen sich auch mit Class.forName(String) über ihren Namen laden (explizites Klassenladen).

Hinweis. Um zu sehen, welche Klassen überhaupt geladen werden, lässt sich der virtuellen Maschine beim Start der Laufzeitumgebung ein Schalter mitgeben -verbose:class. Dann gibt die Maschine beim Lauf alle Klassen aus, die sie lädt.

JAR-Dateien

Große Sammlungen von Java-Klassendatein und Ressourcen werden in sogenannten Java-Archiven, kurz JAR-Dateien, zusammengefasst. Diese Dateien sind im Grunde ganz normale ZIP-Archive mit einem besonderen Verzeichnis META-INF für Meta-Dateien. Das JDK bringt im bin-Verzeichnis das Werkzeug jar zum Aufbau und Extrahieren von JAR-Dateien mit.

JAR-Dateien behandelt die Laufzeitumgebung wie Verzeichnisse von Klassendateien und Ressourcen. Wenn Java-Software ausgeliefert wird, dann bieten sich JAR-Dateien an, denn es ist einfacher, nur ein komprimiertes Archiv weiterzugehen als einen großen Dateibaum. Zudem haben Java-Archive den Vorteil, dass sie signiert werden können und illegale Änderungen auffallen.

Woher die kleinen Klassen kommen: Die Suchorte und spezielle Klassenlader

Die Laufzeitumgebung nutzt zum Laden nicht nur einen Klassenlader, sondern mehrere. Die Java-Laufzeitumgebung nutzt diese verschiedenen Klassenlader um unterschiedliche Orte festzulegen. Ein festes Schema bestimmt die Suche nach den Klassen:

  1. Klassen Typen wie String, Object oder Point stehen in einem ganz speziellen Archiv. Wenn ein eigenes Java-Programm gestartet wird, so sucht die virtuelle Maschine die angeforderten Klassen zuerst in diesem Archiv. Da es elementare Klassen sind, die zum Hochfahren eines Systems gehören, werden sie Bootstrap-Klassen Das Archiv mit diesen Klassen heißt oft rt.jar (für Runtime). Andere Archive können hinzukommen – wie i18n.jar, das Internationalisierungsdaten beinhaltet. Die Implementierung dieses Bootstrap-Klassenlader ist nicht öffentlich und wird von System zu System unterschiedlich sein. Ab Java 9 wird sich das grundlegend ändern.
  2. Ist eine Klasse keine Bootstrap-Klasse, beginnt der System-Klassenlader Applikations-Klassenlader die Suche im Klassenpfad (Classpath). Diese Pfadangabe besteht aus einer Aufzählung einzelner Klassendateien, Verzeichnisse, Klassen oder JAR-Archive, in denen die Laufzeitumgebung nach den Klassendateien sucht. Standardmäßig ist dieser Klassenpfad auf das aktuelle Verzeichnis gesetzt („.“), er lässt sich aber beliebig setzen.

Zusätzliche statische Initialisier im enum

Es sind Blöcke der Art static { … } im Rumpf eines Aufzählungstyps erlaubt. Lädt die Laufzeitumgebung einer Klasse, initialisiert sie der Reihe nach alle statischen Variablen bzw. führt die static-Blöcke aus. Die Aufzählungen sind statische Variablen und werden beim Laden initialisiert. Steht der statische Initialisierer hinter den Konstanten, so wird auch er später aufgerufen als die Konstuktoren, die vielleicht auf statische Variablen zurückgreifen wollen, die der static-Block initialisiert. Ein Beispiel:

public enum Country {
  GERMANY, UK, CHINA;
  {
    System.out.println( "Objektinitialisierer" );
  }
  static {
    System.out.println( "Klasseninitialisier" );
  }

  private Country() {
    System.out.println( "Konstruktor" );
  }

  public static void main( String[] args ) {
    System.out.println( GERMANY );
  }
}

Die Ausgabe ist:

Objektinitialisierer

Konstruktor

Objektinitialisierer

Konstruktor

Objektinitialisierer

Konstruktor

Klasseninitialisier

GERMANY

Die Ausführung und Ausgabe hängt von der Reihenfolge der Deklaration ab und jede Umsortierung führt zu einer Verhaltensänderung. Jetzt könnten Programmierer auf die Idee kommen, mögliche static-Blöcke an den Anfang zu setzen, vor die Konstanten. Meine Leser sollten das Ergebnis testen …

Inselupdate: Suchen und Ersetzen mit Mustern

Für den Suchfall mit Java RegEx gibt es noch eine Erweiterung, dass nämlich die Pattern-Klasse die Fundstellen nicht nur ermittelt, sondern sie auch durch etwas anderes ersetzen kann.

Beispiel: In einem String sollen alle Nicht-JVM-Sprachen ausgepiept werden:

String  text    = „Ich mag Java, Groovy und ObjectiveC und PHP.“;
Matcher matcher = Pattern.compile(„ObjectiveC|PHP“ ).matcher( text );
StringBuffer sb = new StringBuffer();
while ( matcher.find() )
matcher.appendReplacement( sb, „[PIEP]“ );
matcher.appendTail( sb );
System.out.println( sb );  // Ich mag Java, Groovy und [PIEP] und [PIEP].

Um mit dem Mechanismus „Suchen und Ersetzen“ zu arbeiten, wird zunächst ein Container vom Typ StringBuffer aufgebaut, denn in dem echten String kann Pattern die Fundstellen nicht ersetzen. (Leider ist ein StringBuffer nötig, die API akzeptiert keinen StringBuilder.) Erkennt der Matcher ein Muster, macht appendReplacement(…) zwei Dinge:

1. Die Methode füllt den Container mit allen Zeichen vom letzten Fund bis zur jetzigen Fundstelle auf. Beim ersten Aufruf ist das „Ich mag Java, Groovy und “, dann folgt „ und “.

2. In den StringBuffer können wir unsere Ersetzung, in diesem Falle „[PIEP]“, setzen.

So wächst der StringBuffer von Schritt zu Schritt. Nach der letzten Fundstelle setzt appendTail(…) das noch verbleibende Teilstück von der letzten Funstelle bis zum Stringende in den StringBuffer-Container.

Im Prinzip können wir in der while-Schleife mit matcher.group(…) auf das Fundstück zurückgreifen und es in die Ersetzung einbauen. Doch toll an appendReplacement(…) ist, dass der Ersetzungsstring ein $ enthalten darf – mit dem Problem, dass ein vorkommendes Dollar-Zeichen ausmaskiert werden muss –, der Zugriff auf die Suchgruppe bietet. Damit lassen sich sehr elegante Lösungen bauen. Nehmen wir an, wir müssen in einer Zeichenkette alle URLs in HTML-Hyperlinks konvertieren. Dann rahmen wir einfach jede Fundstelle in die nötigen HTML-Tags ein. In Quellcode sieht das so aus:

Listing 2.7: RegExSearchAndReplace.java, main()

String  text    = „Hi, schau mal bei http://stackoverflow.com/ “ +
                  „oder http://www.tutego.de/ vorbei.“;
String  regex   = „http://[a-zA-Z0-9\\\]+\\.[a-zA-Z]{2,3}(\\S*)?“;
Matcher matcher = Pattern.compile( regex ).matcher( text );
StringBuffer sb = new StringBuffer( text.length() );
while ( matcher.find() )
  matcher.appendReplacement( sb, „<a href=\“$0\“>$0</a>“ );
matcher.appendTail( sb );
System.out.println( sb );

Der StringBuffer enthält dann zum Schluss „Hi, schau mal bei <a href=“http://stackoverflow.com/“>http://stackoverflow.com/</a> oder <a href=“http://www.tutego.de/“>http://www.tutego.de/</a> vorbei.“. (Der gewählte reguläre Ausdruck für URLs ist kurz, aber nicht vollständig. Für das Beispiel spielt das aber keine Rolle.)

Tipp: Die String-Methoden replaceAll(…) und replaceFirst(…) ersetzen direkt, und arbeiten im Hintergrund genauso. Zum Einsatz kommt die replaceAll(.,.)-Methode vom Matcher.

Hinweis: Der Ersetzungsausdruck „<a href=\“$0\“>$0</a>“ enthält mit $ Steuerzeichen für den Matcher. Wenn die Ersetzung aber überhaupt nicht mit $n auf das gefundene Wort zurückgreift, sollten die beiden Sonderzeichen \ und $ ausmaskiert werden. Auf diese Weise werden merkwürdige Fehler vermieden, wenn doch in der Ersetzung ein Dollar-Zeichen oder ein Backslash vorkommt. Das Ausmaskieren übernimmt die Methode quoteReplacement(…), sodass sich zum Beispiel Folgendes ergibt:

matcher.appendReplacement( sb, Matcher.quoteReplacement( replacement ) );

Lokale Klassen und effektiv finale Variablen

Im folgenden Beispiel deklariert die main(…)-Methode eine innere Klasse Snowden mit einem Konstruktor, der auf die finale Variable PRISM zugreift:

public class NSA {

public static void main( String[] args ) {

final int PRISM = 1;

int tempora = 2;

tempora++; // (*)

class Snowden {

Snowden() {

System.out.println( PRISM );

// System.out.println( tempora ); // Auskommentiert ein Compilerfehler

}

}

new Snowden();

}

}

Die Deklaration der inneren Klasse Snowden wird hier wie eine Anweisung eingesetzt. Ein Sichtbarkeitsmodifizierer ist bei inneren lokalen Klassen ungültig, und die Klasse darf keine Klassenmethoden und allgemeinen statischen Variablen deklarieren (finale Konstanten schon).

Jede lokale Klasse kann auf Methoden der äußeren Klasse zugreifen und zusätzlich auf die lokalen Variablen und Parameter, die final sind. Das ist die Variable PRISM auf jeden Fall, tempora ist nicht final (tempora++ ist ein Schreibzugriff), und daher führt eine Konsolenausgabe mit println(tempora) auch zu einem Compilerfehler – Eclipse meldet: „Local variable tempora defined in an enclosing scope must be final or effectively final”. Der letzte Teil der Fehlermeldung gibt einen Hinweis auf eine kleine Änderung seit Java 8, dass Variablen nicht zwingend mit dem Modifizierer final ausgezeichnet werden müssen, um final zu sein. Gibt es keinen Schreibzugriff auf Variablen, sind sie effektiv final. Im Beispiel kann das einfach getestet werden: Wird die Zeile (*) mit tempora++; auskommentiert, so ist tempora effektiv final und Snowden kann auf tempora zugreifen.

Liegt die innere Klasse in einer statischen Methode, kann sie keine Objektmethoden der äußeren Klasse aufrufen.

Varargs-Design-Tipps

  • Hat eine Methode nur einen Array-Parameter, und steht der noch am Ende, so kann dieser relativ einfach durch ein Vararg ersetzt werden. Das gibt dem Aufrufer die komfortable Möglichkeit, eine kompaktere Syntax zu nutzen. Unsere main(String[] args)-Methode kann auch als main(String… args) deklariert werden, sodass der main(…)-Methode bei Tests einfach variable Argumente übergeben werden können.
  • Muss eine Mindestanzahl von Argumenten garantiert werden – bei max(…) sollten das mindestens zwei sein –, ist es besser, eine Deklaration wie folgt zu nutzen: max(int first, int second, int… remaining).
  • Aus Performance-Gründen ist es nicht schlecht, Methoden mit häufigen Parameterlistengrößen als feste Methoden anzubieten, etwa max(double, double), max(double, double, double) und dann max(double…). Der Compiler wählt automatisch immer die passende Methode aus und für zwei oder drei Parameter sind keine temporären Feld-Objekte nötig und die automatische Speicherbereinigung muss nichts wegräumen.