Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger.

 
Inhaltsverzeichnis
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Der Umgang mit Zeichenketten
5 Eigene Klassen schreiben
6 Objektorientierte Beziehungsfragen
7 Ausnahmen müssen sein
8 Äußere.innere Klassen
9 Besondere Typen der Java SE
10 Generics<T>
11 Lambda-Ausdrücke und funktionale Programmierung
12 Architektur, Design und angewandte Objektorientierung
13 Komponenten, JavaBeans und Module
14 Die Klassenbibliothek
15 Einführung in die nebenläufige Programmierung
16 Einführung in Datenstrukturen und Algorithmen
17 Einführung in grafische Oberflächen
18 Einführung in Dateien und Datenströme
19 Einführung ins Datenbankmanagement mit JDBC
20 Einführung in <XML>
21 Testen mit JUnit
22 Bits und Bytes und Mathematisches
23 Die Werkzeuge des JDK
A Java SE-Paketübersicht
Stichwortverzeichnis


Download:

- Beispielprogramme, ca. 35,4 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 13 Komponenten, JavaBeans und Module
Pfeil 13.1 JavaBeans
Pfeil 13.1.1 Properties (Eigenschaften)
Pfeil 13.1.2 Einfache Eigenschaften
Pfeil 13.1.3 Indizierte Eigenschaften
Pfeil 13.1.4 Gebundene Eigenschaften und PropertyChangeListener
Pfeil 13.1.5 Veto-Eigenschaften – dagegen!
Pfeil 13.2 JavaFX Properties
Pfeil 13.2.1 javafx.beans-Paket mit XXXProperty-Klassen
Pfeil 13.2.2 Property-Veränderungen registrieren
Pfeil 13.2.3 Beans-Binding
Pfeil 13.2.4 Property-Schnittstelle und bindXXX(…)-Methoden
Pfeil 13.2.5 XXXProperty-Beziehungen (für Typ-Fetischisten) *
Pfeil 13.2.6 Ausblick
Pfeil 13.3 Klassenlader (Class Loader) und Klassenpfad
Pfeil 13.3.1 Klassenladen auf Abruf
Pfeil 13.3.2 JAR-Dateien
Pfeil 13.3.3 Woher die kleinen Klassen kommen: die Suchorte und spezielle Klassenlader
Pfeil 13.3.4 Setzen des Klassenpfades
Pfeil 13.4 Zum Weiterlesen
 

Zum Seitenanfang

13.2JavaFX Properties Zur vorigen ÜberschriftZur nächsten Überschrift

Klassen werden zu JavaBeans, wenn sie Setter/Getter für Properties besitzen. Die JavaBeans-Spezifikation definiert Properties durch eine einfache Namenskonvention. Properties sind demnach keine Eigenschaft der Sprache Java, auch wenn dafür oft eine spezielle Java-Syntax gefordert wird. Doch auch ohne Sprachunterstützung gibt es im Paket java.beans ein paar Hilfsklassen zur einfachen Ereignismeldung.

Parallel zum java.beans-Paket gibt es in Java eine alternative Umsetzung von Properties, die über eine Namensgebung hinausgeht und die eine Ereignisbehandlung vereinfacht. Darüber hinaus lassen sich Beziehungen zwischen Properties herstellen, was Beans-Binding genannt wird. All diese Neuerungen zogen in Java über die Hintertür ein, als ein neues GUI-Framework mit dem Namen JavaFX entwickelt wurde. Deshalb liegen die neuen Typen auch nicht im java.beans-Paket, sondern im javafx.beans-Paket mit diversen Unterpaketen; gemischt werden die Typen in der Regel nicht.

 

Zum Seitenanfang

13.2.1javafx.beans-Paket mit XXXProperty-Klassen Zur vorigen ÜberschriftZur nächsten Überschrift

Wir bleiben für unser Beispiel wieder bei einer Klasse Person, der wir Namen und Alter geben. Soll die Bean eine klassische JavaBean sein, werden setName(…)/getName() und setAge(…)/getAge() auf die internen Attribute String name und int age zugreifen. Falls eine Benachrichtigung über irgendwelche Property-Änderungen gewünscht ist, kann die Klasse Person eine Methode addPropertyChangeListener(…) anbieten, damit ein PropertyChangeListener Änderungen an der Person melden kann.

