Rekursiv nach Dateien/Ordnern suchen mit Files.find(…)

Neu in Java 8 ist die Methode find(…) in Files um Dateien nach gewissen Kriterien zu finden.

final class java.nio.file.Files

  • static Stream<Path> find(Path start, int maxDepth, BiPredicate<Path,BasicFileAttributes> matcher, FileVisitOption… options) throws IOException
    Sucht einen Verzeichnisbaum rekursiv ab und wendet auf jede Path den Filter (Prädikat) an. Falls der Filter zusagt, kommt der Path in den Ergebnis-Stream.

Beispiel und Hinweis: Finde alle Ordner unter dem Standard-Windows Bilder-Verzeichnis und gib sie aus:

Files.find( Paths.get( System.getProperty( „user.home“ ) )

.resolve( „Pictures“ ),

Integer.MAX_VALUE,

(p,attr) -> Files.isReadable( p ) && attr.isDirectory()

).forEach( System.out::println );

Intern greift find(…) auf den gleichen Mechanismus wie walk(…) zurück, doch ist eine Eigenimplementierung mit Hilfe von walk(…) mitunter besser, da wir beim visitFileFailed(…) Fehler ignorieren können – bei find(…) führen Fehler direkt zum Abbruch. Bei Windows führt eine rekursive Suche schnell zu einem java.nio.file.AccessDeniedException durch einen Ordner, bei dem Java nicht dran darf und dann ist mit find(…) sofort Schluss.

Rekursives Ablaufen des Verzeichnisbaums mit Stream oder FileVisitor

Die Utility-Klasse Files bietet vier statische Methoden (zwei mehr in Java 8), die, bei einem Startordner beginnend, die Verzeichnisse rekursiv ablaufen.

final class java.nio.file.Files

  • static Stream<Path> walk(Path start, FileVisitOption… options) throws IOException
  • static Stream<Path> walk(Path start, int maxDepth, FileVisitOption… options) throws IOException
  • static Path walkFileTree(Path start, FileVisitor<? super Path> visitor)
  • static Path walkFileTree(Path start, Set<FileVisitOption> options, int maxDepth, FileVisitor<? super Path> visitor)

Bei allen Varianten bestimmt der erste Parameter den Startordner. Während walk(…)einen java.util.stream.Stream liefert (die Methoden sind neu in Java 8), erwarten die anderen beiden walkFileTree(…)-Methoden ein Objekt mit Callback-Methoden, die walkFileTree(…) beim Ablaufen des Verzeichnisbaums aufruft

Verzeichnislistings (DirectoryStream/Stream) holen

In der Klasse Files finden sich vier Methoden (eine mehr unter Java 8), um zu einem gegebenen Verzeichnis alle Dateien und Unterverzeichnisse aufzulisten:

final class java.nio.file.Files

  • static Stream<Path> list(Path dir) throws IOException (neu in Java 8)
  • static DirectoryStream<Path> newDirectoryStream(Path dir) throws IOException
  • static DirectoryStream<Path> newDirectoryStream(Path dir,
    DirectoryStream.Filter<? super Path> filter) throws IOException
  • static DirectoryStream<Path> newDirectoryStream(Path dir, String glob) throws IOException

Die Rückgabe DirectoryStream<T> ist ein Closeable (und somit AutoCloseable) sowie Iterable<T>, und so unterscheidet sich die Möglichkeit zur Anfrage der Dateien im Ordner grundsätzlich von der Methode list(…) in der Klasse File, die immer alle Dateien in einem Feld auf einmal zurückliefert. Bei einem DirectoryStream wird Element für Element über den Iterator geholt; trotz des Namensanhangs „Stream“ ist der DirectoryStream kein Strom im Sinne von java.util.stream. Ein Stream<String> hingegen liefert die kompakte Methode list(Path), sie nutzt intern einen DirectoryStream.

try ( DirectoryStream<Path> files =
Files.newDirectoryStream( Paths.get( „c:/“ ) ) ) {
  for ( Path path : files )
    System.out.println( path.getFileName() );
}

