Kommandozeilenprogramme jrunscript und jjs

Java bringt zwei Werkzeuge im bin-Verzeichnis zum Ausführen von Skripte mit:

· jrunscript: Führt ein Skript mit einer JSR-223-kompatiblen Skript-Einige aus. Standardmäßig ist das JavaScript, weil keine anderen Skript-Engine vorinstalliert ist. Der Schalter -I bestimmt alternative Skript-Sprachen. Zur weiteren Dokumentation der Optionen siehe http://download.java.net/jdk8/docs/technotes/tools/windows/jrunscript.html.

· jjs: Führt immer JavaScript-Programme mit Nashorn aus. Siehe auch http://download.java.net/jdk8/docs/technotes/tools/windows/jjs.html für weitere Optionen. Interessant für JavaFX-Anwendungen ist der Schalter –fx, um verkürzte JavaFX-Anwendungen in JavaScript schreiben zu können.

Während also jrunscript generisch für alle Skriptsprachen sind, ist jjs exklusiv für Nashorn und bietet Angabe eines Log-Level, die Möglichkeit den abstrakten Syntaxbaum anzuzeigen, und vieles mehr.

Hinweis: Ohne Argumente geben beide in einen interaktiven Modus:

$ jjs

jjs> print(„Hallo Nashhorn“)

Hallo Nashhorn

jjs> 12+3

15

jjs>

java.lang.reflect.Parameter in Java 8

Ein Parameter repräsentiert einen Parameter einer Methode oder eines Konstruktors. Zu den neuen Methoden zählen:

final class java.lang.reflect.Parameter

implements AnnotatedElement

§ String getName()

§ int getModifiers()

§ boolean isNamePresent()

§ boolean isImplicit()

§ boolean isSynthetic()

§ boolean isVarArgs()

§ Type getParameterizedType()

§ Executable getDeclaringExecutable()

§ Class<?> getType()

§ AnnotatedType getAnnotatedType()

Die in Java 8 eingeführte finale Klasse Parameter implementiert AnnotatedElement, da seit Java 8 auch Parametervariablen annotiert sein können; die Methoden sind in der oberen Aufzählung nicht noch einmal aufgezählt.

Um an einen Parameter zu gelangen nutzen wir getParameters() auf einem Executable, also konkret Constructor, Method.

abstract class java.lang.reflect.Executable<T>
extends AccessibleObject

implements Member, GenericDeclaration

§ public Parameter[] getParameters()

Falls etwas beim Erfragen schief geht, gibt es eine MalformedParametersException, eine ungeprüfte Ausnahme.

Beispiel:

Constructor<?>[] constructors = Point.class.getDeclaredConstructors();

for ( Constructor<?> constructor : constructors ) {

System.out.println( constructor );

for ( Parameter param : constructor.getParameters() )

System.out.printf( “ %s %s%n“, param.getType(), param.getName() );

}

Mit der Ausgabe (an der abzusehen ist, dass die Parameternamen für die JVM nicht bekannt sind):

public java.awt.Point(int,int)

int arg0

int arg1

public java.awt.Point(java.awt.Point)

class java.awt.Point arg0

public java.awt.Point()

Ausgaben mit Calendar-Methoden getDisplayName(…)

Die Calendar-Klasse liefert mit getDisplayName(int field, int style, Locale locale) einen String für einen Feldwert zurück. Der style ist eine Konstante und deklariert sind folgende Konstanten: SHORT_FORMAT (SHORT), SHORT_STANDALONE, LONG_FORMAT (LONG), LONG_STANDALONE, NARROW_FORMAT, NARROW_STANDALONE, einige neu seit Java 8. Eine weiter Methoden getDisplayNames(int field, int style, Locale locale) liefert ein Map<String,Integer> mit allen String-Repräsentationen für ein Feld:

Beispiel

Calendar cal = Calendar.getInstance();

System.out.println( cal.getDisplayName( Calendar.MONTH, Calendar.LONG_FORMAT, Locale.GERMANY ) ); // Februar

System.out.println( cal.getDisplayNames( Calendar.MONTH, Calendar.LONG_FORMAT, Locale.GERMANY ) ); // {Oktober=9, Mai=4, …

SplittableRandom in Java 8

Die Klasse SplittableRandom aus dem java.util-Paket ist neu in Java 8 und hat die Aufgabe Folgen guter Zufallszahlen zu liefern. (Auch wenn die Klasse SplittableRandom heißt, hat sie mit einem java.util.Spliterator nichts zu tun.) Während bei Random eher die einzelne Zufallszahl im Mittelpunkt steht, rückt SplittableRandom Folgen von Zufallszahlen in den Mittelpunkt, die insbesondere den Dieharder-Test[1] bestehen.

Die Methoden von SplittableRandom drehen sich daher auch um Ströme von Zufallszahlen, die als IntStream, LongStream und DoubleStream geliefert werden. Zudem gibt es auch die auf Random bekannten nextXXX()-Methoden und eine Methode split(), die ein neues SplittableRandom liefert, sodass zwei parallele Threads weiterhin unabhängig gute Zufallszahlen bekommen.


[1] http://www.phy.duke.edu/~rgb/General/dieharder.php

Inselraus: Applets

»Mode ist, was man selbst trägt. Altmodisch ist, was die anderen tragen.«– Oscar Wilde (1854–1900)

Applets sind kleine Java-Programme, die in einem Webbrowser ablaufen. Sie gehören zu den Java-Programmen der ersten Stunde. Referenziert eine Web-Seite ein Applet, so startet der Browser eine virtuelle Maschine und führt das Applet aus.

Applets in der Wiege von Java