Neue Typen aus dem Paket javafx.beans.property geben uns eine Alternative zum Deklarieren von Properties. Damit können wir die Klasse Person mit zwei Properties auch so formulieren:

Listing 13.8com/tutego/insel/fxbean/Person.java, Person

public class Person {

private final StringProperty name = new SimpleStringProperty();

private final IntegerProperty age = new SimpleIntegerProperty();

public StringProperty nameProperty() { return name; }

public final String getName() { return name.get(); }

public final void setName( String name ) { this.name.set( name ); }

public IntegerProperty ageProperty() { return age; }

public final int getAge() { return age.get(); }

public final void setAge( int age ) { this.age.set( age ); }

}

Nach außen hin tritt die Klasse immer noch als JavaBean auf, doch gibt es eine Reihe von Änderungen und Neuerungen:

  1. Anstatt intern das Attribut (String name, int age) direkt zu deklarieren, gibt es einen Behälter, der den Wert aufnimmt. Für String ist das StringProperty, für int ist das IntegerProperty. Wir werden später sehen, dass diese XXXProperty-Klassen eine Reihe interessanter Möglichkeiten bieten; zuerst reichen uns aber set(…) und get().

  2. Die XXXProperty-Typen sind Schnittstellen, aber keine Klassen. Anstatt selbst die Schnittstellen zu implementieren, nutzen wir eine Standardimplementierung von Java, die SimpleXXXProperty-Klassen. Sie finden nur intern Verwendung, ihr Typ ist in der Klasse Person nicht relevant.

  3. Eine XXXProperty wird mit einer eigenen Methode nach außen gegeben, die sich als dritte Methode zu dem Setter/Getter gesellt. Heißt die Property name, so heißt die StringProperty liefernde Methode nameProperty(). Während die Setter/Getter ein Präfix haben, nämlich »set« und »get« – bzw. »is« für Wahrheitsrückgaben –, ist es bei der xxxProperty()-Methode ein Suffix.

  4. Die Setter/Getter greifen auf die XXXProperty-Methoden set(…) und get() zurück und setzen so den Wert des Behälters neu bzw. greifen ihn ab. Wichtig zu verstehen ist, dass in der Implementierung vom Setter/Getter nichts weiter als diese Delegation zur XXXProperty stehen darf. Entwickler könnten sich ja auch direkt über xxxProperty() die XXXProperty schnappen und ohne Umwege set(…)/get() direkt aufrufen. In den setXXX(…)/getXXX()-Methoden zu loggen, Zähler zu setzen oder sogar bei setXXX(…) eine Validitätsprüfung durchzuführen, ist falsch. Damit Unterklassen nicht fälschlicherweise komplexeres Verhalten implementieren, sind die Methoden final, also nicht überschreibbar. So ist zum Beispiel person.nameProperty().get() immer äquivalent mit person.getName().

[»]Hinweis

Viele JavaFX-Klassen nutzen diese Form von Properties. Nehmen wir den Typ javafx. scene.shape.Circle, so finden wir dort Properties für centerX, centerY, radius und folglich drei Setter/Getter und die Methoden centerXProperty, centerYProperty, radiusProperty. Für Properties gibt es in der Javadoc eine eigene Sektion, neben den Bereichen für Attribute, Konstruktoren und Methoden.

Setter-Getter versus Properties

Die im Hintergrund mit JavaFX-Properties arbeitende Person-Klasse kann nun mit den Settern/Gettern als JavaBean dort eingesetzt werden, wo diese Namensgebung vielleicht erforderlich ist, etwa wenn die JavaBeans über ein Web-Framework an die Oberflächenelemente gebunden werden oder bei der objektrelationalen Abbildung. Doch sind Setter/Getter nur eine Konvention, und Entwickler können sich bewusst dagegen entscheiden und stattdessen nur xxxProperty()-Methoden anbieten. Wir haben gesehen, dass Setter/Getter nur Durchreichemethoden sind und diese keine eigene Logik implementieren dürfen; ihr Wert besteht nur darin, die Properties nach einer definierten Namensgebung anzubieten, wenn etwa JavaBeans per Reflection verarbeitet werden.

 

Zum Seitenanfang

13.2.2Property-Veränderungen registrieren Zur vorigen ÜberschriftZur nächsten Überschrift

