Alle Beiträge von Christian Ullenboom

Über Christian Ullenboom

Ich bin Christian Ullenboom und Autor der Bücher ›Java ist auch eine Insel. Einführung, Ausbildung, Praxis‹ und ›Java SE 8 Standard-Bibliothek. Das Handbuch für Java-Entwickler‹. Seit 1997 berate ich Unternehmen im Einsatz von Java. Sun ernannte mich 2005 zum ›Java-Champion‹.

Datumsklasse java.time.LocalDate

Ein Datum (ohne Zeitzone) repräsentiert die Klasse LocalDate. Damit lässt sich zum Beispiel ein Geburtsdatum repräsentieren.

Ein temporales Objekt kann über die statischen of(…)-Fabrikmethoden aufgebaut, über ofInstant(Instant instant, ZoneId zone) oder von einem anderen temporalen Objekt abgeleitet werden. Interessant sind die Methoden, die mit einem TemporalAdjuster arbeiten.

Beispiel

 LocalDate today = LocalDate.now();
 LocalDate nextMonday = today.with( TemporalAdjusters.next( DayOfWeek.SATURDAY ) );
 System.out.printf( "Heute ist der %s, und frei ist am Samstag, den %s",
                    today, nextMonday );

Mit den Objekten in der Hand können wir diverse Getter nutzen und einzelne Felder erfragen, etwa getDayOfMonth(), getDayOfYear() (liefern int) oder getDayOfWeek(), das eine Aufzählung vom Typ DayOfWeek liefert, und getMonth(), das eine Aufzählung vom Typ Month Weiterhin gibt es long toEpochDay() und und in Java 9 long toEpochSecond(LocalTime time, ZoneOffset offset).

Dazu kommen Methoden, die mit minusXXX(…) oder plusXXX(…) neue LocalDate-Objekte liefern, wenn zum Beispiel mit minusYear(long yearsToSubtract) eine Anzahl Jahre zurückgelaufen werden soll. Durch die Negation des Vorzeichens kann auch die jeweils entgegengesetzte Methode genutzt werden, sprich LocalDate.now().minusMonths(1) kommt zum gleichen Ergebnis wie LocalDate.now().plusMonths(-1). Die withXXX(…)-Methoden belegen ein Feld neu und liefern ein modifiziertes neues LocalDate-Objekt.

Von einem LocaleDate lassen sich andere temporale Objekte bilden, atTime(…) etwa liefert LocalDateTime-Objekte, bei denen gewisse Zeit-Felder belegt sind. atTime(int hour, int minute) ist so ein Beispiel. Mit until(…) lässt sich eine Zeitdauer vom Typ Period liefern. Interessant sind zwei neue Java 9-Methoden, die einen Strom von LocalDate-Objekten bis zu einem Endpunkt liefern.

  • Stream<LocalDate> datesUntil(LocalDate endExclusive)
  • Stream<LocalDate> datesUntil(LocalDate endExclusive, Period step)

java.time.Duration

Eine Klasse Duration repräsentiert Dauern von Zeiten und ist weder mit Zeitzonen verbunden noch mit anderen Zeitleisten. Daher ist auch ein Tag idealisiert exakt 24 Stunden lang, Schaltsekunden kennt die Klasse nicht, und einen Tag zu addieren heißt, 24 Stunden aufzurechnen.

Die interne Berechnungseinheit ist Sekunden bzw. Nanosekunden, auch wenn Hilfsmethoden Zeiteinheiten bis Stunden erlauben. Darüber wird eine Duration auch aufgebaut, über ofXXX()-Methoden wie ofSeconds(long seconds, long nanoAdjustment) oder ofDays(long days). Differenzen bildet wieder Duration between(Temporal startInclusive, Temporal endExclusive), wobei Temporal eine Schnittstelle ist, die etwa von LocalDate, LocalDateTime, LocalTime, OffsetDateTime, OffsetTime, ZonedDateTime, Year, YearMonth, Instant implementiert wird, nicht aber von Period.

Beispiel: Wie viel Zeit vergeht zwischen der Ausführung?

Instant start = Instant.now();
 try {
   Files.walk( Paths.get( System.getProperty( "user.home" ) ) ).count();
 } catch ( Exception e ) { }
 Instant end = Instant.now();
 System.out.println( Duration.between( start, end ).toMillis() + " ms" );