Haben sich in den Anfangsjahren die Browser-Hersteller selbst darum gekümmert, die Applets in ihren Browsern um Laufen zu bringen, hat sich das heute gewandelt. Oracle liefert die Java Plug-In Technology (kurz Java Plug-In) aus, was die Browser bei der Ausführung von Applets auf den gleichen Stand bringt. Das hat den Vorteil, dass  die Java-Technologie auch zum Internet Explorer kommt, da ja Microsoft seine JVM nicht mehr aktualisiert. (Microsoft lieferte für Windows XP immerhin noch eine eigene JVM aus, obwohl sie auf dem Stand von Java 1.1 stehen blieb, aber XP ist ja auch nicht mehr Stand der Dinge.) Früher lief die virtuelle Maschine im Browser selbst, in neuen Java-Versionen läuft die Java-Anwendung in einem eigenen Prozess.

Applets heute

Obwohl Applets Java an die Spitze der Programmiersprachen brachten, sind sie heute nur noch selten zu sehen. Es gibt zwar Ausnahmen, wie den Routenplaner http://www.de.map24.com/ oder diverse Aktien-Chart-Analysen, doch im Allgemeinen sind Applets von öffentlichen Webseiten weitestgehend verschwunden. Der Grund, warum Java-Applets weniger attraktiv für den Konsumenten sind, liegt nicht darin, dass die clientseitige Darstellung und Logik unwichtig geworden ist, sondern vielmehr an anderen Gründen:

  • Mit HTML, CSS sowie JavaScript lassen sich heutzutage viele Aufgaben lösen, die 1995 unlösbar waren. Dagegen wirken compilierte Java-Programme nicht gerade wie Raketentechnik. Während bei Java-Applets erst eine JVM gestartet werden muss, was natürlich eine gewisse Zeit kostet, sind JavaScript und HTML sofort bereit. Starke JavaScript-Bibliotheken ermöglichen tolle Effekte und mit performanten JavaScript-Engines wie der quelloffenen V8 von Google oder TraceMonkey der Mozilla Foundation eine schnelle Verarbeitung.
  • Java ist als allgemeine Programmiersprache entworfen worden, aber nicht als einfache Programmiersprache für grafische Effekte. Hier liegt der Vorteil von Adobe Flash oder auch Microsoft Silverlight. Mit starken Tools können Designer großartige Oberflächen entwerfen, und die Verbreitung des Flash-Players ist phänomenal.[1] Zudem erweitert Adobe die Multimedia-Technologie Flash, die Programmiersprache ActionScript sowie die Produktpalette zur Entwicklung kontinuierlich. Oracle versucht mit der Plattform JavaFX (http://www.oracle.com/technetwork/java/javafx/overview/index.html) dagegenzusetzen, aber JavaFX kann wohl eher Swing für Rich-Clients ablösen und wird wohl im Internet ein Exot bleiben.
  • Ist Java installiert, steht auf den Rechnern eine moderne und schnelle Java-Laufzeitumgebung für Applets über ein Browser-Plugin zur Verfügung. Das Problem beginnt aber, wenn Anwender Applets nutzen möchten (oder müssen), aber kein JVM installiert ist. HTML, CSS und JavaScript sind immer Teil des Browsers, und in der Regel ist auch Flash installiert. Dagegen ist der Bezug einer JVM langwierig, und viele Megabyte Daten müssen vom Oracle-Server geladen werden. (Microsoft lieferte zwar früher für den IE eine JVM mit, doch wegen immer wieder auftretender Sicherheitsprobleme sollten Anwender Microsofts JVM deinstallieren. Microsoft liefert für Vista kein Java mit aus, und daher muss sowieso Oracles JVM installiert werden.[2])

Hinweis: Wir wollen im Folgenden davon ausgehen, dass nicht die Java-Laufzeitumgebung 1.1 von Microsoft installiert ist, sondern ein vollwertiges Java von Oracle. Ist Oracles JVM installiert, ersetzt sie – falls diese installiert war – die JVM von Microsoft, und aktuelle Java-Programme lassen sich ausführen.

(J)Applet und Applikationen

Verfügen normale Applikationen, die von der Kommandozeile gestartet werden, über eine statische main(String[])-Methode, ist das bei Applets anders. Sie erweitern zwingend die Klasse javax.swing.JApplet oder java.applet.Applet und implementieren Callback-Methoden statt einer main(…)-Methode. (Hybride Programme, die sowohl Applikation als auch Applet sind, sind ebenfalls möglich. Diese Sorte Programm erweitert einfach die Klasse (J)Applet und implementiert eine statische main(…)-Methode.)

Ein Applet unterliegt hohen Sicherheitsbestimmungen – eine auf dem Client-Rechner liegende Datei kann zum Beispiel nicht gelöscht werden, und schon der lesende Zugriff ist unzulässig. Vom Sicherheitsstandpunkt aus betrachtet, kontrolliert der SecurityManager die Handlungen der Software.

Das erste Hallo-Applet

Ein Programm wird leicht zu einem Applet, wenn es die Klasse Applet erweitert. Als Einstieg soll ein kleines Beispiel-Applet dienen.

import java.applet.Applet;
import java.awt.Graphics;

 public class HelloWorldApplet extends Applet {
   /* @Override */ public void paint( Graphics g )
   {
     g.drawString( "Hallo Welt!", 50, 25 );
   }
 }

Die beiden ersten import-Anweisungen binden die notwendigen Informationen über Applets und über Zeichenmethoden ein. Das HelloWorldApplet erweitert die Klasse Applet, denn so wird ein Applet erzeugt. Eine statische main(String[])-Methode kommt nicht vor, und es muss eine Methode paint(Graphics) überschrieben werden, die den Bildschirmaufbau übernimmt.[3] Der Webbrowser oder Applet-Viewer ruft diese Methode per Callback auf.

Damit der Viewer weiß, was zu machen ist, gibt der HTML-Code einen Hinweis auf die Klasse, die in die Seite eingebettet wird. Dies wird über ein spezielles Tag erreicht.

<html><body>
<applet code="HelloWorldApplet.class" width="200" height="100"></applet>
</body></html>

Neben dem Namen der Klasse übermittelt das Applet-Tag der virtuellen Maschine auch die Maße des Fensters, in dem das Applet zeichnen kann.

Ab Java 1.1 lassen sich gepackte Dateien im Jar-Format übermitteln. Die Angabe der Klasse in der Code-Anweisung sollte keine Leerzeichen beinhalten. Die Endung .class ist nicht in jedem Browser erforderlich, wird aber empfohlen.

Fehler in Applets finden

Werden Applets vom Browser nicht ausgeführt, weil es Fehler in unserem Programm gibt, so bietet die Java Console (http://www.java.com/en/download/help/javaconsole.xml) die Möglichkeit, Log-Informationen anzusehen und so die Fehler genauer zu studieren.

Die Applet-API

Die Zyklen eines Applets

Beim Start eines Applets werden unterschiedliche Methoden vom Browser automatisch aufgerufen. Es beginnt mit dem Aufruf der Methode init(). Dort sollten Initialisierungen erfolgen. init() wird nur einmal aufgerufen, wenn die Seite vom Browser geladen wird. Nach der Initialisierung folgt ein Wechsel der Methoden start() und stop() immer dann, wenn ein Applet im Browser sichtbar ist oder von der Seite verschwindet, etwa wenn der Anwender über die Schieberegler einen anderen Bereich auswählt, in dem das Applet nicht liegt. Beim Verlassen der Seite wird abschließend destroy() aufgerufen. Dort können Ressourcen freigegeben werden.

Parameter an das Applet übergeben

Dem Applet können Parameter im Applet-Tag übergeben werden. Dazu wird im <applet>-Element ein <param>-Element eingebettet. Im Folgenden zeichnet ein Applet einen grünen oder roten Kasten, in Abhängigkeit davon, ob eine URL korrekt aufgebaut ist.

<html><body>
 <a href="http://tutego.com/index.html">Java-Seminare</a>
 <applet code="CheckUrlApplet.class" width="10" height="10">
 <param name="url" value="http://tutego.com/index.html">
 </applet>
 <p>
 <a href="tutego.com/index.html2">Java-Seminare Falsch</a>
 <applet code="CheckUrlApplet.class" width="10" height="10">
 <param name="url" value="tutego.com/index.html2">
 </applet>
</body></html>

Das Applet nimmt den Parameter an und prüft den gültigen Aufbau der URL über eine MalformedURLException.

import java.applet.Applet;
import java.awt.Color;
import java.awt.Graphics;
import java.net.MalformedURLException;
import java.net.URL;

public class CheckUrlApplet extends Applet {
  private boolean urlOk = false;

  public void init() {
    try {
     new URL( getParameter( "url" ) );
      urlOk = true;
    }
    catch ( MalformedURLException e ) { /* urlOk is false */ }
  }
  @Override public void paint( Graphics g ) {
     g.setColor( urlOk ? Color.GREEN : Color.RED );
     g.fillRect( 0, 0, 10, 10 );
   }
}

Interessant wäre natürlich, wenn das Applet gleich die URL auf Erreichbarkeit prüfen würde. Relativ einfach ergeben sich dann folgende Zeilen:

try {
 urlOk = ((HttpURLConnection)new URL( getParameter("url" ) ).
 openConnection()).getResponseCode() == HttpURLConnection.HTTP_OK;
}
catch ( IOException e ) { /* urlOk is false */ }

Doch bei der Prüfung von üblichen Links kommt es zu einem Fehler! Aus Sicherheitsgründen kann ein Applet nur auf den Rechner zugreifen, von dem es geladen wurde, nicht auf andere. Ohne explizite Sicherheitserweiterungen kann so ein allgemeines Applet zum Prüfen eines Links nicht geschrieben werden.

Wie das Applet den Browser-Inhalt ändern kann *

Das Applet kann mit showDocument(URL [,String]) auf den Inhalt der Seite Einfluss nehmen. So lassen sich Applets bauen, die eine Baumstruktur der Seite anzeigen und dann zum Inhalt verweisen, falls eine Seite ausgewählt wird. Verwendet werden hier die Methoden von AppletContext. In Kurzform:

getAppletContext().showDocument( new URL("http://tutego.com/") );

Oder, falls ein spezieller Frame mit Namen angesprochen ist:

getAppletContext().showDocument( new URL("http://tutego.org"), "Framename" );

class java.applet.Applet extends Panel

  • AppletContext getAppletContext()
    Liefert den Kontext des Applets. Dieser Kontext erlaubt es dem Applet herauszufinden, in welcher Umgebung, also auf welcher Web-Seite, es sich bewegt.

interface java.applet.AppletContext

  • void showDocument(URL url)
    Ersetzt den Inhalt auf der aktuellen Seite durch eine neue Seite von der angegebenen URL.
  • void showDocument(URL url, String target)
    Ersetzt den Inhalt auf der aktuellen Seite durch eine neue Seite von der angegebenen URL. Dabei wird das Dokument in einem Frame abgelegt, dessen Name zusätzlich festgelegt ist. Für target sind erlaubt: _self (Seite, die das Applet enthält), _parent (bettet die neue Seite in die Vaterseite des Applets ein; falls diese nicht existiert, verhält es sich wie _self), _top (im Top-Level-Frame anzeigen; falls dieser nicht existiert, wie _self), _blank (erzeugt ein neues Fenster), und wenn der Name nicht mit den Konstanten übereinstimmt, wird die Anzeige in einen Frame gelegt, der diesen Namen trägt.

Den Ursprung des Applets erfragen

Greift ein Applet auf Daten des Servers zu und ist ihm die Adresse nicht bekannt, so kann es nachfragen. Die Applet-Klasse stellt die Methoden getCodeBase() und getDocumentBase() zur Verfügung.

class java.applet.Applet extends Panel

  • URL getCodeBase()
    Liefert die Basis-URL des Applets.
  • URL getDocumentBase()
    Liefert die URL der Webseite, die das Applet enthält.

Auf dem URL-Objekt liefert getHost() eine String-Repräsentation der URL. So kommen wir mit der Methode getCodeBase().getHost() an den Hostnamen und auch an die Daten des Servers.

Beispiel: Applets können problemlos von Webseiten geklaut werden. Um dem einen Riegel vorzuschieben, können wir verlangen, dass die Zeichenkette von getDocumentBase().getHost() immer die Webseite unseres Servers repräsentiert.

String web = getDocumentBase().getHost();
if ( ! "www.tutego.com".equals(web) ) {
 // hier meckern, dass was nicht stimmt.
}

Wir könnten die Überprüfung auch über ein InetAddress-Objekt realisieren.

class java.net.URL implements Serializable, Comparable

  • String getHost()
    Liefert den Hostnamen des URL-Objekts. Handelt es sich um das »file«-Protokoll, so ist der Rückgabewert ein leerer String.

Beispiel: Baue eine URL-Verbindung zu einer Grafikdatei auf. Wir benutzen hier zunächst die Methode getDocumentBase(), um an die URL des Servers zu gelangen, und anschließend den URL-Konstruktor, der uns relativ zur Basisadresse eine Pfadangabe erlaubt.

URL u1 = getDocumentBase();
try {
URL u2 = new URL( u1, "image.gif" );
   ...
}
catch ( MalformedURLException e ) { ... }

Datenaustausch zwischen Applets *

Sind mehrere Applets auf einer Webseite untergebracht, gibt es Fälle, in denen die Applets Daten austauschen wollen. Zwei Lösungen sind populär:

  • Da alle Applets in einer einzigen JVM laufen, lässt sich über statische Attribute auf die anderen Elemente zugreifen. Dies spricht jedoch gegen die Datenkapselung und ist sehr unfein. Diese Technik hat einen weiteren Schwachpunkt: Statische Variablen hängen eng mit dem Klassenlader zusammen, und hier traten in der Vergangenheit bei einigen Browsern Probleme auf.
  • Eleganter ist da schon die Möglichkeit über die Schnittstelle AppletContext, die es ermöglicht, einen Verweis auf das Applet über den Namen zu bekommen.

class java.applet.Applet extends Panel

  • AppletContext getAppletContext()
    Bestimmt die Umgebung eines Applets.

Applets über den AppletContext erfragen

Mit dem AppletContext gibt es zwei Möglichkeiten, an das Applet zu gelangen:

  • das Applet über einen Namen ansprechen
  • eine Aufzählung aller Applets erfragen

Um einen Namen zu vergeben, wird das name-Attribut im <applet>-Tag genutzt, etwa so:

<applet code="Applet.class" name="applet" width="10" height="10">

Eine Verbindung der Methoden getAppletContext() aus Applet und getApplet(String name) aus AppletContext führt zu folgender Zeile:

Applet anotherApplet = applet.getAppletContext().getApplet( "applet" );

Die zweite Variante war, sich mit getApplets() eine Enumeration aller Applets einer Seite zu besorgen:

Applet otherApplet = null;
Enumeration applets = getAppletContext.getApplets();
while ( applets.hasMoreElements() ) {
 otherApplet = (Applet) applets.nextElement();
 if ( otherApplet != this )
  break;
 // Jetzt können wir etwas mit dem anderen Applet machen
 // if ( otherApplet instanceof Applet2 )
 //   ...
}

interface java.applet.AppletContext

  • Applet getApplet(String name)
    Sucht das Applet namens name in dem Dokument, das durch den AppletContext gegeben ist. Der Name kann durch das HTML-Tag gesetzt sein. Falls kein Applet dieses Namens existiert, liefert die Methode null.
  • Enumeration<Applet> getApplets()
    Findet alle Applets, die durch AppletContext angegeben sind.

Praktische Kommunikation

Das Applet können wir gegebenenfalls in eine Unterklasse casten. Dann lassen sich alle Methoden aufrufen und die Variablen auslesen. Leider funktionieren beide vorgestellten Methoden nur, wenn die Applets in dem gleichen Frame liegen. Liegen sie in verschiedenen Frames, findet zumindest die Netscape-Methode getApplet(String) das Applet leider nicht. Hier bleibt aber noch die Variante über statische Variablen übrig. Eine weitere Möglichkeit, Applets über verschiedene Frames kommunizieren zu lassen, führt über eine JavaScript-Funktion. Sie fungiert als Brücke, was etwa so aussieht: top.frames[1].document.applet[„applet“].method().

Das folgende Beispiel zeigt zwei Applets, Applet1 und Applet2, auf einer Webseite. Zunächst der HTML-Code:

<html><body>
<applet code="Applet1.class" name="applet1" height="200" width="200"></applet><applet code="Applet2.class" name="applet2" height="200" width="400"></applet>
</body></html>

Es folgen die Implementierungen für die beiden Applets:

import java.applet.Applet;
import java.awt.*;

public class Applet1 extends Applet {
   private TextField inputText = new TextField( "", 10 );
   public void init() {
     add( inputText );
     add( new Button( "Sende an Applet2" ) );
   }

   public boolean action( Event ev, Object arg ) {
     if ( ev.target instanceof Button ) {
       Applet2 applet2 = (Applet2) getAppletContext().getApplet( "applet2" );

       if ( applet2 != null )     {
         applet2.appendTheText( inputText.getText().trim() );
         return true;
       }
     }

     return false;
   }
 }

import java.applet.Applet;
import java.awt.TextArea;

public class Applet2 extends Applet {
   private TextArea textBox = new TextArea( 5, 40 );

   public void init() {
     add( textBox );
   }

   public void appendTheText( String s ) {
     textBox.append( s + "\n" );
   }
 }

Da bei verschiedenen Frames getAppletContext() jedoch das andere Applet nicht zurückgeben muss, bleibt nur noch die Variante über die statische Variable. Glücklicherweise lassen sich mit Beobachtermustern aus auch elegante Benachrichtigungen realisieren.

Reiseverbot für Daten: Was ein Applet alles darf *

Ein Applet unterliegt bestimmten Sicherheitsbeschränkungen, die eine Java-Security-Einheit überprüft.

Viele der bekannten Fehler in Java, die potenzielle Sicherheitslücken darstellen, sind mittlerweile behoben. Schon das Auffinden setzt eine gründliche Kenntnis der Java-Quelltexte voraus, beispielsweise der Fehler mit der Host-Adresse: Wenn ein Benutzer ein Applet von tutego.com liest, darf dieses Applet nur mit diesem Host eine Verbindung aufbauen und mit keinem anderen. Doch leider gab es in den Quelltexten von Java einen Fehler, sodass das Applet nur den Rechnernamen des Hosts vergleicht, nicht aber die IP-Adresse. Ein bösartiges Applet kann nun dem DNS (Domain Name Server) eine falsche Zuordnung von Rechnername und IP-Adresse vorspielen, und nun verhält sich tutego.com wie www.ganz-boese.com.

Ist Java im Browser aktiviert? *

Wenn unser Browser Java-Applets ausführen soll, aber Java gar nicht aktiviert ist, dann lassen sich einige interaktive Benutzeraktionen nicht durchführen. Wir sollten daher zumindest eine Meldung anbieten, dass der Browser Java gerade nicht aktiviert hat. Dies kann beabsichtigt oder nicht beabsichtigt sein. Natürlich kommt Java dafür nicht infrage, aber eine Skript-Sprache mit einem ähnlichen Namen: JavaScript. Ab JavaScript-Version 1.1 bietet uns der Interpreter die Funktion javaEnabled() an, sodass wir eine Weiterschaltung vornehmen können:

if ( ! navigator.javaEnabled() ) {
 self.location.href = "nix_mit_java.html";
}

Für diese Lösung muss natürlich JavaScript aktiviert sein. Für einige Surfer ist selbst dies schon eine Sicherheitslücke, und wenn JavaScript deaktiviert ist, lässt sich hier nichts mehr machen. Falls JavaScript aktiviert ist, kommen wir dem Benutzer einen Schritt entgegen, sodass er nicht mehr manuell angeben muss, ob Java aktiv ist oder nicht. Von dieser Technik sollten wir auch Gebrauch machen, denn nicht immer hat der Benutzer bewusst Java abgeschaltet. Im Beispiel oben haben wir eine Seite angesteuert, wobei natürlich andere Anweisungen denkbar sind. Doch diese Form ist sinnvoll, denn wir können Benutzern eine Kurzbeschreibung darüber liefern, wie Java im Browser aktiviert wird. Zusammen mit der Browservariante ist eine browsergenaue Beschreibung einsetzbar.

Applet unter Firefox (Netscape) oder Microsoft Internet Explorer? *

Kann der Browser ein Applet aus irgendwelchen Gründen nicht ausführen, so sind die Meldungen an den Benutzer meist mager. Oft beschränken sie sich auf eine Exception-Angabe in der Statuszeile. Dies mag keiner mehr sehen. Doch leider verschärfen inkompatible Browser die Situation. Was hier Abhilfe schafft, ist ein kleines Programm, das zunächst herausfindet, auf welchem Browser das Applet läuft. Dann können unter Umständen browser- und versionsabhängige Varianten ausgeführt werden.

Wir verwenden einen Trick, der auch beim Erkennen von Prozessortypen angewendet wird: Wir versuchen, Klassen zu laden oder Methoden aufzurufen, die es für den jeweils anderen Browser nicht gibt. Der Internet Explorer hat zum Beispiel eine private Klasse com.ms. applet.GenericAppletContext, und Mozilla hat eine Klasse netscape.applet.MozillaAppletContext. Löst die JVM beim Laden der Klasse eine Exception aus, wissen wir Bescheid, um welchen Browser es sich handelt.

Versuchen wir, über die selbst gebastelten Methoden isNetscape() und isMicrosoft() etwas über unsere Laufzeitumgebung herauszufinden.

import java.applet.Applet;

public class BrowserDetector extends Applet {

 public void init() {
     if ( isNetscape() )
       System.out.println( "Netscape, Firefox, ... Browser." );

     if ( isMicrosoft() )
       System.out.println( "Microsoft Browser." );
   }

   public static boolean isNetscape() {
     try {
       Class.forName( "netscape.applet.MozillaAppletContext" );
     }
     catch ( ClassNotFoundException e ) { return false; }
     return true;
   }

   public static boolean isMicrosoft() {
     try {
       Class.forName( "com.ms.applet.GenericAppletContext" );
     }
     catch ( ClassNotFoundException e ) { return false; }
     return true;
   }
 }

Die Idee lässt sich natürlich auch anwenden, um Java-Versionen zu testen; es wird einfach eine Klasse erfragt, die bei einer neuen Java-Version hinzugekommen ist, bei Java 2 etwa Point2D.

Tipp: Da nicht immer sichergestellt sein kann, dass Java in einer vernünftigen Version (>= 1.2) auf dem Client-Rechner der Benutzer installiert ist, lässt sich ein Test-Applet vorschalten, das zunächst die Java-Version prüft. Anschließend kann dieses Eingangs-Applet über getAppletContext().showDocument() auf eine andere Seite mit einem Applet verweisen. Für unterschiedliche Browser und Java-Installationen können somit unterschiedliche Applets auf die Situation eingehen.

Webstart

Bevor Software auf dem Rechner läuft, wird sie in der Regel installiert. Dazu legen die Hersteller der Software ein spezielles Installationsprogramm bei – unter Windows oft die InstallShields. Das Installationsprogramm legt passende Verzeichnisse an und initialisiert etwa die Registrierdatenbank unter Windows. Etwas anders sieht das bei Java-Programmen aus. Die Installation erfordert zuerst eine Java-Laufzeitumgebung. Anschließend kann das Programm entpackt und gestartet werden. Wünschenswert ist jedoch eine Art Umgebung, wie sie bei Java-Applets definiert ist. Ein Java-Applet läuft innerhalb eines Browsers in einem speziellen Sicherheitsmodus, und es wäre günstig, wenn dies auch für alle anderen Applikationen möglich wäre. Das bedeutet: Eine beliebige Applikation kann von einer Webseite geladen und auf dem lokalen Rechner ausgeführt werden. Die Applikation soll sich nicht von anderen Applikationen unterscheiden, die lokal installiert sind.

Damit der Start möglich ist, ist die Oracle-Technologie Webstart nötig. Webstart deckt die Bereiche Installation, Start und Update durch ein eigenes Protokoll ab, das Java Network Launcher Protocol (JNLP). Die Technologie wurde auf der JavaOne 2000 erstmals vorgestellt. Neben der offiziellen Webseite widmet sich auch die (nicht mehr ganz so frische) Unofficial Java Web Start/JNLP FAQ unter http://lopica.sourceforge.net/faq.html dem Thema.

Zum Weiterlesen

Die API rund um Applets blieb lange Zeit stabil und unaufregend. Mit einer neuen Browser-Implementierung und Annäherung an WebStart passiert aber doch noch das ein oder andere. So können Applets mittlerweile aus Web-Seiten herausgezogen werden, und Applets können problemlos den DOM-Baum der Seite modifizieren, in der sie laufen. Mehr Informationen zu diesen Themen gibt es in Oracles Java-Tutorial unter http://download.oracle.com/javase/tutorial/deployment/applet/index.html. Es gibt auch von Oracle eine JavaScript-API, die das korrekte HTML für ein Applet generiert, sodass Entwickler nicht das eigentlich veraltete <applet> nutzen müssen – Weiteres dazu unter http://download.oracle.com/javase/7/docs/technotes/guides/jweb/deployment_advice.html#deplToolkit.


[1]    http://www.adobe.com/products/player_census/flashplayer/version_penetration.html

[2]    http://windowshelp.microsoft.com/Windows/en-US/Help/59c3a93d-1342-43a6-a01a-f720c7a17ffc1033.mspx

[3]    Das gilt nicht für JApplet. Dort ist das Überschreiben von paint() oder paintComponent() unüblich, da im Allgemeinen eine eigene, sich zeichnende Komponente auf das JApplet gesetzt wird.

Wunschzeit und wo die Inseln für Java 8 stehen

Jetzt wo Java 8 auf der Zielgeraden ist, und ich mit keinen API-Änderungen mehr rechnen muss, gehen auch die beiden Insel in die Endphase. Die Neuerungen sind ja im Grunde sehr große Bausteine wie Lambda-Ausdrücke und Stream-API und dann duzend verteilte Änderungen.

Nervig für mich als Autor war die Dynamik, die die API noch in der letzten Phase entwickelte; besonders trat das bei der Stream-API auf. Doch komplett fertig sind nun die großen Kapitel über

  • Lambda-Ausdrücke (ca. 30 Word-Seiten)
  • java.util.function und funktionale Programmierung (ca. 20 Word-Seiten)
  • Stream-API (ca. 30 Word-Seiten)

Alle kleineren Sprachänderungen wie Default-Methoden und statische Schnittstellenmethoden sind ebenfalls komplett fertig und verteilt.

Fast alle kleineren API-Änderungen sind ebenfalls eingeflossen und im Grunde sind damit die beiden neuen Bände fertig. Was noch fehlt — und daran arbeite ich gerade als letztes — ist die neue Date-Time-API. Große Freude bereitet mir das nicht … Vielleicht baue ich das später in der nächsten Auflage weiter aus, wenn der andere Datums-Teil (Date, Calender, …) komprimiert wird. Ebenfalls von meiner Lust abhängig wird sein, wie weit ich JFC (Swing, Java 2D) kürze und die GUI-Themen nach JavaFX bringe.

Die Insel Band 2 greift nicht alle Bibliotheken in hundertprozentiger Tiefe auf, da es Bereiche gibt, die schon sehr speziell sind, und dafür fehlt auch der Platz. Vermutlich werden ich zu folgenden (extrem spannenden Themen) nicht mehr als einen Satz bringen:

  • Neuer Typ java.util.concurrent.locks.StampedLock
  • Neuer Typ java.util.concurrent.CountedCompleter
  • Neuer Typ java.util.concurrent.ConcurrentHashMap.KeySetView
  • Neuer Typ java.util.concurrent.CompletionStage
  • Neuer Typ java.util.concurrent.CompletionException
  • Neuer Typ java.util.concurrent.CompletableFuture
  • Neuer Typ java.util.concurrent.CompletableFuture.AsynchronousCompletionTask
  • Änderungen am ForkJoinPool

Bis auf ein paar weiteren Kleinigkeiten (Implementierung vom eigenen Spliterator, Reflection-Kram, Locale-Update, …) ist sonst die Abdeckung der neuen Themen bei 100%.

Kummer macht mir die Java 8 Unterstützung der Eclipse IDE, da es bisher keine Eclipse-Version mit integriertem Java 8 Compiler gibt, nur über ein Update. Luna soll im Juni fertig sein, das wäre eigentlich zu spät. Bei NetBeans haben wir das Problem natürlich nicht.

Haben meine Leser noch spezielle Wünsche an die nächste Auflage?

Kommen in Java 9 nun Collection Literals?

Neu ist die http://openjdk.java.net/jeps/186, schauen wir, was wird. Ziel “Being able to initialize arrays, lists, sets, and maps with a compact expression”. Wobei ich die Syntax jetzt nicht so schön finde:

List<Integer> list = #[ 1, 2, 3 ];

Die Diskussion unter http://mail.openjdk.java.net/pipermail/lambda-dev/2014-January/011671.html ist schon recht lang ausführlich.

LongAccumulator und DoubleAccumulator

Die beiden XXXAdder-Klassen haben eine ganz spezielle Aufgabe und das ist Werte zu addieren und aufzusummieren. Allerdings gibt es noch viele weitere Aufgaben, die ähnlich wie die Summation auf ein Endergebnis gebracht werden. Für diesen Anwendungsfall deklariert das Paket java.util.concurrent.atomic weiterhin LongAccumulator und DoubleAccumulator. Im Konstruktor nehmen die Klassen einen XXXBinaryOperator an und die Identität, die beim binären Operator auf nur einem Ergebnis genau das Ergebnis gibt.

· LongAccumulator(LongBinaryOperator accumulatorFunction, long identity)

· DoubleAccumulator(DoubleBinaryOperator accumulatorFunction, double identity)

Die Methoden heißen dann accumulate(long x) bzw. accumulate(double x) und get() verdichtet das Ergebnis zum Schluss. Die Addition der Klassen LongAdder und DoubleAdder lässt sich dann alternativ ausdrücken durch new XXXXAccumulator((x, y) -> x + y, 0).

java.util.concurrent.atomic.LongAdder und DoubleAdder in Java 8

Die AtomicXXX-Klassen sind gut, wenn es nicht zu viele parallel Threads gibt, die gleichzeitig die AtomicXXX-Exemplare verändern. Der Grund ist einfach: jeder Thread warten muss, bis ein anderer Thread die Veränderung am AtomicXXX vorgenommen hat. Stehen also 100 Threads in der Schlage den AtomicXXX zu verändern, werden sie erst nacheinander abgearbeitet – das geht zwar an schnell, dennoch führt die sequenzielle Verarbeitung zu Wartesituationen.

Wenn es wirkliche viel nebenläufige Threads gibt, sind die AtomicXXX-Klassen nicht optimal und Java biete ab Java 8 zwei neue Klassen LongAdder und DoubleAdder. Ein XXXAdder sieht nach außen wie ein long/double aus (die Klassen erweitern auch Number), doch intern sind sie vielmehr eine Liste von Werten, auf die dann unterschiedliche Thread zugreifen können, ohne zu warten. Um sich das vorzustellen zu können ein Beispiel: Nehmen wir an, mehrere Threads teilen sich einen LongAdder. Ruft ein Thread add(1) auf, so führt das intern zu einem Element in einer Liste[1]. Kommt gleichzeitig add(2) am LongAdder an, muss der Thread nicht auf das Ende vom ersten add(…) warten, sondern fügt einen neuen Knoten an. Kommt später ein dritter und vierter Thread über den Weg und führt add(3) und add(4) aus, können diese ohne Warten an den ersten und zweiten existieren Knoten gehen und die Werte addieren, in den beiden internen Knoten stehen also 4 und 6. Es sind also nur so viele Knoten intern nötig, wie wirklich parallele Threads auftauchen. Eine Summation am Ende mit sum() läuft dann über die internen Knoten und summiert sie auf zu 10, was 1+2+3+4 ist.


[1] Genau genommen beim ersten Element noch in einer Variablen, die Liste beginnt erst beim zweiten Element, also beim zweiten parallelen Thread.

map(…) Methoden von Optional für funktionale Programmierung

Java 8 bekommt eine Optional Klasse und die beiden XXXmap(…)-Methoden sind besonders interessant. Sie ermöglichen einen ganz neuen Programmierstil. Warum soll ein Beispiel zeigen.

Der folgende Zweizeiler gibt auf meinem System „MICROSOFT KERNELDEBUGGER-NETZWERKADAPTER“ aus:

String s = NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase();

System.out.println( s );

Allerdings ist der Programmcode alles andere als gut, denn NetworkInterface.getByIndex(int) kann null zurückgeben und getDisplayName() auch. Um ohne eine NullPointerException um die Klippen zu schiffen müssen wir schreiben:

NetworkInterface networkInterface = NetworkInterface.getByIndex( 2 );

if ( networkInterface != null ) {

String displayName = networkInterface.getDisplayName();

if ( displayName != null )

System.out.println( displayName.toUpperCase() );

}

Von der Eleganz des Zweizeilers ist nicht mehr viel geblieben. Integrieren wir Optional (was ja eigentlich ein toller Rückgabetyp für getByIndex() und getDisplayName():

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

if ( networkInterface.isPresent() ) {

Optional<String> name = Optional.ofNullable( networkInterface.get().getDisplayName() );

if ( name.isPresent() )

System.out.println( name.get().toUpperCase() );

}

Mit Optional wird es nicht sofort besser, doch statt if können wir ein Lambda-Ausdruck einsetzen und bei ifPresent(…) einsetzen:

Optional<NetworkInterface> networkInterface = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) );

networkInterface.ifPresent( ni -> {

Optional<String> displayName = Optional.ofNullable( ni.getDisplayName() );

displayName.ifPresent( name -> {

System.out.println( name.get().toUpperCase() );

} );

} );

Wenn wir nun die lokale Variablen entfernen, kommen wir aus bei:

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) ).ifPresent( ni -> {

Optional.ofNullable( ni.getDisplayName() ).ifPresent( name -> {

System.out.println( name.get().toUpperCase() );

} );

} );

