1 JavaFX Collectionsの使用
このチュートリアルでは、Java Collections Frameworkの拡張であるJavaFX Collections APIについて説明し、コンパイルして実行できるコード・サンプルを提供しています。
このチュートリアルではまず、Java Collections Frameworkの関連クラスおよびインタフェースについて簡単に再確認してから、これらがJavaFX Collections APIでどのように拡張されて追加の動作を提供するかを説明します。Java Collectionsの詳細なチュートリアルは、JavaチュートリアルのCollectionsトレールを参照してください。
Java Collectionsの基本事項の再確認
この項では、java.util.List
とjava.util.Map
インタフェースおよびjava.util.Collections
クラスについて簡単に説明します。Java Collectionsについてすでによく理解している場合は、次の項である「JavaFX Collectionsの学習」に進んでください。
List
List
は順序付けされたオブジェクトのコレクションであり、java.util.List
インタフェースによって表されます。List
内のオブジェクトは要素と呼ばれ、同じList
内には複数の要素が存在できます。List
インタフェースによって有用な複数のメソッドが定義されており、これにより、要素の追加、特定のインデックスにある要素へのアクセスまたは変更、サブリストの作成、リスト内の要素の検索、リストの消去などを行うことができます。
例1-1に、これらのメソッドをString
オブジェクトのList
とともに示します。
例1-1 Listの使用
package collectionsdemo; import java.util.List; import java.util.ArrayList; public class CollectionsDemo { public static void main(String[] args) { // Create a List. System.out.println("Creating the List..."); List<String> list = new ArrayList<String>(); list.add("String one"); list.add("String two"); list.add("String three"); // Print out contents. printElements(list); // Set a new element at index 0. System.out.println("Setting an element..."); list.set(0, "A new String"); printElements(list); // Search for the newly added String. System.out.println("Searching for content..."); System.out.print("Contains \"A new String\"? "); System.out.println(list.contains("A new String")); System.out.println(""); // Create a sublist. System.out.println("Creating a sublist..."); list = list.subList(1,3); printElements(list); // Clear all elements. System.out.println("Clearing all elements..."); list.clear(); printElements(list); } private static void printElements(List<String> list) { System.out.println("Size: "+list.size()); for (Object o : list) { System.out.println(o.toString()); } System.out.println(""); } }
例1-1の出力は、次のとおりです。
Creating the List...
Size: 3
String one
String two
String three
Setting an element...
Size: 3
A new String
String two
String three
Searching for content...
Contains "A new String"? true
Creating a sublist...
Size: 2
String two
String three
Clearing all elements...
Size: 0
このプログラムではまず、ArrayList
(List
インタフェースの具象実装)がインスタンス化され、list
変数に割り当てられます。次に、add
メソッドを呼び出すことにより、リストに3つのString
オブジェクトが追加されます。(その実行の様々なポイントで、プログラムにより、printElements
と呼ばれるカスタムprivate static
メソッドを呼び出すことによって要素が出力されます。)行list.set(0,"A new String")
によって、最初のインデックス位置にある元のString
オブジェクトが新しいString
オブジェクトと置き換えられます。contains
メソッドによって、List
に指定した要素が存在するかどうかがレポートされ、sublist
メソッドによって、所定のインデックス値で指定された範囲から新しいList
が返されます。最後に、clear
メソッドによって、List
からすべての要素が削除されます。
Map
Map
は、キーを値にマップするオブジェクトです。Map
には重複するキーを含めることができません。キーはそれぞれ1つの値にのみマップできます。キーと値をMap
に配置し、そのキーを渡すことによって値を取得できます。たとえば、キーappleではfruitが返され、carrotではvegetableが返されます。
例1-2に、これらのメソッドをString
オブジェクトのMap
とともに示します。
例1-2 マップの使用
package collectionsdemo; import java.util.Map; import java.util.HashMap; public class CollectionsDemo { public static void main(String[] args) { // Create a Map. Map<String,String> map = new HashMap<String,String>(); map.put("apple", "fruit"); map.put("carrot","vegetable"); System.out.println("Size: "+map.size()); System.out.println("Empty? "+map.isEmpty()); // Pass in keys; print out values. System.out.println("Passing in keys and printing out values..."); System.out.println("Key is apple, value is: "+map.get("apple")); System.out.println("Key is carrot, value is: "+map.get("carrot")); System.out.println(""); // Check keys and values. System.out.println("Inspecting keys and values:"); System.out.println("Contains key \"apple\"? "+ map.containsKey("apple")); System.out.println("Contains key \"carrot\"? "+ map.containsKey("carrot")); System.out.println("Contains key \"fruit\"? "+ map.containsKey("fruit")); System.out.println("Contains key \"vegetable\"? "+ map.containsKey("vegetable")); System.out.println("Contains value \"apple\"? "+ map.containsValue("apple")); System.out.println("Contains value \"carrot\"? "+ map.containsValue("carrot")); System.out.println("Contains value \"fruit\"? "+ map.containsValue("fruit")); System.out.println("Contains value \"vegetable\"? "+ map.containsValue("vegetable")); System.out.println(""); // Remove objects from the map. System.out.println("Removing apple from the map..."); map.remove("apple"); System.out.println("Size: "+map.size()); System.out.println("Contains key \"apple\"? "+ map.containsKey("apple")); System.out.println("Invoking map.clear()..."); map.clear(); System.out.println("Size: "+map.size()); } }
例1-2の出力は、次のとおりです。
Size: 2
Empty? false
Passing in keys and printing out values...
Key is apple, value is: fruit
Key is carrot, value is: vegetable
Inspecting keys and values:
Contains key "apple"? true
Contains key "carrot"? true
Contains key "fruit"? false
Contains key "vegetable"? false
Contains value "apple"? false
Contains value "carrot"? false
Contains value "fruit"? true
Contains value "vegetable"? true
Removing apple from the map...
Size: 1
Contains key "apple"? false
Invoking map.clear()...
Size: 0
このプログラムではまず、HashMap
(Map
インタフェースの具象実装)がインスタンス化され、map
変数に割り当てられます。キーと値のペアが、put
メソッドを呼び出すことによってmap
に追加されます。次に、size()
およびisEmpty()
を呼び出すことによって、マップに関する一定の情報が取得(および出力)されます。また、プログラムにより、所定のキーに対応する値を取得する方法が示されます(たとえば、map.get("apple")
によって値fruitが返されます)。containsKey
およびcontainsValue
メソッドによって、特定のキーまたは値が存在するかどうかをテストする方法が示され、clear
メソッドによってキーと値のマッピングがすべて削除されます。
Collections
List
およびMap
にあるメソッドに加えて、Collections
クラスでは、コレクションを処理したりコレクションを返すいくつかのstaticユーティリティ・メソッドが公開されます。例1-3に、List
を作成し、Collections
クラスを使用してその要素を逆転、スワップおよびソートすることにより、このようなメソッドのいくつかを示します。
例1-3 Collectionsクラスの使用
package collectionsdemo; import java.util.List; import java.util.ArrayList; import java.util.Collections; public class CollectionsDemo { public static void main(String[] args) { System.out.println("Creating the list..."); List<String> list = new ArrayList<String>(); list.add("a"); list.add("b"); list.add("c"); list.add("d"); printElements(list); System.out.println("Reversing the elements..."); Collections.reverse(list); printElements(list); System.out.println("Swapping the elements around..."); Collections.swap(list, 0, 3); Collections.swap(list, 2, 0); printElements(list); System.out.println("Alphabetically sorting the elements..."); Collections.sort(list); printElements(list); } private static void printElements(List<String> list) { for (Object o : list) { System.out.println(o.toString()); } } }
例1-3の出力は、次のとおりです。
Creating the list...
a
b
c
d
Reversing the elements...
d
c
b
a
Swapping the elements around...
b
c
a
d
Alphabetically sorting the elements...
a
b
c
d
このプログラムではまず、具象実装としてArrayList
を使用して、List
に文字a、b、cおよびdが追加されます。次に、Collections.reverse(list)
を呼び出すことによって、リストの要素の順序を逆にします。List
内で要素をスワップするために、Collections.swap
メソッドが呼び出されます(たとえば、Collections.swap(list,0,3)
によって、インデックス位置0および3の要素がスワップされます)。最後に、Collections.sort()
メソッドによって、要素がアルファベット順にソートされます。
Java Collections Frameworkで最も関連性の高い領域を再確認し、CollectionsがJavaFXでどのように表されるかを学習する準備ができました。
JavaFX Collectionsの学習
JavaFXでのCollectionsはjavafx.collections
パッケージにより定義されており、このパッケージは次のインタフェースおよびクラスで構成されています。
インタフェース
ObservableList
: リスナーが変更をその発生時に追跡できるようにするリスト
ListChangeListener
: ObservableList
に対する変更の通知を受け取るインタフェース
ObservableMap
: オブザーバが変更をその発生時に追跡できるようにするマップ
MapChangeListener
: ObservableMap
に対する変更の通知を受け取るインタフェース
クラス
FXCollections
: java.util.Collections
メソッドの1対1のコピーであるstaticメソッドで構成されるユーティリティ・クラス
ListChangeListener.Change
: ObservableList
に対して行われた変更を表す
MapChangeListener.Change
: ObservableMap
に対して行われた変更を表す
次の項では、これらのインタフェースおよびクラスを使用する方法について説明します。
ObservableList、ObservableMapおよびFXCollectionsの使用
javafx.collections.ObservableList
とjavafx.collections.ObservableMap
の両方のインタフェースにより、javafx.beans.Observable
(およびそれぞれjava.util.List
またはjava.util.Map
)が、監視可能性をサポートするList
またはMap
を提供するように拡張されます。これらのインタフェースのいずれかのAPI仕様を確認すると、適切なリスナーを追加または削除するためのメソッドがわかります(ObservableList
に対するListChangeListener
、およびObservableMapに対するMapChangeListener
)。前述のList
およびMap
の例(具象実装としてArrayList
およびHashMap
を使用)とは異なり、例1-4では、javafx.collections.FXCollections
クラスを使用して、ObservableList
およびObservableMap
オブジェクトを作成して返します。
例1-4 ObservableListの使用
package collectionsdemo; import java.util.List; import java.util.ArrayList; import javafx.collections.ObservableList; import javafx.collections.ListChangeListener; import javafx.collections.FXCollections; public class CollectionsDemo { public static void main(String[] args) { // Use Java Collections to create the List. List<String> list = new ArrayList<String>(); // Now add observability by wrapping it with ObservableList. ObservableList<String> observableList = FXCollections.observableList(list); observableList.addListener(new ListChangeListener() { @Override public void onChanged(ListChangeListener.Change change) { System.out.println("Detected a change! "); } }); // Changes to the observableList WILL be reported. // This line will print out "Detected a change!" observableList.add("item one"); // Changes to the underlying list will NOT be reported // Nothing will be printed as a result of the next line. list.add("item two"); System.out.println("Size: "+observableList.size()); } }
例1-4では、最初に標準のList
が作成されます。次に、リストをFXCollections.observableList(list)
に渡すことによって取得されたObservableList
でラップされます。ListChangeListener
が登録されて、ObservableList
に対して変更が行われるたびに通知を受け取るようになります。
ObservableMap
に対する変更は、例1-5に示すように、同様の方法でリスンすることができます。
例1-5 ObservableMapの使用
package collectionsdemo; import java.util.Map; import java.util.HashMap; import javafx.collections.ObservableMap; import javafx.collections.MapChangeListener; import javafx.collections.FXCollections; public class CollectionsDemo { public static void main(String[] args) { // Use Java Collections to create the List. Map<String,String> map = new HashMap<String,String>(); // Now add observability by wrapping it with ObservableList. ObservableMap<String,String> observableMap = FXCollections.observableMap(map); observableMap.addListener(new MapChangeListener() { @Override public void onChanged(MapChangeListener.Change change) { System.out.println("Detected a change! "); } }); // Changes to the observableMap WILL be reported. observableMap.put("key 1","value 1"); System.out.println("Size: "+observableMap.size()); // Changes to the underlying map will NOT be reported. map.put("key 2","value 2"); System.out.println("Size: "+observableMap.size()); } }
最後に、Collections
またはFXCollections
からのstaticユーティリティ・メソッドを使用できます(リストの要素の順序を逆にするためなど)。ただし、FXCollections
クラスでは、メソッドの呼出し時には少数の変更通知(通常は1つ)が生成されることに注意してください。他方で、Collections
のメソッドを呼び出すと、例1-6に示すように、複数の変更通知が生成される可能性があります。
例1-6 CollectionsとFXCollectionsの変更通知
package collectionsdemo; import java.util.List; import java.util.ArrayList; import javafx.collections.ObservableList; import javafx.collections.ListChangeListener; import javafx.collections.FXCollections; public class CollectionsDemo { public static void main(String[] args) { // Use Java Collections to create the List List<String> list = new ArrayList<String>(); list.add("d"); list.add("b"); list.add("a"); list.add("c"); // Now add observability by wrapping it with ObservableList ObservableList<String> observableList = FXCollections.observableList(list); observableList.addListener(new ListChangeListener() { @Override public void onChanged(ListChangeListener.Change change) { System.out.println("Detected a change! "); } }); // Sort using FXCollections FXCollections.sort(observableList); } }
例1-6では、行FXCollections.sort(obervableList)
によってリスト内のString
オブジェクトがアルファベット順にソートされ、画面には変更通知が1つのみ出力されます。ただし、Collections.sort(observableList)
を使用した場合は、変更通知が4回出力されます。
ListChangeListener
またはMapChangeListener
を使用している場合、onChanged
メソッドには、変更に関する情報をカプセル化するオブジェクトが必ず含まれます。これは、ListChangeListener.Change
(ObservableList
に対応)またはMapChangeListener.Change
(ObservableMap
に対応)のインスタンスです。ListChangeListener.Change
を使用する場合は、常に、変更オブジェクトへのすべてのコールを、change.next()
を呼び出すループ内にラップしてください。例1-7に、この例を示します。
例1-7 ListChangeListener.Changeオブジェクトの問合せ
... // This code will work with any of the previous ObservableList examples observableList.addListener(new ListChangeListener() { @Override public void onChanged(ListChangeListener.Change change) { System.out.println("Detected a change! "); while (change.next()) { System.out.println("Was added? " + change.wasAdded()); System.out.println("Was removed? " + change.wasRemoved()); System.out.println("Was replaced? " + change.wasReplaced()); System.out.println("Was permutated? " + change.wasPermutated()); } } }); ...
例1-7では、ListChangeListener.Change
オブジェクトに対して様々なメソッドが呼び出されています。注意が必要な重要な点は、ListChangeListener.Change
オブジェクトに複数の変更が含まれる場合があるため、while
ループでそのnext()
メソッドをコールすることによって反復する必要があることです。ただし、MapChangeListener.Change
オブジェクトには、実行されたput
またはremove
操作を表す変更のみが含まれます。
使用可能なメソッドの詳細は、ListChangeListener.ChangeおよびMapChangeListener.Change
APIドキュメントを参照してください。