Neben den bekannten Settern/Gettern bietet der direkte Zugriff auf die XXXProperty noch mehr. Als Erstes sind das zwei Listener:

  • javafx.beans.InvalidationListener. Meldet, wenn die Property invalide wird. Welche Eigenschaft sich ändert und wie die neue Belegung ist, kann dieser Listener nicht melden.

  • javafx.beans.value.ChangeListener<T>. Meldet konkrete Änderungen an einer Property und kennt den Status vor der Änderung und nach der Änderung.

Die jeweiligen addListener(…)/removeListener(…)-Methoden stammen aus den Schnittstellen javafx.beans.Observable und javafx.beans.value.ObservableValue<T>, die alle XXXProperty-Typen erweitern.

Im nächsten Beispiel wollen wir einen InvalidationListener nutzen und diesen an die JavaFX-Property für das Alter und den Namen hängen. Des Weiteren wollen wir einen individuellen ChangeListener für eine Namensänderung nutzen:

Listing 13.9com/tutego/insel/fxbean/PersonProperty.java, main()

Person chris = new Person();

chris.setName( "Chris" );

chris.setAge( 102 );

InvalidationListener invalidationListener =

observable -> System.out.println( "Änderung am " + observable );

chris.nameProperty().addListener( invalidationListener );

chris.ageProperty().addListener( invalidationListener );

ChangeListener<String> changeListener =

(observable, oldValue, newValue) -> System.out.printf( "%s -> %s%n",

oldValue, newValue );

chris.nameProperty().addListener( changeListener );

chris.setName( "Cora" );

chris.setAge( 52 );

Nach dem Start gibt es die zu erwartenden Ausgaben:

Änderung am StringProperty [value: Cora]

Chris -> Cora

Änderung am IntegerProperty [value: 52]
 

Zum Seitenanfang

13.2.3Beans-Binding Zur vorigen ÜberschriftZur nächsten Überschrift

Ändern sich die Zustände von Beans, informieren diese die Listener, und Code kann auf diese Modifikation reagieren. Besonders nützlich ist das bei grafischen Oberflächen, sodass Änderungen an den Model-Objekten automatisch in der Darstellung reflektiert werden. Doch anstatt diese Beobachtung von Eigenschaften und die manuelle Aktualisierung als Entwickler von Hand zu machen, gibt uns Java eine großartige API an die Hand, mit der Properties-Änderungen automatisch verfolgt und synchron gehalten werden können. Wir sprechen hier vom Property-Binding.

Beim Property-Binding wird eine Property an ein Ziel gebunden; ändert sich das Ziel, ändert sich auch automatisch die Property. Dazwischen sind spezielle Konvertierungen möglich. Bindungen erlauben es also, auf komfortable Weise die Beziehungen zwischen Properties aufrechtzuerhalten. Im Fall unserer grafischen Oberfläche könnte das so aussehen, dass eine Änderung am Geburtsdatum in der Oberfläche gleich mit der JavaBean synchronisiert wird, dann vielleicht das Alter in der JavaBean aktualisiert wird und dieses Alter wiederum mit der GUI synchronisiert wird. Die Abhängigkeiten müssen wir nicht von Hand mit Listenern verfolgen, sondern das übernimmt die Java-Bibliothek.

 

Zum Seitenanfang

13.2.4Property-Schnittstelle und bindXXX(…)-Methoden Zur vorigen ÜberschriftZur nächsten Überschrift

Alle XXXProperty-Klassen implementieren die Schnittstelle Property, und diese schreibt fünf Methoden vor (von den vererbten einmal abgesehen):

interface javafx.beans.property.Property<T>

extends ReadOnlyProperty<T>, WritableValue<T>
  • void bind(ObservableValue<? extends T> observable)

  • void bindBidirectional(Property<T> other)

  • boolean isBound()

  • void unbind()

  • void unbindBidirectional(Property<T> other)

Unidirektionales Binden

Um eine Property an ein Ziel zu binden, wird die Methode bind(…) genutzt.

[zB]Beispiel

Jeder Fahrer im Auto hat die gleiche Geschwindigkeit wie das Auto. Wir binden daher die Fahrergeschwindigkeit an die Geschwindigkeit des Autos:

IntegerProperty carSpeed = new SimpleIntegerProperty();

IntegerProperty driverSpeed = new SimpleIntegerProperty();

driverSpeed.bind( carSpeed );

carSpeed.set( 200 );

System.out.println( driverSpeed.get() ); // 200