Von der Struktur ist das mit der if-Afrage identisch und über die Einrückungen auch zu erkennen. Fallunterscheidungen mit Optional und ifPresent(…) umzuschreiben bringt also keinen Vorteil.

In Fallunterscheidungen zu denken hilft hier nicht weiter. Was wir uns bei NetworkInterface.getByIndex( 2 ).getDisplayName().toUpperCase() vor Augen halten müssen ist eine Kette von Abbildungen. NetworkInterface.getByIndex(int) bildet auf NetworkInterface ab, getDisplayName() von NetworkInterface bildet auf String ab, und toUpperCase()bildet von einem String auf einen anderen String ab. Wir verketten also drei Abbildungen und müssten ausdrücken können: Wenn eine Abbildung fehlschlägt, dann höre mit der Abbildung auf. Und genau hier kommt Optional und map(…) ins Spiel. In Code:

Optional<String> s = Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

. map( ni -> ni.getDisplayName() )

. map( name -> name.toUpperCase() );

s.ifPresent( System.out::println );

Die Klasse Optional hilft uns bei zwei Dingen: Erstes wird map(…) beim Empfangen einer null-Referenz auf ein Optional.empty() abbilden. Und zweitens ist das Verketten von leeren Optionals kein Problem, es passiert einfach nichts – Optional.empty().map(…) führt nichts aus und die Rückgabe ist einfach nur ein leeres Optional.