Aus der Tatsache, dass die Dateien und Unterverzeichnisse nicht in einem Rutsch geholt werden, leitet sich die Konsequenz ab, dass der DirectoryStream/Stream<String> geschlossen werden muss, da nicht klar ist, ob der Benutzer wirklich alle Dateien abholt oder nach den ersten 10 Einträgen aufhört. Die Schnittstelle DirectoryStream erweitert die Schnittstelle Closeable (und die ist AutoCloseable, weshalb unser Beispiel ein try-mit-Ressourcen nutzt) und Stream implementiert AutoCloseable, daher ist es guter Stil, den DirectoryStream/Stream am Ende zu schließen, um blockierte Ressourcen freizugeben. try-mit-Ressourcen gibt immer etwaige Ressourcen frei, auch wenn es beim Ablaufen des Verzeichnisses zu einer Ausnahme kam.

Dateiinhalte lesen/schreiben mit Utility-Klasse Files

Da die Klasse Path nur Pfade, aber keine Dateiinformationen wie die Länge oder Änderungszeit repräsentiert und Path auch keine Möglichkeit bietet, Dateien anzulegen und zu löschen, übernimmt die Klasse Files diese Aufgaben.

Einfaches Einlesen und Schreiben von Dateien

Mit den Methoden readAllBytes(…), readAllLines(…), lines(…) bzw. write(…) kann Files einfach ein Dateiinhalt einlesen oder Strings bzw. ein Byte-Feld schreiben.

URI uri = ListAllLines.class.getResource( „/lyrics.txt“ ).toURI();
Path path = Paths.get( uri );
System.out.printf( „Datei ‚%s‘ mit Länge %d Byte(s) hat folgende Zeilen:%n“, path.getFileName(), Files.size( path ) );
int lineCnt = 1;
for ( String line : Files.readAllLines( path /*, StandardCharsets.UTF_8 vor Java 8 */) )
  System.out.println( lineCnt++ + „: “ + line );

final class java.nio.file.Files

  • static long size(Path path) throws IOException
    Liefert die Größe der Datei.
  • static byte[] readAllBytes(Path path) throws IOException
    Liest die Datei komplett in ein Byte-Feld ein.
  • static List<String> readAllLines(Path path) throws IOException (Java 8)
    static List<String> readAllLines(Path path, Charset cs) throws IOException
  • Liest Zeile für Zeile die Datei ein und liefert eine Liste dieser Zeilen. Optional ist die Angabe einer Kodierung, standardmäßig ist es StandardCharsets.UTF_8.
  • static Path write(Path path, byte[] bytes, OpenOption… options) throws IOException
    Schreibt eine Byte-Feld in eine Datei.
  • static Path write(Path path, Iterable<? extends CharSequence> lines,
    OpenOption… options) throws IOException (Java 8)
  • static Path write(Path path, Iterable<? extends CharSequence> lines, Charset cs,
    OpenOption… options) throws IOException
    Schreibt alle Zeilen aus dem Iterable in eine Datei. Optional ist die Kodierung, die StandardCharsets.UTF_8 ist, wenn nicht anders angegeben.
  • static Stream<String> lines(Path path)
  • Stream<String> lines(Path path, Charset cs)
    Liefert einen Stream von Zeilen einer Datei. Optional ist die Angabe der Kodierung, die sonst standardmäßig StandardCharsets.UTF_8 ist. Beide Methoden sind neu in Java 8.

Die Aufzählung OpenOption ist ein Vararg, und daher sind Argumente nicht zwingend nötig. StandardOpenOption ist eine Aufzählung vom Typ OpenOption mit Konstanten wie APPEND, CREATE, …

Hinweis: Auch wenn es naheliegt, die Files-Methode zum Einlesen mit einem Path-Objekt zu füttern, das ein HTTP-URI repräsentiert, funktioniert dies nicht. So liefert schon die erste Zeile des Programms eine Ausnahme des Typs »java.nio.file.FileSystemNotFoundException: Provider „http“ not installed«.

