Mein Abendvortrag “Java 8 Features” in Braunschweig am 13. Juni

Java User Group Ostfalen

CKC
13 Am Alten Bahnhof
38122 Braunschweig
Deutschland

Donnerstag, 13. Juni 2013, von 19:00 bis 22:00 (MESZ)

 

Christian Ullenboom gibt in seinem Abendvortrag einen Einblick in die Neuerungen von Java 8, angefangen von statischen Schnittstellenmethoden über Default-Methoden, Lambda-Ausdrücken, Annotationen-Ergänzungen bis zu den Erweiterungen der Standard-Bibliothek. In der gemütlichen Diskussionsrunde im Anschluss gibt es keine Denkverbote und es darf auch über Plagiate, Flughäfen in großen Städten, ausgefüllte Dirndl offen und frei gesprochen werden.

 

Referent:

Presenter.build().name( "Christian Ullenboom" ).born( 1973 ).studied( "Diplom-Informatik" )
.cameToJava( "im ‚Ruck‘-Jahr 1997" )
.knownFor( "Java-Blog", URI.create( "http://javainselblog.tutego.de" ) )
.autored( "Java ist auch eine Insel" ).autored( "Java – mehr als eine Insel" )
.orElse( "Den Rest des Tages verbringt er mit seiner Liebe" )
.orElse( "Erweiterung des Schulungsunternehmens tutego", URI.create( "http://tutego.de/" ) )
.orElse( "alten Computern/Videospielkonsolen", URI.create( "http://RetroBu.de/" ) ).done();

Thema der Woche: Etwas Design mit UML