Umgeschrieben mit Methoden-Referenzen und weiter verkürzt ist das Code sehr gut lesbar.

Optional.ofNullable( NetworkInterface.getByIndex( 2 ) )

. map( NetworkInterface::getDisplayName )

. map( String::toUpperCase )

.ifPresent( System.out::println );

Die Logik kommt ohne externe Fallunterscheidungen aus und arbeitet nur mit optionalen Abbildungen. Das ist ein schönes Beispiel für funktionale Programmierung.

Compact-Profile in Java 8

Nicht jedes Java-Programm braucht den vollen Satz von 4000 Typen, sondern oftmals reicht eine kleine Teilmenge. Eine Teilmenge der Java-Bibliothek wiederum ermöglicht es, kleinere Laufzeitumgebung zu bauen, was es erlaubt, Java auch für Geräte mit weniger Ressourcen einzusetzen. Eine kleine Java-Version für Embedded-Systeme braucht kein CORBA, AWT oder JavaFX und kann so viel kleiner als das Standard-JRE sein.

In Java 8[1] wurde die Bibliothek in 4 Gruppen eingeteilt, auf der einen Seite stehen drei kompakte aufsteigende Teilmengen der Standardbibliothek, genannt Compact-Profile, und auf der anderen Seite das vollständige System. Das Profil contact1 ist das kleinste, compact2 enthält contact1, contact3 enthält compact2 und das Gesamt-JRE compact3.