Die Property driverSpeed ist danach eine gebundene Property, und isBound() würde true ergeben. Somit ist eine Veränderung mit set(…) nicht mehr erlaubt und würde eine »java.lang.RuntimeException: A bound value cannot be set« auslösen. Nur bei einer bidirektionalen Verbindung sind Setter-Aufrufe auf beiden Seiten möglich.

Diese Bindung im Beispiel ist einseitig, und bind(…) benötigt nur ein ObservableValue; was das für ein ObservableValue ist, ist egal, es muss gar keine Property sein, es könnte auch ein beliebiger Ereignismelder sein.

Bidirektionales Binden

Bidirektionale Beziehungen baut bindBidirectional(…) auf, und unbindBidirectional(…) löst diese wieder auf. Natürlich müssen die Typen zusammenpassen, darauf achten die verwendeten Generics.

[zB]Beispiel

Zwei ObjectProperty-Objekte werden aneinander gebunden. Änderungen an einem Behälter werden immer mit dem anderen synchron gehalten:

ObjectProperty<Instant> time1 = new SimpleObjectProperty<>();

ObjectProperty<Instant> time2 = new SimpleObjectProperty<>();

time1.bindBidirectional( time2 );

time1.set( Instant.now() );

System.out.println( time1.get() ); // 2014-03-05T18:46:39.110Z

System.out.println( time2.get() ); // 2014-03-05T18:46:39.110Z

time2.set( Instant.now() );

System.out.println( time1.get() ); // 2014-03-05T18:46:39.303Z

System.out.println( time2.get() ); // 2014-03-05T18:46:39.303Z

XXXBinding-Klassen

Bisher haben wir eine Property direkt gespiegelt, also den Wert 1:1 von einer Quelle in das Ziel gebracht. Allerdings kann die Bindung noch etwas mehr, als nur den Wert zu übertragen: Properties können auch zu neuen abgeleiteten Properties werden. Hier kommt der Typ Binding ins Spiel, ein spezieller ObservableValue – und damit ein Kandidat für bind(ObservableValue <? extends T> observable).

[zB]Beispiel

Der Umfang eines Rechtecks (formular) soll als Bindung angelegt und eine Property (perimeter) mit dieser Bindung neu angelegt werden, a und b sind Abhängigkeiten:

IntegerProperty a = new SimpleIntegerProperty( 3 );

IntegerProperty b = new SimpleIntegerProperty( 2 );

NumberBinding formular = a.multiply( 2 ).add( b.multiply( 2 ) );

System.out.println( formular.getValue() ); // 10

IntegerProperty perimeter = new SimpleIntegerProperty();

perimeter.bind( formular );

System.out.println( perimeter.get() ); // 10

a.set( 6 );

System.out.println( perimeter.get() ); // 16

Die Variable formular bietet eine Methode getValue(), weil ein NumberBinding ein Observable ist und so der berechnete Wert erfragt werden kann. Außerdem binden wir die Berechnung an eine andere Property und holen uns das Ergebnis mit get() ab.

Die Schnittstelle Binding wird von diversen XXXBinding-Klassen implementiert, NumberBinding ist unser Typ im Beispiel. Die den Ausdruck formulierenden Methoden – wie in unserem Fall add(…) und multiply(…) – stammen aus den XXXExpression-Klassen: Unterklassen sind die XXXProperty-Klassen sowie die XXXBinding-Klassen. Die XXXExpression-Klassen haben jeweils unterschiedliche Methoden, die zum Typ passen, also etwa numerische Methoden bei den numerischen XXXExpression-Typen, Zeichenketten-Methoden bei StringExpression usw.:

XXXProperty

XXXBinding

XXXExpression

Expression-Methoden

BooleanProperty

BooleanBinding

BooleanExpression

not, and, or

FloatProperty

FloatBinding

FloatExpression

add, divide, isEqualTo, greaterThan, lessThan

DoubleProperty

DoubleBinding

DoubleExpression

IntegerProperty

IntegerBinding

IntegerExpression

LongProperty

LongBinding

LongExpression

StringProperty

StringBinding

StringExpression

concat, isEmpty, isNull, length, lessThan

ObjectProperty<T>

ObjectBinding

ObjectExpression

asString, isNull

ListProperty

ListBinding

ListExpression

add, clear, contains

SetProperty

SetBinding

SetExpression

add, clear, contains

MapProperty

MapBinding

MapExpression

put, clear, isEmpty

Tabelle 13.1XXXExpression ist Basisklasse von XXXProperty sowie XXXBinding und vererbt viele Methoden.