DRUG LORD (http://www.old-games.com/download/5218/druglord) ist ein Spielklassiker für DOS von 1993. Mit Kauf- und Verkauf von Drogen in verschiedenen Städten muss man möglichst viel Geld verdienen.

5218-5-druglord

Bild © old-games.com

Überlege, wie das Spiel in seinem Grundzügen (nur Kauf/Verkauf von Drogen, Orte und Cash aber kein Hospital/Health, keine Waffen, Bank, Schulden) objektorientiert modelliert werden kann. Nutzte UML-Diagramme zur Darstellung. Das Programm soll am Ende lauffähig sein, die Darstellung kann beliebig sein. (Wer das Retro-Feeling mag, kann http://sourceforge.net/projects/javacurses/ ausprobieren.)

Mit Java für C64 entwickeln

Knackige Überschrift, was steckt dahinter? Wir warten auf den Architekten für unser Restaurant hier in den Philippinen und so hatte ich etwas Leerlauf, um was total Unnötiges zu programmieren.

Um Java-Programme, beziehungsweise eine Teilmenge der Sprache/Bibliothek, auf dem C64 (oder anderen 8-Bit-Computern) zum Laufen zu bringen, sind mehrere Ansätze denkbar:

  • Eine JVM auf dem Heimcomputer, die Bytecode interpretiert. Das würde im Prinzip gehen, es gibt auch Mini-Laufzeitumgebungen für so etwas. Auch Java Smart Card ist ein Stichwort.
  • Compiler, die entweder Bytecode oder Source-Code in ein Quellformat umsetzen, was der 65xx versteht.

Ein JVM ist richtig viel Arbeit, und war in 2 Tagen nicht zu schaffen. Und mit http://sourceforge.net/projects/vm02/ gibt es so etwas auch schon für Apple II Computer. Ein (Cross-)Compiler ist deutlicher einfacher. Bytecode in 65xx-Assembler zu übersetzen ist relativ einfach, doch dann müsste ich wieder Assembler anfassen und damit das ganze schnell wird, müsste ich auch einen Code-Optimierer schreiben, denn einfach die Stack-Maschine umzusetzen, führt auch zu keinen Performance-Wundern. Da es für den C64 auch Compiler gibt, etwa für PASCAL (auf der Maschine) oder C (als Cross-Compiler etwa mit CC65), kann man aus Bytecode auch dieses Format generieren. Aber dann hätte ich wieder mit Java-Bytecode arbeiten müssen, was mir auch keine Freunde macht. Am Schnellsten verspricht Resultate eine Code-Transformation von Java nach C. Das Resultat kann dann der http://www.cc65.org/ in Maschinencode umsetzen, und dann bekommt man auch ein paar Optimierung geschenkt.

Das JDK bringt alles mit, um an den AST des Compilers zu kommen, wie schon im Blog hier beschrieben: http://www.tutego.de/blog/javainsel/2012/07/mit-der-internen-compiler-api-auf-den-ast-einer-klasse-zugreifen/. Von den erkannten Elementen (Ausdrücke, Variablenzugriff, Schleife, …) muss man nur C-Code schreiben und fertig ist. Um mir die Sache einfach zu machen vereinfache ich Java jedoch massiv:

  • kein float/double/long/boolean
  • kein new, keine Klassen, Stellen und sonstiges objektorientiertes “Zeugs”, das dranhängt, wie enum, erweitertes for, nur statische Methoden, keine Ausnahmen, Keine String-Konkatenation
  • String-Literale können verwendet werden, allerdings nur von 0x0000 – 0x00FF (256) und eigentlich geht PETSCII nur von 0-191
  • Nichts von der Java-Bibliothek

Des weiteren muss sehr “C”-ähnlich programmiert werden:

  • Alle lokale Variablen müssen am Anfang einer Methode deklariert sein
  • Alle Bezeichner müssen für den C-Compiler gültig sein, keine Unicodes
  • Die Reihenfolge muss stimmen, der Umsetzer erzeugt keine Prototypen
  • Eine main()-Methode muss etwas zurückgeben in C99, Java macht das nicht, daher nutzt man System.exit(0).

Das auf diese Weise kastrierte Java ist zwar im Prinzip für nix mehr zu gebrauchen, aber für Heimcomputer immer noch akzeptabel und eine nette Spielerei.

Wer bis dahin noch nicht das Interesse verloren hat, kann ein wenig mit dem Compiler spielen; die Source liegen unter https://code.google.com/p/java2c-transcompiler/.

Jetzt brauchen wir Input:

import static j2c.lib.Stdio.printf;

// Source: http://skoe.de/wiki/doku.php?id=ckurs:04-abend4
public class Application
{
  public static char istPrimzahl( int n )
  {
    int divisor;
    int testEnde = n / 2;

    /* Alle potentiellen Teiler bis zur Mitte testen */
    for ( divisor = 3; divisor < testEnde; divisor += 2 ) {
      /* Mit Rest 0 teilbar? */
      if ( n % divisor == 0 ) {
        /* Ueberprüfung abbrechen, keine Primzahl */
        return 0;
      }
    }

    /* Kein Test durchgefallen, ist eine Primzahl */
    return 1;
  }

  public static void main( String[] args )
  {
    int zahl;

    /* Von 3 beginnend jede zweite Zahl testen, bis unter 1000 */
    for ( zahl = 3; zahl < 1000; zahl += 2 ) {
      if ( istPrimzahl( zahl ) != 0 ) {
        printf( "Primzahl: %u\n", zahl );
      }
    }

    System.exit( 0 );
  }
}

Für die C-Funktionen (http://www.cc65.org/doc/funcref.html) gibt es eine paar statische Imports und native Platzhalter:

package j2c.lib;

// http://www.cplusplus.com/reference/cstdio/

public class Stdio
{
  /**
   * Print formatted data to stdout.
   * <code>int printf ( const char * format, ... );</code>
   * @param format
   * @param args
   * @return
   */
  native public static int printf( String format, Object... args );
}

Das setzt der Compiler im Grunde 1:1 so um. Es lohnt sich das Eclipse CDT unter http://download.eclipse.org/tools/cdt/releases/juno zu installieren, damit die syntaktische Hervorhebung funktioniert (den CC65 Compiler einbinden könnten wir hier NICHT). Nach einer CDT-Neuformatierung ergibt sich:

#include <stdio.h>
#include <stdlib.h>
#include <peekpoke.h>
#include <c64.h>
#include <conio.h>
/* CLASS Application { */
char istPrimzahl(int n) {
    int divisor;
    int testEnde = n / 2;
    for (divisor = 3; divisor < testEnde; divisor += 2) {
        if (n % divisor == 0) {
            return 0;
        }
    }
    return 1;
}
int main(void) {
    int zahl;
    for (zahl = 3; zahl < 1000; zahl += 2) {
        if (istPrimzahl(zahl) != 0) {
            printf("Primzahl: %u\n", zahl);
        }
    }
    return 0;
}
/* END CLASS } */

Um das Compilat zu Erzeugen muss nun der cc65 installiert werden. Unter ftp://ftp.musoftware.de/pub/uz/cc65/ lädt man die für Windows etwa die 1.3 MB große EXE und installiert. Die Eintragungen in den Path kann man vornehmen, nach der Installation folgt dann mit dem kleinen Test:

C:\..>cc65

cc65.exe: No input files

Kappt also.

Das Ganze soll im Emulator laufen, hier ist WinVICE gut: http://www.viceteam.org/#download.

Eine Batch-Datei bindet alles zusammen, also Java –> C, Compilieren und im VICE starten:

set JAVA_HOME="C:\Program Files\Java\jdk1.7.0"
%JAVA_HOME%\bin\java -cp bin/;%JAVA_HOME%\lib\tools.jar j2c.J2CC65 src/java/Application.java > app.c
del app.prg
cl65 -o app.prg app.c
"C:\Program Files\WinVICE-2.2-x64\x64.exe" app.prg

Und das Ergebnis sieht so aus:

image

Hat jmd. Lust das weiter zu entwickeln? Schreibt mir eine E-Mail.

Klassen mit einer abstrakten Methode als funktionale Schnittstelle in Java 8?

Als die Entwickler Lambda-Ausdrücke diskutierten, stand auch die Frage im Raum, ob abstrakte Klassen, die nur über eine abstrakte Methode verfügen – früher wurde hier die Abkürzung SAM (Single Abstract Method) genutzt –, ebenfalls für Lambda-Ausdrücke genutzt werden können. Sie entschieden sich dagegen, da bei Implementierung von Schnittstellen die JVM weitreichende Optimierungen vornehmen kann. Und bei Klassen wir das schwierig, was auch daran liegt, dass ein Konstruktor umfangreiche Initialisierungen mit Seiteneffekten vornehmen (die Konstruktoren aller Oberklassen nicht zu vergessen) sowie Ausnahmen auslösen könnte. Gewünscht ist aber nur die Ausführung einer Implementierung der funktionalen Schnittstelle und kein anderer Code.

Es gibt nun im JDK einige abstrakte Klassen, die genau eine abstrakte Methode vorschreiben, etwa jva.util.TimerTask. Solche Klassen können nicht über einen Lambda-Ausdruck realisiert werden; hier müssen Entwickler weiterhin zu Klassenimplementierungen greifen, und das kürzeste ist eine innere anonyme Klasse. Eigene Hilfsklassen können natürlich den Code etwas abkürzen, aber eben nur mit eigener Implementierung. Zwei Strategien bieten sich an: durch Delegation oder Vererbung. Nehmen wir das Beispiel für TimerTask und gehen beide Varianten durch:

import java.util.*;

class TimerTaskLambda {

public static TimerTask createTimerTask( Runnable runnable ) {

return new TimerTask() {

@Override public void run() { runnable.run(); }

};

}

public static void main( String[] args ) {

new Timer().schedule( createTimerTask( () -> System.out.println("Hi") ), 500 );

}

}

Oder mit Vererbung:

public class LambdaTimerTask extends TimerTask {

private final Runnable runnable;

public LambdaTimerTask( Runnable runnable ) {

this.runnable = runnable;

}

@Override public void run() { runnable.run(); }

}

Der Aufruf ist dann statt createTimerTask(…) der des Konstruktors:

new Timer().schedule( new LambdaTimerTask( () -> System.out.println("Hi") ), 500 );

Inselraus: Die Farben des Systems über java.awt.SystemColor

Bei eigenen Java-Programmen ist es wichtig, dass diese sich so perfekt wie möglich in die Reihe der anderen Client-Programme einordnen, ohne großartig aufzufallen. Dafür muss ein Fenster die globalen Einstellungen wie den Zeichensatz und die Farben kennen. Für die Systemfarben gibt es die Klasse SystemColor, die alle Farben einer grafischen Oberfläche auf symbolische Konstanten abbildet. So ist SystemColor.text[1] die Hintergrundfarbe von Texteingabefeldern. Besonders praktisch ist dies bei Änderungen von Farben während der Laufzeit. Über diese Klasse können immer die aktuellen Werte eingeholt werden, denn ändert sich beispielsweise die Hintergrundfarbe der Laufleisten, ändert sich damit auch der RGB-Wert.

Die Systemfarben sind Konstanten von Typ SystemColor, was eine Unterklasse von Color ist. Damit lassen sich sich direkt nutzen, etwa über setColor(Color) oder über getRGB() der RGB-Anteil erfragen. Die Klasse SystemColor hat keine eigenen öffentlichen Methoden, sondern überschreibt nur toString().

Die Klasse deklariert die folgenden statischen finalen Variablen:

class java.awt.SystemColor extends Color implements Serializable

SystemColor

Welche Farbe darauf anspricht

desktop

Farbe des Desktop-Hintergrunds

activeCaption

Hintergrundfarben für Text im Fensterrahmen

activeCaptionText

Farbe für Text im Fensterrahmen

activeCaptionBorder

Rahmenfarbe für Text im Fensterrahmen

inactiveCaption

Hintergrundfarbe für inaktiven Text im Fensterrahmen

inactiveCaptionText

Farbe für inaktiven Text im Fensterrahmen

inactiveCaptionBorder

Rahmenfarbe für inaktiven Text im Fensterrahmen

window

Hintergrundfarbe der Fenster

windowBorder

Rahmenfarbe der Fenster

windowText

Textfarbe für Fenster

menu

Hintergrundfarbe für Menüs

menuText

Textfarbe für Menüs

text

Hintergrundfarbe für Textkomponenten

textText

Textfarbe für Textkomponenten

textHighlight

Hintergrundfarbe für hervorgehobenen Text

textHighlightText

Farbe des Texts, wenn dieser hervorgehoben ist

textInactiveText

Farbe für inaktiven Text

control

Hintergrundfarbe für Kontrollobjekte

controlText

Textfarbe für Kontrollobjekte

controlHighlight

normale Farbe, mit der Kontrollobjekte hervorgehoben werden

controlLtHighlight

hellere Farbe, mit der Kontrollobjekte hervorgehoben werden

controlShadow

normale Hintergrundfarbe für Kontrollobjekte

controlDkShadow

dunklerer Schatten für Kontrollobjekte

scrollbar

Hintergrundfarbe der Schieberegler

Info

Hintergrundfarbe der Hilfe

infoText

Textfarbe der Hilfe

Konstanten der Systemfarben

Hinweis: Die Klasse javax.swing.UIManager ist ein großer Assoziativspeicher, bei dem sich weitere Belegungen erfragen lassen. Es erfragt zum Beispiel UIManager.getColor("Table.background") die Tabellen-Hintergrundfarbe vom gerade eingestellen Look and Feel.[2]


[1] Sun verstößt mal wieder gegen die eigenen Namenskonventionen. Die finalen Variablen – Konstanten – sollten großgeschrieben werden. Das funktioniert bei den SystemColor-Objekten aber nicht, da es alle Bezeichnernamen schon in Großbuchstaben gibt, und zwar für Variablen vom Typ Byte, die Verweise in eine interne Tabelle darstellen.

[2] Die Seite http://www.devdaily.com/java/java-uimanager-color-keys-list liefert eine Auflistung der Schlüssel und ein Programm zur Anzeige.

Inselraus: Zeichensätze des Systems ermitteln

Um herauszufinden, welche Zeichensätze auf einem System installiert sind, liefert getAvailableFontFamilyNames() auf einem GraphicsEnvironment ein Feld mit Font-Objekten. Ein Objekt vom Typ GraphicsEnvironment beschreibt die Zeichensätze des Systems und liefert GraphicsDevice-Objekte. Ein GraphicsDevice ist eine Malfläche, also das, worauf das System zeichnen kann. Das kann der Bildschirm sein, aber auch ein Drucker oder eine Hintergrundgrafik. Die statische Fabrikmethode getLocalGraphicsEnvironment() liefert ein solches GraphicsEnvironment-Objekt.

Beispiel: Im folgenden Codesegment gibt eine Schleife alle Zeichensatznamen aus:

for ( String fonts : GraphicsEnvironment.
        getLocalGraphicsEnvironment().getAvailableFontFamilyNames() )
  System.out.println( fonts );

Auf meinem System liefert die Schleife die folgenden Ausgaben:

Arial

Arial Black

Arial Narrow

Wingdings

Wingdings 2

Wingdings 3

Zur API:

abstract class java.awt.GraphicsEnvironment

  • static GraphicsEnvironment getLocalGraphicsEnvironment()
    Liefert das aktuelle GraphicsEnvironment-Objekt.
  • abstract Font[] getAllFonts()
    Liefert ein Feld mit allen verfügbaren Font-Objekten in einer Größe von einem Punkt.
  • abstract String[] getAvailableFontFamilyNames()
    Liefert ein Feld mit allen verfügbaren Zeichensatzfamilien.
  • abstract String[] getAvailableFontFamilyNames(Locale l)
    Liefert ein Feld mit verfügbaren Zeichensatzfamilien, die zu einer Sprache l gehören.

Inselraus: n-Ecke zeichnen

In der Graphics-Klasse gibt es keine Methode, um regelmäßige n-Ecken zu zeichnen. Eine solche Methode ist aber leicht und schnell programmiert: Wir teilen dazu einfach einen Kreis in n Teile auf und berechnen die x- und y-Koordinaten der Punkte auf dem Kreis. Diese Punkte fügen wir einem Polygon-Objekt mittels der addPoint(…)-Methode hinzu. Eine eigene statische Methode drawVertex(…) übernimmt diese Polygon-Erstellung. Der letzte Parameter der Methode ist ein Wahrheitswert, der bestimmt, ob das n-Eck gefüllt werden soll oder nicht:

package com.tutego.insel.ui.graphics;

import java.awt.*;
import javax.swing.*;

public class N_Vertex extends JPanel {

  private static final long serialVersionUID = -6314283966378303073L;

  @Override protected void paintComponent( Graphics g ) {
    VertexDrawer.drawVertex( g, getWidth() / 2, getHeight() / 2, 50, 6, true );
    VertexDrawer.drawVertex( g, getWidth() / 2, getHeight() / 2, 60, 6, false );
  }

  public static void main( String[] args ) {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
    f.add( new N_Vertex() );
    f.setSize( 200, 200 );
    f.setVisible( true );
  }
}

class VertexDrawer {
  public static void drawVertex( Graphics g, int x, int y, int r, int n, boolean filled ) {
    Polygon p = new Polygon();

    for ( int i = 0; i < n; i++ )
      p.addPoint( (int) (x + r * Math.cos( i * 2 * Math.PI / n )),
                  (int) (y + r * Math.sin( i * 2 * Math.PI / n )) );

    if ( filled )
      g.fillPolygon( p );
    else
      g.drawPolygon( p );
  }
}

Inselraus: JScrollPane Viewport und Scrollable

Der Viewport

Den sichtbaren Ausschnitt der Fläche bestimmt ein JViewport-Objekt, das mit zusätzlichen Listenern etwa für die Änderungen des sichtbaren Bereichs ausgestattet werden kann. Die Methode getViewport() liefert das JViewport-Objekt. Die Methoden scrollRectToVisible(Rectangle) und setViewPosition(Point) des JViewport-Objekts ermöglichen die Ansteuerung des sichtbaren Bereichs.

Beispiel: Zeige den sichtbaren Bereich auf dem Bildschirm an:

System.out.println( scrollPane.getViewport().getVisibleRect() );

Die JViewport-Methode getVisibleRect() stammt aus der direkten Oberklasse JComponent. Sie liefert ein Rectangle-Objekt, und getLocation() liefert den java.awt.Point vom Rechteck oben links.

Jeweils auf die gegenüberliegende Seite der Rollbalken lassen sich Zeilen- und Spaltenleisten legen, genauso wie in alle vier Ecken Komponenten. Die Leisten liegen selbst wiederum in einem JViewport, um zum Beispiel im Fall einer Linealbeschriftung mitzuwandern. setRowHeaderView(), setColumnHeaderView() und setCorner() setzen bei der JScrollPane die Ecken und Leisten.

Die Schnittstelle Scrollable

Die komplexen Komponenten, wie etwa Textanzeigefelder, Bäume oder Tabellen, implementieren eine Verschiebefähigkeit nicht selbst, sondern müssen dazu in einer JScrollPane Platz nehmen. Damit JScrollPane jedoch weiß, wie zum Beispiel nach einem Klick auf den Bildlauf der Ausschnitt zu verändern ist, implementieren die Komponenten die Schnittstelle Scrollable. Die zentralen Klassen JList, JTable, JTextComponent und JTree implementieren die Schnittstelle und teilen auf diese Weise Maße der Komponente und Anzahl der Pixel bei einer Verschiebung mit, wenn etwa der Anwender den Rollbalken um eine Position versetzt.

Feature: Eine sinnvolle Eigenschaft ist der automatische Bildlauf. Bei diesem Verfahren wird der Bildlauf auch dann fortgesetzt, wenn der Mauszeiger die Komponente schon verlassen hat. Klickt der Benutzer etwa auf ein Element in einer Liste, und bewegt dann den Mauszeiger mit gedrückter Maustaste aus der Liste heraus, so scrollt die Liste mit eingeschaltetem automatischen Bildlauf selbstständig weiter. Die Eigenschaft wird in Swing über die JComponent-Methode setAutoScroll(boolean) gesteuert.

Inselraus: Bilder auf AbstractButton-basierten Swing-Schaltflächen je nach Zustand ändern

Die Integration mit den Icon-Objekten liegt in der AbstractButton-Klasse. Geben wir im Konstruktor das Icon nicht an, so lässt sich dies immer noch über setIcon(…) nachträglich setzen und ändern. Wenn die Schaltfläche angeklickt wird, kann ein anderes Bild erscheinen. Dieses Icon setzt setPressedIcon(…). Bewegen wir uns über die Schaltfläche, lässt sich auch ein anderes Icon setzen. Dazu dient die Methode setRolloverIcon(…). Die Fähigkeit muss aber erst mit setRolloverEnabled(true) eingeschaltet werden. Beide Eigenschaften lassen sich auch zu einem Icon kombinieren, das erscheint, wenn die Maus über dem Bild ist und eine Selektion gemacht wird. Dazu dient setRolloverSelectedIcon(…). Für JToggleButton-Objekte ist eine weitere Methode wichtig, denn ein JToggleButton hat zwei Zustände: einen selektierten und einen nicht selektierten. Auch hier können zwei Icon-Objekte zugeordnet werden, und das Icon der Selektion lässt sich mit setSelectedIcon(…) setzen. Ist die Schaltfläche ausgegraut, ist auch hier ein gesondertes Icon möglich. Es wird mit setDisabledIcon(…) gesetzt. Dazu passt setDisabledSelectedIcon(…).

Inselraus: Die Schnittstelle Icon und eigene Icons zeichnen

Bei einer genauen Betrachtung fällt auf, dass ImageIcon eine Implementierung der Schnittstelle Icon ist und dass die JLabel-Klasse ein Icon-Objekt erwartet und nicht speziell ein Argument vom Typ ImageIcon. Das heißt aber: Wir können auch eigene Icon-Objekte zeichnen. Dazu müssen wir nur drei spezielle Methoden von Icon implementieren: die Methode paintIcon(…) und ferner zwei Methoden, die die Dimensionen angeben.

interface interface javax.swing.Icon

  • int getIconWidth()
    Liefert die feste Breite eines Icons.
  • int getIconHeight()
    Liefert die feste Höhe eines Icons.
  • void paintIcon(Component c, Graphics g, int x, int y)
    Zeichnet das Icon an die angegebene Position. Der Parameter Component wird häufig nicht benutzt. Er kann jedoch eingesetzt werden, wenn weitere Informationen beim Zeichnen bekannt sein müssen, wie etwa die Vorder- und Hintergrundfarbe oder der Zeichensatz.

Die folgende Klasse zeigt die Verwendung der Icon-Schnittstelle. Das eigene Icon soll einen einfachen roten Kreis mit den Ausmaßen 20 × 20 Pixel besitzen:

class CircleIcon implements Icon {

  @Override public void paintIcon( Component c, Graphics g, int x, int y )  {
    g.setColor( Color.red );
    g.fillOval( x, y, getIconWidth(), getIconHeight() );
  }

  @Override public int getIconWidth() {
    return 20;
  }

  @Override public int getIconHeight() {
    return 20;
  }
}

Wir überschreiben die drei erforderlichen Methoden, sodass ein Icon-Objekt der Größe 20 × 20 Pixel entsteht. Als Grafik erzeugen wir einen gefüllten roten Kreis. Dieser kann als Stopp-Schaltfläche verwendet werden, ohne dass wir eine spezielle Grafik verwenden müssen. Für die Grafik stehen uns demnach 400 Pixel zur Verfügung – genau getIconWidth() mal getIconHeight() –, und alle nicht gefüllten Punkte liegen transparent auf dem Hintergrund. Dies ist auch typisch für leichtgewichtige Komponenten. Über das Component-Objekt können wir weitere Informationen herausholen, wie etwa den aktuellen Zeichensatz oder die darstellende Vaterkomponente.

Um das eigene Icon auf ein JLabel zu setzen, lässt sich die Icon-Implementierung entweder im Konstruktor übergeben oder später über setIcon(Icon):

JLabel circle = new JLabel( new CircleIcon() );

Was die Typen Icon und Image verbindet

Vielleicht wird der eine oder andere sich schon überlegt haben, ob nun ImageIcon eine ganz eigene Implementierung neben der Image-Klasse ist oder ob beide miteinander verwandt sind. Das Geheimnis ist, dass ImageIcon die Icon-Schnittstelle implementiert, aber auch ImageIcon intern die Image-Klasse nutzt. Sehen wir uns das einmal im Detail an: Ein ImageIcon ist serialisierbar. Also implementiert es die Schnittstelle Serializable. Im Konstruktor kann ein URL-Objekt oder ein String mit einer URL stehen. Hier wird einfach getImage(…) vom Toolkit aufgerufen, um sich eine Referenz auf das Image-Objekt zu holen. Eine geschützte Methode loadImage(Image) wartet nun mithilfe eines MediaTrackers auf das Bild. Nachdem dieser auf das Bild gewartet hat, setzt er die Höhe und Breite, die sich dann über die Icon-Methoden abfragen lassen. Doch ein richtiges Icon muss auch paintIcon(…) implementieren. Hier verbirgt sich nur die drawImage(…)-Methode.

Kommen wir noch einmal auf die Serialisierbarkeit der ImageIcon-Objekte zurück. Die Klasse implementiert dazu die privaten Methoden readObject(…) und writeObject(…). Der Dateiaufbau ist sehr einfach. Breite und Höhe befinden sich im Datenstrom, und anschließend existiert ein Integer-Feld mit den Pixelwerten. In readObject(…) liest s.defaultReadObject() – wobei s der aktuelle ObjectInputStream ist – das Feld wieder ein, und über die Toolkit-Methode createImage(…) wird die Klasse MemoryImageSource genutzt, um das Feld wieder zu einem Image-Objekt zu konvertieren. Umgekehrt ist es genauso einfach. writeObject(…) schreibt die Breite und Höhe und anschließend das Ganzzahl-Feld mit den Farbinformationen, das es über einen PixelGrabber bekommen hat.

Inselraus: Zeichensatz eines Swing-Labels ändern

Der gesetzte Text wird im zugewiesenen Zeichensatz des Swing-Look-and-Feels angezeigt. Um diesen zu ändern, müssen wir ein neues Font-Objekt erzeugen. Auf zwei Arten lässt sich dieser Font setzen: global für alle JLabel-Elemente oder lokal nur für dieses eine. Die erste Lösung arbeitet über das UIDefaults-Objekt, das die Einstellungen wie Zeichensätze und Farben für alle Swing-Elemente verwaltet:

UIDefaults uiDefaults = UIManager.getDefaults();
uiDefaults.put( "Label.font",
                ((Font)uiDefaults.get("Label.font")).deriveFont(30f) );

Unter dem Schlüssel »Label.font« legen wir ein neues Font-Objekt ab und überschreiben die alte Definition. Den neuen Font mit der Größe 30 leiten wir mit deriveFont(float) vom alten ab, sodass wir den Zeichensatz »erben«.

Die zweite Lösung kann darin bestehen, den Font direkt mit der setFont(Font)-Methode zu setzen:

JLabel l = new JLabel( "Lebe immer First-Class, sonst tun es deine Erben!" );
l.setFont( new Font("Serif", Font.BOLD, 30) );

Einen speziellen Konstruktor, der ein Font-Objekt als Argument annimmt und dieses verwendet, gibt es nicht.

Inselraus: Dynamisches Layout während einer Größenänderung

Wird ein Fenster vergrößert, dann kann während der Größenänderung der Inhalt sofort neu ausgerichtet und gezeichnet werden oder auch nicht. Wird er nicht dynamisch angepasst, dann sieht der Benutzer diese Anpassung erst nach dem Loslassen der Maus, wenn die Größenänderung abgeschlossen wurde. Dieses dynamische Vergrößern lässt sich im Toolkit-Objekt einstellen, und zwar über Toolkit.getDefaultToolkit().setDynamicLayout(true). Nicht jedes Toolkit unterstützt allerdings diese Fähigkeit! Ob es das tut, verrät Toolkit.getDefaultToolkit().getDesktopProperty("awt.dynamicLayoutSupported").

Inselraus: Selektionen einer Tabelle

In einer JTable können auf unterschiedliche Art und Weise Zellen selektiert werden: zum einen nur in einer Zeile oder Spalte, zum anderen kann auch ein ganzer Block oder können auch beliebige Zellen selektiert werden. Die Art der Selektion bestimmen Konstanten in ListSelectionModel. So wird SINGLE_SELECTION nur die Selektion einer einzigen Zelle zulassen.

Beispiel: In einer JTable soll entweder ein ununterbrochener Block Zeilen oder Spalten ausgewählt werden dürfen:

table.setSelectionMode( ListSelectionModel.SINGLE_INTERVAL_SELECTION );

Mit Methoden lassen sich im Programm alle Elemente einer Spalte oder Zeile selektieren. Die Selektier-Erlaubnis geben zunächst zwei Methoden:

  • table.setColumnSelectionAllowed( boolean )
  • table.setRowSelectionAllowed( boolean )

Die automatische Selektion von Spalten gelingt mit der JTable-Methode setColumnSelectionInterval(int, int), weitere Bereiche lassen sich mit addColumnSelectionInterval(int, int) hinzufügen und mit removeColumnSelectionInterval(int, int) entfernen. Das Gleiche gilt für die Methoden, die »Row« im Methodennamen tragen.

Beispiel: Selektiere in einer JTable table die Spalte 0 komplett:

table.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( false );
table.setColumnSelectionInterval( 0, 0 );

Selektiere in einer Tabelle nur die Zelle 38, 5:

table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION  );
table.setColumnSelectionAllowed( true );
table.setRowSelectionAllowed( true );
table.changeSelection( 38, 5, false, false );

Als Selektionsmodus reicht SINGLE_SELECTION aus, MULTIPLE_INTERVAL_SELECTION wäre aber auch in Ordnung. Beide Selektionen sind zusammen in der Form nicht möglich. Bei einer Einzelselektion wird die Zelle nur umrandet, aber nicht wie beim Standard-Metal-Look-and-Feel blau ausgefüllt.

Die Methode selectAll() selektiert alle Elemente, und clearSelection() löscht alle Selektionen.

Inselraus: Tabellenkopf von Swing-Tabellen

Diese Verschiebung von Spalten kann über das Programm erfolgen (mit moveColumn(int column, int targetColumn) von JTable) oder über den Benutzer per Drag & Drop. JTable delegiert diese Methoden an ein spezielles TableColumnModel und implementiert den Code nicht selbst.

Der Kopf (engl. header) einer JTable ist ein JTableHeader-Objekt, das von der JTable mit getTableHeader() erfragt werden kann. Dieses JTableHeader-Objekt ist für die Anordnung und Verschiebung der Spalten verantwortlich.

Beispiel: In der JTable table sollen die Spalten nicht mehr vom Benutzer verschoben werden können. Er soll auch die Breite nicht mehr ändern dürfen:

table.getTableHeader().setReorderingAllowed( false );
table.getTableHeader().setResizingAllowed( false );

Hier wird deutlich, dass ein JTableHeader die Steuerung der Ausgabe und der Benutzerinteraktion übernimmt, dass aber die Informationen selbst in TableColumn liegen.

Inselraus: Spalteninformationen von JTable-Tabellen

Alle Zelleninformationen der Tabelle stecken im Model einer JTable. Informationen über die Spalten stehen allerdings nicht im TableModel, sondern in Objekten vom Typ TableColumn. Jede Spalte bekommt ein eigenes TableColumn-Objekt, und eine Sammlung der Objekte bildet das TableColumnModel, das wie das TableModel ein Datencontainer der JTable ist.

Beispiel: Zähle alle TableColumn-Objekte einer JTable table auf.

for ( Enumeration<?> e = table.getColumnModel().getColumns(); e.hasMoreElements(); )
  System.out.println( (TableColumn)e.nextElement() );

Vom Spaltenmodell der Tabelle bezieht getColumns()eine Enumeration von TableColumn-Objekten. Soll ein ganz bestimmtes TableColumn-Objekt untersucht werden, kann auch die Methode getColumn(Object identifier) genutzt werden.

Liegt ein TableColumn-Objekt vor, lässt sich von diesem die aktuelle minimale und maximale Breite setzen.

Beispiel: Ändere die Breite der ersten Spalte auf 100 Pixel:

table.getColumnModel().getColumn( 0 ).setPreferredWidth( 100 );

AUTO_RESIZE

Verändert der Anwender die Breite einer Spalte, ändert er entweder die Gesamtbreite einer Tabelle, oder er ändert automatisch die Breite der anderen Spalten, um die Gesamtbreite nicht zu verändern. Hier gibt es für die JTable unterschiedliche Möglichkeiten, die eine Methode setAutoResizeMode(int mode) bestimmt. Erlaubte Modi sind Konstanten aus JTable und AUTO_RESIZE_OFF, AUTO_RESIZE_NEXT_COLUMN, AUTO_RESIZE_SUBSEQUENT_COLUMNS, AUTO_RESIZE_ LAST_COLUMN, AUTO_RESIZE_ALL_COLUMNS. Sinnvoll sind drei von ihnen:

  • AUTO_RESIZE_SUBSEQUENT_COLUMNS: Der Standard. Verändert gleichmäßig die Breiten aller rechts liegenden Spalten.
  • AUTO_RESIZE_NEXT_COLUMN: Ändert nur die Breite der nachfolgenden Spalte.
  • AUTO_RESIZE_OFF: Ändert die Größe der gesamten Tabelle. Ist nur sinnvoll, wenn die JTable in einer JScrollPane liegt.

Inselraus: Größe und Umrandung von JTable-Zellen

Jede Zelle hat eine bestimmte Größe, die durch den Zellinhalt vorgegeben ist. Zusätzlich liegt zwischen zwei Zellen immer etwas Freiraum. Dieser lässt sich mit getIntercellSpacing() erfragen und mit setIntercellSpacing(…) setzen:

 table.setIntercellSpacing( new Dimension(gapWidth, gapHeight) ); 

Soll die Zelle rechts und links zum Beispiel 2 Pixel Freiraum bekommen, ist gapWidth auf 4 zu setzen, denn das Dimension-Objekt gibt immer den gesamten vertikalen und horizontalen Abstand zwischen den Zellen an.

Die Gesamtgröße einer Zelle ist dann die der Margin-Zeile + Zellhöhe beziehungsweise Margin-Spalte + Zellbreite. Da jedoch setIntercellSpacing(…) die Höhe einer Zeile nicht automatisch anpasst, muss sie ausdrücklich gesetzt werden:

 table.setRowHeight( table.getRowHeight() + gapHeight ); 

Zusätzlich zur Margin erhöht eine Linie den Abstand zwischen den Zellen. Auch dieses Raster (engl. grid) lässt sich modifizieren:

Code

Funktion

table.setShowGrid( false );

Schaltet die Umrandung aus.

table.setShowGrid( false );
table.setShowVerticalLines( true );

Zeigt nur vertikale Linien.

table.setGridColor( Color.GRAY );

Die Umrandung wird grau.

Rastermodifizierung

NetBeans 7.3 final und aus der Beta-Phase

Details und Download hier: http://netbeans.org/community/releases/73/index.html ,http://wiki.netbeans.org/NewAndNoteworthyNB73. Im Java-Bereich hat sich gar nicht so viel getan, eher im Umfeld der Web-Entwicklung mit HTML5. Das zeigt irgendwie für mich klar, wohin die Reise in der Entwicklung geht. Ich hätte es schön gefunden, wenn die neuen Sprachfeatures von Java 8 schon integriert würde, aber hier muss man auf die nächste Version warten. Bei Eclipse passiert gefühlt nicht so viel, IntelliJ und NB legen gutes Tempo vor.

Spaß mit Generics und funktionalen Schnittstellen

Funktionale Schnittstellen müssen auf genau eine zu implementierende Methode hinauslaufen, auch wenn aus Oberschnittstellen mehrere Operationen vorgeschrieben werden, die aber durch Einsatz von Generics auf eine Operation verdichten:

interface I<S, T extends CharSequence> {

  void len( S text );

  void len( T text );

}

interface FI extends I<String, String> { }

FI ist unsere funktionale Schnittstelle, da die Signatur der Methode eindeutig ist: len(String).