Welche Typen zu welchem Profil gehören dokumentiert die Java-API auf der Startseite sowie gibt jeder Typ in der Javadoc sein Profil an; die grobe Einteilung ist:

Profil

Größe

Pakete

compact1

10 MiB

java.io, java.math, java.net, java.nio, java.security, java.time, java.util (inklusive Stream-API), javax.crypto, javax.script, javax.security

compact2

17 MiB

java.sql, java.rmi, javax.rmi, javax.transaction, javax.xml, org.xml, org.w3c

compact3

24 MiB

java.lang.instrument, java.lang.management, java.util.prefs, java.lang.model, javax.management, javax.naming, javax.sql.rowset, javax.tools, javax.xml.crypto, org.ieft.jgss, Kerberos, SASL

JRE/JDK

140 MiB

Alles weitere: java.beans, AWT, Swing, JavaFX, CORBA, java.awt.print, Sound, SOAP, Web-Service, …

Inhalt der verschiedenen Profile mit Speicherbedarf[2]

Weiterhin ist die Anzahl verschiedener Provider minimiert worden, es gilt zum Beispiel nur Englisch als verpflichtende Sprache.

Werkzeug-Unterstützung für Profile

Die Werkzeuge javac, javadoc und jdeps aus dem JDK sind für die Profile aktualisiert worden, etwa dass sie prüfen können, ob ein Typ/Eigenschaft zum Profil gehört oder nicht. Der Schalter –profile gibt dabei das gewünschte Profil an.

Beispiel

Versuche die Klasse T mit der Deklaration class T extends java.awt.Point {} mit dem Profile compact3 zu übersetzen:

$ javac -profile compact3 T.java

T.java:2: error: Point is not available in profile ‚compact3‘

class T extends java.awt.Point { }

^

1 error

Obwohl Point eine nützliche Klasse ist, und keine plattformspezifischen Eigenschaften hat, ist das gesamte Paket java.awt gesperrt und kein Teil vom Compact-Profile.


[1] Beschrieben erstmalig unter http://openjdk.java.net/jeps/161

[2] Zahlen von JavaOne Konferenz 2013.