[zB]Beispiel

Die Frage, ob ein String »m« oder »male« enthält, soll eine BooleanBinding beantworten:

StringProperty input = new SimpleStringProperty( "Wings" );

BooleanBinding isMale = input.isEqualToIgnoreCase( "m" )

.or( input.isEqualToIgnoreCase( "male" ) );

System.out.println( isMale.get() ); // false

input.set( "male" );

System.out.println( isMale.get() ); // true

Binding in JavaFX *

Das GUI-Framework JavaFX macht viel Gebrauch von JavaFX-Properties, und daher wollen wir uns ein plastisches Beispiel anschauen. Eine grafische Oberfläche soll einen Schieberegler (Slider) darstellen, der den Radius eines Kreises verändert. Mit Beans-Binding können wir direkt den Radius des Kreises an den Wert des Schiebereglers binden, sodass eine Anpassung des Reglers zu einer direkten Größenänderung führt. Weiterhin kommen zwei Bindings hinzu, sodass der Wert des Schiebereglers in zwei Textfelder übertragen wird; einmal nur für den Radius und einmal für die Berechnung der Fläche.

Listing 13.10com/tutego/insel/fxbean/JavaFXBeanBindingExample.java

package com.tutego.insel.fxbean;

import javafx.application.Application;

import javafx.scene.*;

import javafx.scene.control.*;

import javafx.scene.layout.VBox;

import javafx.scene.shape.Circle;

import javafx.stage.Stage;

public class JavaFXBeanBindingExample extends Application {

public static void main( String[] args ) {

launch( args );

}

@Override

public void start( Stage primaryStage ) {

Slider slider = new Slider( 10, 100, 40 );

Circle circle = new Circle();

circle.radiusProperty().bind( slider.valueProperty() );

TextField radius = new TextField();

radius.textProperty().bind( slider.valueProperty().asString( "%.2f" ) );

TextField area = new TextField();

area.textProperty().bind( slider.valueProperty().multiply( slider.valueProperty() )

.multiply( Math.PI ).asString( "%.2f" ) );

Node[] nodes = { slider, new Label( "Radius:" ), radius,

new Label( "Fläche:" ), area, circle };

primaryStage.setScene( new Scene( new VBox( 4, nodes ), 300, 400 ) );

primaryStage.show();

}

}

Hervorgehoben im Code sind die Properties und Bindings.

Invalide Bindungen

Properties und Bindungen können im Zustand valide und invalide sein. Das hängt damit zusammen, dass die Implementierung von Properties und Bindungen standardmäßig die Auswertung so lange aufschiebt, bis die Daten gebraucht werden; wir sprechen hier von Lazy Evaluation. Haben wir es zum Beispiel mit einer Bindung und einer Abhängigkeit zu tun und ändert sich dieser abhängige Wert, wird die Bindung nicht automatisch den Wert neu berechnen, sondern nur dann, wenn er bezogen wird. Das ist wichtig, um den Zusammenhang mit einem InvalidationListener zu verstehen: Dieser feuert nur einmal, wenn der Wert nicht mehr aktuell ist, aber nicht dauernd bei jeder abhängigen Änderung, denn invalider als invalide kann es nicht geben.

Dazu ein Beispiel: Nach dem ersten Setzen einer Property wird eine abhängige Bindung invalide, und der Listener meldet dies. Aber beim zweiten Setzen der Property meldet der Listener dies nicht mehr, da die Bindung schon invalide war. Erst wenn die Bindung den Wert bezieht, ist die Bindung wieder valide.

Listing 13.11com/tutego/insel/fxbean/ValidInvalidProperty.java, main()

StringProperty input = new SimpleStringProperty( "Wings" );

BooleanBinding isMale = input.isEqualToIgnoreCase( "m" );

isMale.addListener( Observable -> System.out.println( "Invalid" ) );

input.set( "male" );

Die Ausgabe ist nun »Invalid«.

input.set( "m" );

Hier folgt keine Ausgabe, weil der Zustand schon invalide war.

System.out.println( isMale.get() ); // true

input.set( "f" );

Es folgt wieder die Ausgabe »Invalid«, weil get() den Zustand valide gesetzt hat, aber set(…) ihn wieder invalide machte.

[»]Hinweis