Abgewandelt wird eine Duration wieder über withXXX(…) oder die minusXXX(…)/plusXXX(…)-Methoden. Duration dividedBy(long divisor) teilt die Duration durch eine Zeit. Die neue Java 9-Methode long dividedBy(Duration divisor) sagt, wie oft der divisor in der Duration liegt.

toNanos(), toMillis(), toSeconds() – neu in Java 9 –, toMinutes(), toHours(), toDays() konvertieren in ein long, getNano() und getSeconds() liefern die zwei Bestandteile einer Duration als long. Wichtig ist der Unterschied: Die Getter liefern den Feldwert von Duration, während toXXX() immer konvertiert.

Beispiel

Duration aSecond = Duration.of( 1, ChronoUnit.MINUTES );
out.println( aSecond.getSeconds() ); // 60
out.println( aSecond.getNano() ); // 0
out.println( aSecond.toMinutes() ); // 1
out.println( aSecond.toSeconds() ); // 60
out.println( aSecond.toNanos() ); // 60000000000

In Java 9 kommen Methoden hinzu, die die Anteile an Minuten, Sekunden, usw. erfragen, und zwar long toDaysPart(), int toHoursPart(), int toMinutesPart(), int toSecondsPart(), int toMillisPart() und int toNanosPart().

UML-Diagramme mit Eclipse Papyrus