Path path = Paths.get( new URI „http://tutego.de/javabuch/aufgaben/bond.txt“ ) );
List<String> content = Files.readAllBytes( path );
System.out.println( content );

Vielleicht kommt in der Zukunft ein Standard-Provider von Oracle, doch es ist davon auszugehen, dass quelloffene Lösungen diese Lücke schließen werden. Schwer zu programmieren sind Dateisystem-Provider nämlich nicht.

Datenströme kopieren

Sollen die Daten nicht direkt aus einer Datei in eine byte-Feld/String-Liste gehen bzw. aus einer byte-Feld/String-Sammlung in eine Datei, sondern von einer Datei in einen Datenstrom, so bieten sich zwei copy(…)-Methoden an:

final class java.nio.file.Files

  • static long copy(InputStream in, Path target, CopyOption… options)
    Entleert den Eingabestrom und kopiert die Daten in die Datei.
  • static long copy(Path source, OutputStream out)
    Kopiert alle Daten aus der Datei in den Ausgabestrom.

Zeichenorientierte Datenströme über Files beziehen

Neben den statischen Files-Methoden newOutputStream(…) und newInputStream(…) gibt es zwei Methoden, die zeichenorientierte Ströme liefern, also Writer/Reader.

final abstract java.nio.file.Files

  • static BufferedReader newBufferedReader(Path path, Charset cs)
    throws IOException
  • static BufferedWriter newBufferedWriter(Path path, Charset cs, OpenOption… options)
    throws IOException
    Liefert einen Unicode-zeichenlesenden Ein-/Ausgabestrom. Das Charset-Objekts bestimmt, in welcher Zeichenkodierung sich die Texte befinden, damit sie korrekt in Unicode konvertiert werden.
  • static BufferedReader newBufferedReader(Path path)
    throws IOException
    Entspricht newBufferedReader(path, StandardCharsets.UTF_8). Erst in Java 8.
  • static BufferedWriter newBufferedWriter(Path path, OpenOption… options)
    throws IOException
    Entspricht Files.newBufferedWriter(path, StandardCharsets.UTF_8, options). Erst in Java 8,

BufferedReader und BufferedWriter sind Unterklassen von Reader/Writer die zum Zwecke der Optimierung Dateien im internen Puffer zwischenspeichern.

MAC-Adressen auslesen

Die MAC-Adresse (von Media-Access-Control) ist eine (im Idealfall) eindeutige Adresse eines Netzwerkgeräts. MAC-Adressen sind für Ethernet-Verbindungen essenziell, da auf der physikalischen Übertragungsebene Signale zu einer gewünschten Netzwerkkarte aufgebaut werden. Wegen der Eindeutigkeit eignen sie sich gut als Schlüssel, und es ist interessant, auch in Java diese Adresse auszulesen. Das geht mit der Klasse NetworkInterface recht unkompliziert. Alle lokale Netzwerkschnittstellen liefert NetworkInterface.getNetworkInterfaces(), ist die IP-Adresse bekannt, können wir NetworkInterface.getByInetAddress(InetAddress) nutzen.

for ( NetworkInterface ni : Collections.list( NetworkInterface.getNetworkInterfaces() ) ) {
  byte[] adr = ni.getHardwareAddress();
  if ( adr == null || adr.length != 6 )
    continue;
  String mac = String.format( "%02X:%02X:%02X:%02X:%02X:%02X",
                                    adr[0], adr[1], adr[2], adr[3], adr[4], adr[5] );
  System.out.println( mac );
}

Auf der Windows Kommandozeile liefert ipconfig /all alle MAC-Adressen, die dort „physikalische Adresse” heißen.

JavaFX: Put a draggable, resizable polygon on an image

import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.EventHandler;
import javafx.scene.Cursor;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Polygon;
import javafx.stage.Stage;

class DrawingPane extends Pane
{
  public DrawingPane( Image image, Polygon poly )
  {
    poly.setFill( Color.web( "ANTIQUEWHITE", 0.8 ) );
    poly.setStroke( Color.web( "ANTIQUEWHITE" ) );
    poly.setStrokeWidth( 2 );

    getChildren().addAll( new ImageView( image ), poly );

    for ( int i = 0; i < poly.getPoints().size(); i += 2 ) {
      Circle circle = new Circle( poly.getPoints().get( i ), poly.getPoints().get( i + 1 ), 5 );
      circle.setFill( Color.web( "PERU", 0.8 ) );
      circle.setStroke( Color.PERU );
      circle.setStrokeWidth( 2 );

      AtomicInteger polyCoordinateIndex = new AtomicInteger( i );
      circle.centerXProperty().addListener( new ChangeListener<Number>() {
        @Override
        public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue ) {
          poly.getPoints().set( polyCoordinateIndex.get(), newValue.doubleValue() );
        }
      } );
      circle.centerYProperty().addListener( new ChangeListener<Number>() {
        @Override
        public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue ) {
          poly.getPoints().set( polyCoordinateIndex.get() + 1, (Double) newValue );
        }
      } );
      setDragHandler( circle );
      getChildren().add( circle );
    }
  }

  private double dragDeltaX, dragDeltaY; 

  private void setDragHandler( Circle circle )
  {
    circle.setOnMousePressed( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        dragDeltaX = circle.getCenterX() - mouseEvent.getSceneX();
        dragDeltaY = circle.getCenterY() - mouseEvent.getSceneY();
      }
    } );

    circle.setOnMouseDragged( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCenterX( mouseEvent.getSceneX() + dragDeltaX );
        circle.setCenterY( mouseEvent.getSceneY() + dragDeltaY );
        circle.setCursor( Cursor.MOVE );
      }
    } );

    circle.setOnMouseEntered( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCursor( Cursor.HAND );
      }
    } );

    circle.setOnMouseReleased( new EventHandler<MouseEvent>() {
      @Override public void handle( MouseEvent mouseEvent ) {
        circle.setCursor( Cursor.HAND );
      }
    } );
  }
}