Nur der InvalidationListener erlaubt eine spätere Ausführung. Wenn ein ChangeListener anhängt, ist die Berechnung immer aktuell, denn im Ereignis stehen der alte und auch der neue Wert. Mit einem angehängten ChangeListener wird es also keinen Invalid-Zustand geben, denn Veränderungen werden direkt über den Listener gemeldet, und somit wird auch der Wert neu berechnet.

Keine Angst vor eigenen Bindungen

Die Methoden aus den XXXExpression-Klassen sind umfangreich, aber auch beschränkt. Bei den numerischen Klassen finden wir die klassischen einfachen Rechenarten, jedoch keine Wurzel oder Potenzen. Das ist aber kein Problem, denn es ist einfach, eigene Bindungen mit eigener Logik aufzubauen. Dazu sind drei Schritte nötig:

  1. Baue eine eigene Unterklasse von XXXBinding.

  2. Rufe den Konstruktor der Oberklasse auf, und initialisiere die Abhängigkeiten.

  3. Realisiere die Methode computeValue(), die den von der Bindung berechneten Wert liefert.

[zB]Beispiel

Für die Berechnung der Hypotenuse eines rechteckigen Dreiecks ist eine Wurzel nötig, die die numerischen XXXBinding-Klassen nicht bieten:

DoubleProperty a = new SimpleDoubleProperty( 6 );

DoubleProperty b = new SimpleDoubleProperty( 8 );

DoubleBinding hypotenuse = new DoubleBinding() {

{

super.bind( a, b );

}

@Override

protected double computeValue() {

return Math.sqrt( a.get() * a.get() + b.get() * b.get() );

}

};

System.out.println( hypotenuse.getValue() ); // 10.0

a.set( 15 );

System.out.println( hypotenuse.getValue() ); // 17.0

Die Klasse Bindings

Neben der Fluent-API gibt es eine zweite Möglichkeit zum Aufbau der Bindungen, und zwar über die Klasse javafx.beans.binding.Bindings. Sie ist mit über 8.000 Zeilen ein Schwergewicht und enthält über 120 statische Methoden, die XXXBinding-Exemplare liefern. Die Klasse Bindings ist auch deswegen so groß, weil sich hier die eigentlichen Implementierungen finden. Die den Ausdruck bildenden Methoden in den XXXBinding-Klassen delegieren lediglich an Bindings, wie zum Beispiel an or(…) in BooleanExpression abzulesen ist:

Listing 13.12javafx/beans/binding/BooleanExpression.java, or(…)

public BooleanBinding or(final ObservableBooleanValue other) {

return Bindings.or(this, other);

}

Und in Bindings finden wir dann die Implementierung, wie für or(…):

Listing 13.13javafx/beans/binding/Bindings.java, or(…)

public static BooleanBinding or(final ObservableBooleanValue op1,

final ObservableBooleanValue op2) {

if ((op1 == null) || (op2 == null)) {

throw new NullPointerException("Operands cannot be null.");

}

return new BooleanOrBinding(op1, op2);

}

private static class BooleanOrBinding extends BooleanBinding {



@Override

protected boolean computeValue() {

return op1.get() || op2.get();

}



}

[»]Hinweis

Ob Entwickler letztendlich die Fluent-API oder Bindings nutzen oder beides miteinander mischen, ist eine Frage des Geschmacks. Eine Alternative zum vorherigen

NumberBinding binding = a.multiply( 2 ).add( b.multiply( 2 ) );

ist

NumberBinding binding = Bindings.add( a.multiply( 2 ), b.multiply( 2 ) );

und

NumberBinding binding = Bindings.add( Bindings.multiply( a, 2 ), Bindings.multiply( b, 2 ) );

Über ein import static javafx.beans.binding.Bindings.* wird die Anweisung noch etwas kürzer, und die Nutzung von Bindings kann die Lesbarkeit gegenüber der Fluent-API erhöhen:

NumberBinding binding = add( multiply( 2, a ), multiply( 2, b ) );

Außerdem gibt es createXXXBinding(Callable<…> func, Observable... dependencies)-Methoden,[ 217 ](Am Typ java.util.concurrent.Callable ist abzulesen, dass die API vor Java 8 entworfen wurde, denn andernfalls wäre java.util.function.Supplier im Einsatz. ) die einen Produzenten angeben können, der Daten liefert.

Neben den Methoden, die XXXBinding-Objekte liefern, gibt es weitere bindXXX(…)-Methoden zum Binden von Datenstrukturen mit passenden unbindXXX(…)-Methoden.