Papyrus (https://www.eclipse.org/papyrus/) ist ein Modellierungswerkeug, mit dem UML-Diagramme gezeichnet und generiert werden können.

Installation

  1. Help -> Install New Software mit Papyrus selbst:
    http://download.eclipse.org/modeling/mdt/papyrus/updates/releases/neon
    – „Papyrus“ auswählen -> installieren
  2. Help -> Install New Software mit: http://download.eclipse.org/modeling/mdt/papyrus/components/designer
    – Papyrus Designer Category“ aufklappen
    – „Papyrus Java profile library and code generation (Incubation)“ auswählen -> installieren

Weitere Schritte

  1. New -> Other… -> Papyrus -> Papyrus Model erstellen
  2. Windows -> Show View -> Model Explorer / oder Create View im Model
  3.  Im Model Explorer Rechtsklick auf das RootElement: New Diagram -> Class Diagram
  4. Anschließend können einzelne Klassen oder Pakete per Drag & Drop reingezogen werden.

Basics

Alle Elemente die man reingezogen hat können markiert werden um folgende Operationen durchzuführen:

  1. Rechtsklick -> Filters -> Show/Hide Contents : Anzeigen/Ausblenden von Attributen und Operationen etc.
  2. Rechtsklick -> Filters -> Show/Hide Related Links: Anzeigen/Ausblenden der Klassenbeziehungen zueinander
  3. Rechtsklick -> Arrange All : Ordnet alle Elemente übersichtlich innerhalb der Model an
  4. Rechtsklick -> Filters -> Show/Hide Compartments: Anzeigen/Ausblenden der Abteile (für Attribute/Operationen/“nested classifiers“ etc.)

Rock Around the java.time.Clock

Die zeitbezogenen temporalen Typen bekommen ihre Zeit von einer Uhr. Die Uhr kombiniert die aktuelle Zeit mit einer Zeitzone; Java repräsentiert sie durch den Typ java.time.Clock. Der Klasse ist abstrakt und Exemplare sind über statische Fabrikmethoden zu bekommen:

Statische Clock-Mehoden Rückgabe
systemUTC() Uhr mit genauster Zeit in der Greenwich/UTC Zeitzone
systemDefaultZone() Uhr mit genauster Zeit in der aktuellen Systemzeitzone
tickMillis(ZoneId zone) Uhr mit Millisekundenauflösung ohne Nanosekundenanteil in der gegeben Zeitzone (ab Java 9)
tickSeconds(ZoneId zone) Uhr mit Sekundenauflösung ohne Milli- und Nanosekundenanteil in der gegeben Zeitzone
tickMinutes(ZoneId zone) Uhr mit Minutenauflösung ohne Sekunden-, Milli- und Nanosekundenanteil in der gegeben Zeitzone
tick(Clock baseClock, Duration tickDuration) Uhr, die auf der Basis von baseClock in Abständen von tickDuration „tickt“
offset(Clock baseClock, Duration tickDuration) Neue Uhr, die gegenüber der baseClock um tickDuration verschoben ist
fixed(Instant fixedInstant, ZoneId zone) Voreingestellte Uhrzeit, die sie nie ändert

Clock-Exemplare erfragen

Die now()-Methoden der Klassen LocalTime, LocalDate, … nutzen standardmäßig die Uhr in der aktuellen Zeitzone:

public static LocalDate now() {

  return now(Clock.systemDefaultZone());

}

Abzulesen am Beispiel ist, dass die now(…)-Methode überladen ist, sodass ein Programm selbst eine Clock angeben kann. Das ist nützlich zum Testen, wenn zum Beispiel die Zeit immer die gleiche sein soll.

Unixzeit, System.currentTimeMillis(), der 1.1.1970

Der 1.1.1970

Der 1.1.1970 war ein Donnerstag mit wegweisenden Änderungen. In der katholischen Kirche wurde der Allgemeine Römische Kalender eingeführt,[1] und die Briten freuten sich, dass die Volljährigkeit von 24 Jahren auf 18 Jahre fiel. Zu etwas technischem: Der 1.1.1970 heißt auch Unix Epocheund eine Unixzeit wird relativ zu diesem Zeitpunkt in Sekunden beschrieben. So kommen wir 100.000.000 Sekunden nach dem 1.1.1970 beim 3. März 1973 um 09:46:40 aus. Das Unix Billennium wurde bei 1.000.000.000 Sekunden nach dem 1.1.1970 gefeiert, und repräsentiert den 9. September 2001, 01:46:40.

System.currentTimeMillis()

Auch für uns Java-Entwickler ist die Unixzeit von Bedeutung, denn viele Zeiten in Java sind relativ zu diesem Datum. Der Zeitstempel 0 bezieht sich auf den 1.1.1970 0:00:00 Uhr Greenwich-Zeit – das entspricht 1 Uhr nachts deutscher Zeit. Die Methode currentTimeMillis() liefert die vergangenen Millisekunden – nicht Sekunden! – relativ zum 1.1.1970, wobei allerdings die Uhr des Betriebssystems nicht so genau gehen muss. Die Anzahl der Millisekunden wird in einem long repräsentiert, also in 64 Bit. Das reicht für etwa 300 Millionen Jahre.

Warnung: Die Rückgaben von System.currentTimeMillis() sind nicht montoton steigend, weil die Systemuhr umgestellt werden kann, etwa durch eine Zeitkorrektur. Zeitmessungen dürfen daher nie als Differenz von zwei currentTimeMillis() berechnet werden. Eine vernünftge Alternative ist System.nanoTime().

[1]  Cäsar und der julianische Kalender liegen noch weiter zurück.

Socket-Optionen erfragen

Socket und ServerSocket unterstützen Optionen wie zum Beispiel einen Type of Service oder TCP-NoDelay, wobei diese von System zu System unterschiedlich sein können. In Java 9 lassen sich die Optionen einfach abfragen.

Beispiel: Gib alle Optionen aus:

Socket socket = new Socket( "localhost", 80 );

System.out.println( socket.supportedOptions().stream()

                          .map( o -> o.name() + " is " + o.type().getSimpleName() )

                          .collect( Collectors.joining( ", " ) ) );

// SO_LINGER is Integer, IP_TOS is Integer, SO_KEEPALIVE is Boolean, TCP_NODELAY is Boolean, SO_SNDBUF is Integer, SO_REUSEADDR is Boolean, SO_RCVBUF is Integer




ServerSocket serversocket = new ServerSocket( 8080 );

System.out.println( serversocket.supportedOptions() );

// [SO_RCVBUF, SO_REUSEADDR, IP_TOS]

Die supportedOptions()-Methoden liefern eine Set<SocketOption>. Eine SocketOption deklariert name() und type().

Mit yield() und onSpinWait() auf Rechenzeit verzichten

Im besten Fall signalisiert ein Ereignis, dass Daten bereit stehen und abgeholt werden können. Doch nicht immer gibt es eine solche API. Dann findet sich oft ein wiederholter Test auf eine Bedingung und wird diese wahr, stehen zum Beispiel Daten zum Abholen bereit. In Code sieht das so aus:

while ( ! daten_da )

  ;

datenVearbeiten();

Dieses aktive Warten, engl. busy waiting, oder auch spinning genannt, auf Daten (eng. polling) mithilfe einer spin-loop verschwendet so programmiert viel Rechenzeit. Eine Lösung ist mit dem bekannten sleep(…) eine Zeit von Millisekunden zu schlafen, wobei es schwierig ist, die richtige Anzahl an Schlafsekunden zu ermitteln. Zu kurz geschlafen heißt: noch einmal weiter pollen; zu lang geschlafen, heißt: es gibt eine unnötige Verzögerung.

Die Thread-Klasse bietet zwei weitere Methoden, um kooperative Threads zu programmieren: die Methode yield() und ab Java 9 onSpinWait(). Anders als bei sleep(…) gibt es hier keine Millisekunden anzugeben.

while ( ! daten_da )

  Thread.onSpinWait();

datenVearbeiten();

class java.lang.Thread
implements Runnable

  • static void onSpinWait()
    Signalisiert der Laufzeitumgebung. das der Thread in einer spin-loop auf Daten wartet. Dieser Hinweis kann die JVM an den Prozessor weitegeben und die Laufzeit ist bei typischen Systemen besser als mit yield(). Neu in Java 9 im Zuge der Umsetzung der „JEP 285: Spin-Wait Hints“.[1]
  • static voidyield()
    Der laufende Thread gibt freiwillig seine Rechenzeit ab, sodass er bezüglich seiner Priorität wieder in die Thread-Warteschlange des Systems einordnet wird. Einfach ausgedrückt, signalisiert yield() der Thread-Verwaltung: „Ich setze diese Runde aus und mache weiter, wenn ich das nächste Mal dran bin.“ Die Methode ist für Implementierungen der JVM nicht verbindlich. Die API-Dokumentation warnt eher von der Methode „It is rarely appropriate to use this method.”

[1]       http://openjdk.java.net/jeps/285

System-Logging in Java 9

Logging von Bibliotheken und eigener Software ist eine Sache, eine andere ist, dass die Java SE Bibliotheken und die JVM z. B. bei –verbose:gc selbst auch loggen. Bis vor Java 9 war nicht standardisiert wohin die Ausgaben gehen und konnten nicht problemlos über einen benutzerinstallierten Logger geschrieben werden. Java 9 setzt die „JEP 264: Platform Logging API and Service“ um und damit besorgt sich die Java SE-Implementierung über System.getLogger(…) einen System.Logger und schreibt über ihn Informationen in einem gewünschten Log-Level.

Wohin dieser System-Logger schreibt lässt sich konfigurieren, sodass zum Beispiel Log4j 2 statt dem Standard-Logger aus dem java.util.logging-Paket genommen wird. Das geschieht über die java.util.ServiceLoader-API; sie lädt eine Klasse, die LoggerFinder erweitert und abstrakte Methode public Logger getLogger(String name, Module module) so überschreibt, das eine Implementierung von java.lang.System.Logger zurückgegeben wird.

Multiplizieren von long-Ganzzahlen

Es gibt keinen primitiven Datentyp der mehr als 64 Bit (8 Byte) hat, sodass das Ergebnis von long * long mit seinen 128 Bit nur in ein BigInteger komplett passt. Allerdings erlaubt eine neue Methode aus Java 9, die oberen 64 Bit einer long-Multiplikation getrennt zu erfragen, und zwar mit der Methode multiplyHigh(long x, long y) in Math und StrictMath – wobei StrictMath nur auf Math leitet.

BigInteger v = BigInteger.valueOf( Long.MAX_VALUE )

                         .multiply( BigInteger.valueOf( Long.MAX_VALUE ) );

System.out.println( v );  // 85070591730234615847396907784232501249




long lowLong = Long.MAX_VALUE * Long.MAX_VALUE;

long highLong = Math.multiplyHigh( Long.MAX_VALUE, Long.MAX_VALUE );

BigInteger w = BigInteger.valueOf( highLong )

                         .shiftLeft( 64 )

                         .add( BigInteger.valueOf( lowLong ) );

System.out.println( w );  // 85070591730234615847396907784232501249

Multiplizieren von int-Ganzzahlen

Der *-Operator führt bei int keine Anpassung an den Datentypen durch, sodass die Multiplikation von zwei ints wiederum int liefert. Doch das Produkt kann schnell aus dem Wertebereich laufen, sodass es zum Überlauf kommt. Selbst wenn das Produkt in eine long-Variable geschrieben wird, erfolgt die Konvertierung von int in long erst nach der Multiplikation:

int  i = Integer.MAX_VALUE * Integer.MAX_VALUE;

long l = Integer.MAX_VALUE * Integer.MAX_VALUE;

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

System.out.println( l );     // 1

Sollen zwei ints ohne Überlauf multipliziert werden, ist einer der beiden Faktoren auf long anzupassen, damit es zum korrekten Ergebnis 4611686014132420609 führt.

System.out.println( Integer.MAX_VALUE * (long) Integer.MAX_VALUE );

System.out.println( (long) Integer.MAX_VALUE * Integer.MAX_VALUE );

Da diese Typanpassung schnell vergessen werden kann und nicht besonders explizit ist, bieten die Klassen Math und StrictMath die statische Methode long multiplyFull(int x, int y), die für uns über (long)x * (long)y die Typumwandlung vornehmen.

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.

Rundungsmodus BigDecimal

Eine Besonderheit stellt die Methode divide(…) dar, die zusätzlich einen Rundungsmodus und optional auch eine Anzahl gültiger Nachkommastellen bekommen kann. Zunächst ohne Rundungsmodus:

BigDecimal a = new BigDecimal( "10" );
BigDecimal b = new BigDecimal( "2" );
System.out.println( a.divide(b) );   // 5

Es ist kein Problem, wenn das Ergebnis eine Ganzzahl oder das Ergebnis exakt ist:

System.out.println( BigDecimal.ONE.divide(b) );  // 0.5

Wenn das Ergebnis aber nicht exakt ist, lässt sich divide(…) nicht einsetzen. Die Anweisung new BigDecimal(1).divide( new BigDecimal(3) ) ergibt den Laufzeitfehler: „java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.“

An dieser Stelle kommen diverse Rundungsmodi ins Spiel, die bestimmen, wie die letzte Ziffer einer Rundung bestimmt werden soll. Sie lassen sich über eine Aufzählung java.math.RoundingMode übermitteln. Folgende Konstanten gibt es:

Konstante in RoundingMode Bedeutung
DOWN Runden nach 0
UP Runden weg von 0
FLOOR Runden nach negativ unendlich
CEILING Runden nach positiv unendlich
HALF_DOWN Runden zum nächsten Nachbarn und weg von der 0, wenn beide Nachbarn gleich weit weg sind
HALF_UP Runden zum nächsten Nachbarn und hin zur 0, wenn beide Nachbarn gleich weit weg sind
HALF_EVEN Runden zum nächsten Nachbarn und zum geraden Nachbarn, wenn beide Nachbarn gleich weit weg sind
UNNECESSARY Kein Runden, Operation muss exakt sein

 

ROUND_UNNECESSARY darf nur dann verwendet werden, wenn die Division exakt ist, sonst gibt es eine ArithmeticException.

BigDecimal one   = BigDecimal.ONE;

BigDecimal three = new BigDecimal( "3" );

System.out.println( one.divide( three, RoundingMode.UP ) );      // 1

System.out.println( one.divide( three, RoundingMode.DOWN ) );    // 0

Jetzt kann noch die Anzahl der Nachkommastellen bestimmt werden:

System.out.println( one.divide( three, 6, RoundingMode.UP ) );   // 0.333334

System.out.println( one.divide( three, 6, RoundingMode.DOWN ) ); // 0.333333

Beispiel

BigDecimal bietet die praktische Methode setScale(…) an, mit der sich die Anzahl der Nachkommastellen setzen lässt. Das ist zum Runden sehr gut. In unserem Beispiel sollen 45 Liter Benzin zu 1,399 bezahlt werden:

BigDecimal petrol = new BigDecimal( "1.399" ).multiply( new BigDecimal(45) );
 System.out.println( petrol.setScale( 3, BigDecimal.ROUND_HALF_UP ) );
 System.out.println( petrol.setScale( 2, BigDecimal.ROUND_HALF_UP ) );

Die Ausgaben sind 62955 und 6296.

class java.math.BigDecimal
extends Number
implements Comparable<BigDecimal>

  • BigDecimal divide(BigDecimal divisor,RoundingMode roundingMode)
  • BigDecimal divide(BigDecimal divisor, int scale, RoundingMode roundingMode)
  • BigDecimal setScale(int newScale,RoundingMode roundingMode)

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.

Google Guava steigt (endlich) auf Java 8 um

Die News unter https://github.com/google/guava/wiki/Release21:

Significant API additions and changes

common.base

  • Function, Predicate and Supplier: changed to extend the new java.util.functioninterfaces with the same names.
  • Optional: added toJavaUtil and fromJavaUtil methods for easy conversion between Guava’s Optional and java.util.Optional.
  • Objects: removed deprecated firstNonNull and toStringHelper methods (both found on MoreObjects since Guava 18.0).

common.cache

New default methods on ConcurrentMap that were added in Java 8 are now implemented and safe to use for Cache.asMap() views.

common.collect

Many APIs in collect now have better implementations of many of the default methods added to Collection and Map types in Java 8.

New classes

  • Comparators: With the addition of many useful methods to the Comparator type in Java 8, Ordering now provides little benefit. Comparators is a new location for methods on Orderingthat still don’t have a good equivalent in the JDK.
  • Streams: Utility class for working with java.util.stream.Stream. Includes methods for creating streams (such as stream(Iterable), stream(Optional) and concat(Stream...)) and methods that do things with streams (such as findLast(Stream)).
  • MoreCollectors: Factories for java.util.stream.Collector objects; note that Collectors for Guava’s collection types are generally found on those types themselves rather than here.
  • Interners.InternerBuilder: Builder for Interner instances, with options similar to those found on MapMaker. Created with Interners.newBuilder().

Removed classes

  • MapConstraint and MapConstraints: deprecated since 19.0.

Changes

  • FluentIterable: added stream() method.
  • ForwardingBlockingDeque: deprecated; moved to util.concurrent.
  • Immutable* types: added methods to all named toImmutable[Type]() (e.g. ImmutableList.toImmutableList()) which return a Collector for collecting a Stream into an immutable collection/map object. As with most methods that create Collectors, these are generally intended to be used as static imports.
  • Multimap: added forEach(BiConsumer) method.
  • Multimaps: added toMultimap and flatteningToMultimap methods returning Collectorobjects that collect to a Multimap.
  • Multiset: added forEachEntry(ObjIntConsumer) method.
  • Maps: added toImmutableEnumMap methods returning Collector objects that collect to an ImmutableMap with enum keys.
  • Sets: added toImmutableEnumSet method returning a Collector that collects to an ImmutableSet of enums.
  • Tables: added toTable methods returning Collector objects that collect to a Table.
  • RangeSet: added default addAll(Iterable<Range>), removeAll(Iterable<Range>) and enclosesAll(Iterable<Range>) methods.
  • ImmutableRangeSet: added copyOf(Iterable<Range>), unionOf(Iterable<Range>), union(RangeSet), intersection(RangeSet) and difference(RangeSet) methods.
  • TreeRangeSet: added create(Iterable<Range>) method.
  • A number of rarely-used methods on concrete implementations of Guava collection types that aren’t present on the interface they implement have been deprecated. These include: ArrayListMultimap.trimToSize(), TreeMultimap.keyComparator(), and TreeBasedTable.row/columnComparator().

common.io

  • MoreFiles: New class which contains methods similar to those in Files, but for use with java.nio.file.Path objects.
  • This includes deleteRecursively and deleteDirectoryContents methods which are secure against race conditions that Java previously had no way of dealing with provided that the FileSystem supports SecureDirectoryStream (modern Linux versions do; Windows [NTFS at least] does not). For security, these will throw an exception if SecureDirectoryStream is not supported unless RecursiveDeleteOption.ALLOW_INSECURE is passed when calling the method.

common.primitives

  • Most classes: added constrainToRange([type] value, [type] min, [type] max) methods which constrain the given value to the closed range defined by the min and max values. They return the value itself if it’s within the range, the min if it’s below the range and the max if it’s above the range.

common.util.concurrent

  • ForwardingBlockingDeque: added; moved from common.collect because BlockingDeque is a concurrent type rather than a standard collection (it’s defined in java.util.concurrent).
  • AtomicLongMap: added a number of methods such as accumulateAndGet(K, LongBinaryOperator) that take advantage of new Java functional types.
  • Monitor: added newGuard(BooleanSupplier).
  • MoreExecutors: removed sameThreadExecutor(); deprecated since 18.0 in favor of directExecutor() (preferable) or newDirectExecutorService().