public class JavafxDemo extends Application
{
  @Override
  public void start( Stage stage )
  {
    Image image = new Image( "http://tours-tv.com/uploads/maps/map-Medizinische-Hochschule-Hannover-karta.jpg" );
    Polygon poly = new Polygon( 10, 10, 100, 10, 200, 100, 50, 200 );

    stage.setScene( new Scene( new DrawingPane( image, poly ), 450, 300 ) );
    stage.show();
  }

  public static void main( String[] args )
  {
    launch( args );
  }
}

Example for JavaFX CubicCurve with animation

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.CubicCurve;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import javafx.util.Duration;

public class CubicCurveDemo extends Application
{
  @Override
  public void start( Stage stage )
  {
    double startX = 200, startY = 200;
    DoubleProperty controlX1 = new SimpleDoubleProperty( 20 );
    DoubleProperty controlY1 = new SimpleDoubleProperty( 20 );
    DoubleProperty controlX2 = new SimpleDoubleProperty( 400 );
    DoubleProperty controlY2 = new SimpleDoubleProperty( 20 );
    double endX = 300, endY = 200;

    // Linie von [controlX1, controlY1] nach [startX, startY]
    Line line1 = new Line( 0, 0, startX, startY );
    line1.startXProperty().bind( controlX1 );
    line1.startYProperty().bind( controlY1 );
    line1.setStrokeWidth( 2 );

    // Linie von [controlX2, controlY2] nach [endX, endY]
    Line line2 = new Line( 0, 0, endX, endY );
    line2.startXProperty().bind( controlX2 );
    line2.startYProperty().bind( controlY2 );
    line2.setStrokeWidth( 2 );

    // Animierte Kontrollpunkte
    Timeline timeline = new Timeline( new KeyFrame( Duration.millis( 1000 ),
                                        new KeyValue( controlX1, 300 ),
                                        new KeyValue( controlY2, 300 ) ) );
    timeline.setCycleCount( Timeline.INDEFINITE );
    timeline.setAutoReverse( true );
    timeline.play();

    CubicCurve curve = new CubicCurve( startX, startY, 0, 0, 0, 0, endX, endY );
    curve.controlX1Property().bind( controlX1 );
    curve.controlY1Property().bind( controlY1 );
    curve.controlX2Property().bind( controlX2 );
    curve.controlY2Property().bind( controlY2 );
    curve.setFill( null );
    curve.setStroke( Color.BLUEVIOLET );
    curve.setStrokeWidth( 3 );

    stage.setScene( new Scene( new Group( line1, line2, curve ), 450, 300 ) );
    stage.show();
  }

  public static void main( String[] args )
  {
    launch( args );
  }
}

Inselraus: Swing/AWT-Bilder im Speicher erzeugen