[zB]Beispiel

Eine ObservableList soll an eine List gebunden werden, sodass jede Änderung an der beobachteten Liste in einer anderen Liste gespiegelt wird:

ObservableList<Integer> observableList = FXCollections.observableArrayList();

List<Integer> list = new ArrayList<>();

Bindings.bindContent( list, observableList );

observableList.addAll( 1, 2, 3 );

System.out.println( list ); // [1, 2, 3]
 

Zum Seitenanfang

13.2.5XXXProperty-Beziehungen (für Typ-Fetischisten) * Zur vorigen ÜberschriftZur nächsten Überschrift

In den nächsten Absätzen wollen wir uns detaillierter mit der Typhierarchie beschäftigen. In der Praxis ist das aus Sicht des Anwenders nicht wirklich relevant und nur nützlich, wenn eigene Property-Klassen implementiert werden sollen.

Für alle wichtigen Wertetypen gibt es abstrakte Klassen XXXProperty. Für die primitiven Typen sind das BooleanProperty, IntegerProperty, LongProperty, FloatProperty und DoubleProperty, für String gibt es StringProperty. Dazu kommt ObjectProperty<T>, ein generischer Typ für alle anderen Referenzen. Am besten funktioniert dieser Typ für immutable Objekte, denn wenn die ObjectProperty zum Beispiel einen mutable StringBuilder referenziert und dieser StringBuilder direkt verändert wird, bekommt die ObjectProperty nichts von der Änderung mit. Perfekt passen daher immutable Typen wie BigDecimal, da hier neue Exemplare gebaut und mit set(…) der ObjectProperty gegeben werden müssen, die dann die Änderungen melden kann.

Neben den genannten Property-Klassen für bestimmte primitive Werte, Strings und allgemeine Referenzen gibt es noch drei spezielle Sammlungstypen: ListProperty, SetProperty und MapProperty. Jede SimpleXXXProperty-Klasse erweitert indirekt XXXProperty, und diese erweitert selbst noch eine abstrakte Klasse ReadOnlyXXXProperty. Ein Blick auf die Klassenhierarchie von SimpleIntegerProperty macht die Klassenvererbung noch einmal deutlich:

java.lang.Object

javafx.beans.binding.NumberExpressionBase

javafx.beans.binding.IntegerExpression

javafx.beans.property.ReadOnlyIntegerProperty

javafx.beans.property.IntegerProperty

javafx.beans.property.IntegerPropertyBase

javafx.beans.property.SimpleIntegerProperty

Jede XXXProperty ist also eine Erweiterung von ReadOnlyXXXProperty. Der Typ ReadOnlyXXXProperty ist naheliegend, wenn eine JavaBean nur Lesezugriff auf ein Attribut geben soll; dann bietet die Klasse nur einen Getter, und anstatt den Typ XXXProperty zurückzugeben, liefert sie eine ReadOnlyXXXProperty.

Neben der Klassenvererbung sind einige implementierte Schnittstellen mit im Spiel, da die einzelnen Oberklassen diverse Schnittstellen implementieren, die wiederum andere Schnittstellen erweitern. In der Summe implementiert eine SimpleIntegerProperty die folgenden Schnittstellen: NumberExpression, Observable, Property<Number>, ReadOnlyProperty<Number>, ObservableIntegerValue, ObservableNumberValue, ObservableValue<Number>, WritableIntegerValue, WritableNumberValue, WritableValue<Number>. Das erlaubt eine extrem fein granulierte API, die genau die Typen erwartet, die im Kontext gefragt sind.

Noch mehr Schnittstellen ObservableXXXValue, WritableXXXValue *

Die Klasse XXXProperty bietet eine Lese-Schreibsicht auf die Bean, die XXXReadOnlyProperty-Klasse nur eine Lesesicht. Die Lese-/Schreibmethoden gibt es dabei zweimal. Zum einen stammen typisierte Lesemethoden aus der Schnittstelle ObservableXXXValue (also etwa ObservableIntegerValue oder ObservableStringValue); hier finden wir zum Beispiel int get() bei ObservableIntegerValue und ein String get() bei ObservableStringValue. Die typisierten Schreibmethoden set(…) stammen aus der Schnittstelle WritableXXXValue; so bietet WritableIntegerValue ein set(int) und WritableStringValue ein set(String).