Nicht immer kommen die Bilder vom Datensystem oder aus dem Internet. Mit der Java-Bibliothek lassen sich einfach auch eigene (Buffered)Image-Objekte anlegen. Dazu bieten sich – wieder historisch bedingt – verschiedene Varianten an:

  • Jede AWT-Komponente, wie Frame oder Panel, bietet die Methode createImage(…). Die Anweisung Image image = panel.createImage(800, 600); erzeugt ein Image-Objekt mit 800 Pixeln in der Breite und 600 in der Höhe, das mit getGraphics() Zugriff auf den Grafikkontext bietet. Wenn die AWT-Komponente noch nicht angezeigt wurde, liefert createImage(…) die Rückgabe null, sodass hier leicht eine NullPointerException entstehen kann. Auch unterstützen die Bilder keine Transparenz.
  • Java 1.2 führte die Klasse BufferedImage ein, die eine Erweiterung der Image-Klasse ist. Image ist eine abstrakte Klasse und BufferedImage eine konkrete nicht abstrakte Unterklasse. Ein zentraler Unterschied ist, dass der Zugriff auf die Pixel von BufferedImage-Objekten einfach ist, weil sie auf der Java-Seite in Byte-Arrays gespeichert sind, dass aber der Zugriff auf die Pixel von Image-Objekten schwierig ist, da Image-Objekte vom Betriebssystem kommen. Bei BufferedImage ist die Manipulation der Pixel einfach. Die Klasse bietet drei Konstruktoren. Beim Erzeugen ist immer ein Bildtyp anzugeben, der über die physikalische Speicherung bestimmt.[1]
  • createCompatibleImage(…) über GraphicsConfiguration erzeugt ein BufferedImage und benötigt keinen Bildtyp.
BufferedImage erzeugen lassen

Ein Bild über createCompatibleImage(…) zu erzeugen, hat den großen Vorteil, dass das Daten- und Farbmodell optimal gewählt ist. Der einzige Nachteil dieser Methode ist die große Menge an benötigten Hilfsobjekten – was zusätzliche Schreibarbeit bedeutet:

GraphicsConfiguration gfxConf = GraphicsEnvironment.getLocalGraphicsEnvironment().
    getDefaultScreenDevice().getDefaultConfiguration();
int width = 600, height = 400;
BufferedImage image = gfxConf.createCompatibleImage( width, height );

Neben createCompatibleImage(int, int) gibt es auch eine Variante, die die Angabe einer Transparenz ermöglicht.

abstract class java.awt.GraphicsConfiguration

  • abstract BufferedImage createCompatibleImage(int width, int height)
    Erzeugt ein BufferedImage.
  • BufferedImage createCompatibleImage(int width, int height, int transparency)
    Erzeugt ein BufferedImage mit optionaler Transparenz. Das Argument für transparency kann sein: Transparency.OPAQUE (keine Transparenz, der Alpha-Wert ist 1,0), Transparency.BITMASK (Bilddaten sind komplett sichtbar, also opak mit Alpha-Wert 1, oder transparent, also Alpha-Wert 0), Transparency.TRANSLUCENT (Grafik erlaubt das Durchscheinen mit Alpha-Werten von 0,0 bis 1,0).
Das Bild bemalen

Image-Objekte (BufferedImage ist eine Unterklasse) geben über getGraphics() das Graphics-Objekt zurück, mit dem sich das Bild bemalen lässt. Im Fall eines speziellen BufferedImage-Objekts ist es jedoch üblich, die Methode createGraphics() einzusetzen, da sie ein Graphics2D-Objekt – eine Unterklasse von Graphics – liefert, mit dem weitere Zeichenoperationen möglich sind. Außerdem ruft getGraphics() sowieso createGraphics() auf …

Beispiel: Initialisiere ein Bild img mit weiß.

Graphics2D g = img.createGraphics();
g.setColor( Color.WHITE );
g.fillRect( 0, 0, b – 1, h – 1 );

Alternativ kann zum Löschen des Hintergrunds auch g.setBackground(Color.WHITE); g.clearRect(…); verwendet werden.

BufferedImage von Hand erzeugen

Der Konstruktor der Klasse BufferedImage wird mit den Maßen parametrisiert und zusätzlich mit einem Speichermodell für die Bildinformationen. Das ermöglicht die Verwendung von beliebigen Farb- und Speichermodellen:

int h = 400,
    b = 600;
BufferedImage img = new BufferedImage( b, h, BufferedImage.TYPE_INT_RGB );