Parallel zu den typisierten set(…)/get()-Methoden gibt es generische Varianten, die die Wrapper-Typen nutzen, und zwar über den generischen Typ WritableValue<T>. Dort finden wir die zwei Methoden T getValue() und void setValue(T value). Alle WritableXXXValue erweitern WritableValue jeweils um die konkreten Typen. Genau genommen ist ein WritableIntegerValue eine Erweiterung von WritableNumberValue, das ein WritableValue<Number> ist. So gesehen bietet WritableIntegerValue kein setValue(Integer), sondern nur setValue(Number). Eine StringProperty erweitert WritableStringValue; das ist ein WritableObjectValue<String> und das wiederum ein WritableValue. Die einfachen Setter/Getter sind natürlich praktischer, doch die setValue(…)/getValue()-Methoden sind angenehm für Werkzeuge, die die Zustände generisch behandeln wollen.

Bean-Verweis und Property-Name

Werfen wir noch einen Blick auf die Schnittstelle ReadOnlyProperty<T>, die von allen XXXProperty-Klassen implementiert wird. Hier finden sich nur zwei Abfragemethoden, Object getBean() und String getName(). Sie liefern die zu beobachtende Bean und den Namen der Property. Allerdings werden diese Informationen nicht automatisch bezogen, sondern sind vom Entwickler selbst zu setzen. Dazu haben die SimpleXXXProperty-Klassen diverse Konstruktoren, um genau diese Eigenschaften zu initialisieren. Am Beispiel von SimpleStringProperty sieht das so aus:

  • SimpleStringProperty(String initialValue)

  • SimpleStringProperty(Object bean, String name)

  • SimpleStringProperty(Object bean, String name, String initialValue)

In unserem ersten Beispiel für die Person haben wir nur den Standard-Konstruktor zum Aufbau der Properties genutzt:

private final StringProperty name = new SimpleStringProperty();

private final IntegerProperty age = new SimpleIntegerProperty();

Bei der Abfrage new Person().nameProperty().getName() – nicht zu verwechseln mit new Person().getName() – kommt der leere String heraus und bei nameProperty().getBean() nur die null-Referenz. Hätten wir die StringProperty name stattdessen mit dem parametrisierten Konstruktor new SimpleStringProperty(this, "name") initialisiert, kämen bei

  • new Person().nameProperty().getName() eben »name« und bei

  • new Person().nameProperty().getBean() die gerade aufgebaute Person heraus.

Wenn möglich sollten die parametrisierten Konstruktoren verwendet und immer so viele Informationen wie möglich mitgegeben werden.

 

Zum Seitenanfang

13.2.6Ausblick Zur vorigen ÜberschriftZur nächsten Überschrift

Das Beans-Binding von JavaFX ist umfangreich und leistungsfähig. Blicken Entwickler zuerst auf die Pakete, überwältigt die Zahl von 140 Typen in den Paketen javafx.beans.binding, javafx.beans.property und javafx.beans.property.adapter, und es dauert etwas, bis das Muster und die Hierarchien klar werden. Doch nach einigem Blättern tauchen weitere interessante Typen auf, etwa When für Fallunterscheidungen wie in new When(cond).then (value1).otherwise(value2), das beim Aufbau eines BooleanBinding hilft. Wir haben in Bindings diverse selectXXX(…)-Methoden mit Rückgabe XXXBinding, die per Reflection auf Eigenschaften zurückgreifen können, und einen Typ JavaBeanStringProperty, der klassische JavaBeans mit PropertyChangeListener in die Bindings-Welt von JavaFX bringt.

 


Ihr Kommentar

Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.

>> Zum Feedback-Formular
<< zurück
 Zum Katalog
Zum Katalog: Java ist auch eine Insel Java ist auch eine Insel

Jetzt bestellen


 Ihre Meinung?
Wie hat Ihnen das <openbook> gefallen?

Ihre Meinung



 Buchempfehlungen
Zum Katalog: Java ist auch eine Insel

Java ist auch eine Insel




Zum Katalog: Java SE 9-Standard-Bibliothek

Java SE 9-Standard-Bibliothek




Zum Katalog: Professionell entwickeln mit Java EE 8

Professionell entwickeln mit Java EE 8




Zum Katalog: Entwurfsmuster

Entwurfsmuster




Zum Katalog: IT-Projektmanagement

IT-Projektmanagement




 Shopping
Versandkostenfrei bestellen in Deutschland und Österreich

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2017

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de