Das notwendige dritte Argument kennzeichnet den Speichertyp; hier sind die Farben durch je 8 Bit Rot, Grün und Blau abgebildet. Um weitere 2 der über 10 Bildtypen zu nennen: TYPE_USHORT_GRAY (Graubilder) oder TYPE_INT_ARGB (RGB mit jeweils 8 Bit sowie Alpha).

class java.awt.image.BufferedImage
extends Image
implements RenderedImage, Transparency, WritableRenderedImage

  • BufferedImage(int width, int height, int imageType)
    Liefert ein neues Hintergrundbild mit den gegebenen Maßen.

[1] Details finden Sie unter http://weblogs.java.net/blog/chet/archive/2004/08/toolkitbuffered.html.

Inselraus: Neue TrueType-Fonts in AWT/Swing nutzen

Die auf allen Systemen vordefinierten Standardzeichensätze sind etwas dürftig, obwohl die Font-Klasse selbst jeden installierten Zeichensatz einlesen kann. Da ein Java-Programm aber nicht von der Existenz eines bestimmten Zeichensatzes ausgehen kann, ist es praktisch, einen Zeichensatz mit der Installation auszuliefern und dann diesen zu laden; das kann die Font-Klasse mit der statischen Methode createFont(…) sein. Aus einem Eingabestrom liest die Methode den TrueType-Zeichensatz und erstellt das entsprechende Font-Objekt, Bsp.:

Font font = Font.createFont( Font.TRUETYPE_FONT,
                             getClass().getResourceAsStream( „/NASALIZA.TTF“) );

Das erste Argument ist immer Font.TRUETYPE_FONT. Das zweite Argument bestimmt den Eingabestrom zur Binärdatei mit den Zeichensatzinformationen. Die Daten werden ausgelesen und zu einem Font-Objekt verarbeitet.

Waren die Beschreibungsinformationen in der Datei ungültig, so erzeugt die Font-Klasse eine FontFormatException(„Unable to create font – bad font data“). Dateifehler fallen nicht darunter und werden extra über eine IOException angezeigt. Der Datenstrom wird anschließend nicht wieder geschlossen.

An dieser Stelle verwundert es vielleicht, dass die Arbeitsweise der statischen Methode createFont(…) der des Konstruktors ähnlich sein müsste, aber der Parameterliste die Attribute fehlen. Das liegt daran, dass die Methode automatisch einen Zeichensatz der Größe 1 im Stil Font.PLAIN erzeugt. Um einen größeren Zeichensatz zu erzeugen, müssen wir ein zweites Font-Objekt anlegen, was am einfachsten mit der Methode deriveFont(…) geschieht.

class java.awt.Font
implements Serializable

  • static Font createFont(int fontFormat, InputStream fontStream)
    throws FontFormatException, IOException
    Liefert ein neues Zeichensatzobjekt in der Größe von einem Punkt und mit keinem besonderen Stil.

Soll nicht direkt der Font verwendet werden, sondern soll der Zeichensatz unter seinem Namen in den Namensraum gelegt werden, sodass er später auch über den Font-Konstruktor gefunden werden kann, lässt er sich mit registerFont(Font) anmelden. Das sieht etwa so aus:

GraphicsEnvironment.getLocalGraphicsEnvironment().registerFont( font );

toString(), toGenericString(), getCanonicalName() in Class

Die Klasse Class überschreibt die Methode toString() und greift dabei auf getName() zurück. toString() fügt zusätzlich Informationen über die Art des repräsentierten Typs (normale Klasse, Schnittstelle oder primitiver Datentyp) ein. Neu in Java 8 ist toGenericString(), was auch noch in spitzen Klammern die Typvariablen anzeigt. (Natürlich nicht den Typparameter, da der zur Laufzeit wegen der Typlöschung nicht bekannt ist.) Um die Typvariable zu erfragen wird getTypeName() verwendet; die Methode ist eine Implementierung aus der Schnittstelle Type, die Class implementiert.

Beispiel: Teste für ein Class-Objekt die drei String-Repräsentationen:

Class<?> c = HashMap.class;

System.out.println( c.getName() ); // java.util.HashMap

System.out.println( c.toString() ); // class java.util.HashMap

System.out.println( c.toGenericString() ); // public class java.util.HashMap<K,V>

Bei inneren Typen trennt ein $ bei der String-Repräsentation den äußeren und inneren Typ. Anders verhält sich getCanonicalName(), wie das Beispiel zeigt:

Anweisung

Rückgabe

Map.Entry.class.getName()

java.util.Map$Entry

Map.Entry.class.getCanonicalName()

java.util.Map.Entry

Map.Entry.class.toString()

interface java.util.Map$Entry

Map.Entry.class.toGenericString()

public abstract static interface java.util.Map$Entry<K,V>

String-Repräsentation bei inneren Typen

Inselraus: Punkt in einer Form, Schnitt von Linien, Abstand Punkt/Linie

Die unterschiedlichen Klassen für die geometrischen Formen aus dem Java 2D-Paket besitzen Methoden, um zum Beispiel festzustellen, ob ein Punkt in einer Form liegt.

interface java.awt.Shape

  • boolean contains(int x, int y )
  • boolean contains(Point2D p)
    Liefert true, wenn der Punkt in der Form liegt.
  • boolean contains(int x, int y, int w, int h)
  • boolean contains(Rectangle2D r)
    Liefert true, wenn das beschriebene Rechteck komplett in der Form liegt.

Besonders praktisch ist die Methode contains(…) für Polygone.[1] Sie arbeitet aber nur korrekt für Punkte innerhalb der eingeschlossenen Fläche. Bei Abfrage von Punkten, die den Eckpunkten entsprechen, kommen immer sehr willkürliche Werte heraus – und genauso bei der Abfrage, ob die Punkte auf der Linie zum Innenraum gehören oder nicht.

Die Klasse Point2D berechnet den Abstand zweier Punkte mit den Methoden:

  • double distance(double PX, double PY)
  • static double distance(double X1, double Y1, double X2, double Y2)
  • double distance(Point2D pt)
  • double distanceSq(double PX, double PY)
  • static double distanceSq(double X1, double Y1, double X2, double Y2)
  • double distanceSq(Point2D pt)

Verwandte Methoden zur Berechnung des Abstands eines Punktes zur Line bietet auch Line2D:

  • double ptLineDist(double PX, double PY)
  • static double ptLineDist(double X1, double Y1, double X2, double Y2, double PX, double PY)
  • double ptLineDist(Point2D pt)
  • double ptLineDistSq(double PX, double PY)
  • static double ptLineDistSq(double X1, double Y1, double X2, double Y2, double PX, double PY)
  • double ptLineDistSq(Point2D pt)
  • double ptSegDist(double PX, double PY)
  • static double ptSegDist(double X1, double Y1, double X2, double Y2, double PX, double PY)
  • double ptSegDist(Point2D pt)
  • double ptSegDistSq(double PX, double PY)
  • static double ptSegDistSq(double X1, double Y1, double X2, double Y2, double PX, double PY)
  • double ptSegDistSq(Point2D pt)

Die relativeCCW(…)-Methoden von Line2D können herausfinden, ob der Punkt rechts oder links einer Linie liegt. Ob sich zwei Linien schneiden, ermitteln zwei überladene Line2D-Methoden intersectsLine(…). Neben der Objektmethode testet die mit acht Parametern gesegnete statische Methode linesIntersect(…), ob zwei Liniensegmente sich schneiden. Zwei allgemeine intersects(…)-Methoden deklariert die Schnittstelle Shape, doch bei diesen Methoden aus Line2D geht es darum, ob eine Form ein Rechteck schneidet. intersectsLine(…) bietet auch Rectangle2D und meldet damit, ob ein Rechteck eine Linie schneidet.

Genau das Gegenteil vom Schnitt ist die Vereinigung. So legt die Methode union(Rectangle2D src1, Rectangle2D src2, Rectangle2D dest) von Rectangle2D zwei Rechtecke zusammen, wobei ein neues Rechteck entsteht, das die äußersten Koordinaten der beiden Ursprungsrechtecke besitzt. Die Methode outcode(double, double) ist ebenfalls interessant, da sie über eine Bit-Maske in der Rückgabe angibt, wo ein außerhalb des Rechtecks befindlicher Punkt steht, also etwa OUT_BOTTOM, OUT_LEFT, OUT_RIGHT, OUT_TOP.


[1] Ob ein Punkt im Polygon ist, entscheidet der Gerade/Ungerade-Test (http://en.wikipedia.org/wiki/Point_in_polygon).