ラッパークラスのオブジェクトを格納したリストであれば基本選択法を使って並べ替えを行うか、java.util.Collectionsクラスのstaticなsort()メソッドを使って簡単にソートすることが可能です。
APIでArrayListクラスのsort()メソッドを調べると、このように記述されています。
※オーバーロードされたsort()メソッドは後述します。
リストオブジェクトのみを引数にする場合は、その内容のオブジェクトがComparableインターフェースを実装している必要がある、という意味です
Comparableインターフェースを実装している既存のクラスで主なものは、ラッパークラスの他、Stringクラスなどがあります。これらはすべて昇順のソートになっています。
では、次のようなオブジェクトが格納されたリストをソートするにはどうすればいいのでしょうか。
1人分のテストの成績を収めておくJavaBeansです。(Student.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
package jp.kenschool.blog; import java.io.Serializable; public class Student implements Serializable{ /*フィールド*/ private int id; private String name; private int score; /*コンストラクター*/ public Student(){} public Student(int id, String name, int score) { this.id = id; this.name = name; this.score = score; } /* 基本的なアクッセッサーは省略 */ } |
上記を実行するアプリケーションクラス (SortTest1.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
package jp.kenschool.blog; import java.util.ArrayList; public class SortTest1 { public static void main(String[] args) { ArrayList<Student> list = new ArrayList<>(); list.add(new Student(1,"Mark",87)); list.add(new Student(2,"John",90)); list.add(new Student(3,"Jaxon",87)); list.add(new Student(4,"Alice",75)); /*ここで並べ替えたい!*/ display(list); } private static void display(ArrayList<Student> al) { System.out.println(" id Name Score"); System.out.println("---------------------------"); for(Student st : al) { System.out.printf("%2d %-8s%5d\n",st.getId(),st.getName(),st.getScore()); } } } |
【実行結果】
id Name Score
——————–
1 Mark 87
2 John 90
3 Jaxon 87
4 Alice 75
単純にCollectons.sort(list)とするわけにはいきません。何の順番で並べ替えるか?これを指定しなくてはソートできませんね。
これを実現するには2つの方法があります。(他のAPIを使用する方法もあるようですが、ここでは取り上げません。)
- Studentクラスでlang.Comparable<T>インターフェースを実装しcompareTo<T>()メソッドをオーバーライドする
- 並べ替え専用のutil.Comparator<T>インターフェースを実装したクラスを定義し、compare()メソッドをオーバーライドしたオブジェクトを利用する
1.Comparableインターフェースの利用
1の方法で得点(Score)の高い順(降順)に並べ替えてみます。
前述のように、Comparableインターフェースを実装したクラスであればよいので、今回の場合、StudentクラスにComparableインターフェースが実装されていればこのメソッドを利用することができる、ということになります。
Studentクラスは既にSerializableインターフェースを実装しているので、クラス宣言は「public class Student implements Serializable,Comparable<Student>」となります。
※比較したいオブジェクトは同じStudentクラスのオブジェクトなのでジェネリクスでStudentを指定します。
この場合Comparableインターフェースの抽象メソッドpublic int compareTo(<T> obj)メソッドです。
ジェネリクスのTはもちろんStudentです。
compareTo()メソッドではint型の戻り値を返すのですが、自オブジェクトのscoreの値と他のオブジェクトのscoreの値の差を返すようにしておけば、リストに存在するすべてのオブジェクトの値を比較できます。
昇順にソートするには、引き算してマイナスの値が返れば順序を入れ替え、そうでない場合は入れ替えない、というようにリスト内のすべてのオブジェクトをソートしてくれます。値の大小ではなく、「プラスかマイナスか」が重要になります。
今回は降順にソートしたいので、引き算の解の+と-を反転させておきます。
Studentクラスに次のメソッドを追加します。
1 2 3 4 5 6 7 |
@Override public int compareTo(Student s) { return -(this.score – s.getScore()); } |
1 2 3 |
/*ここで並べ替えたい!*/ Collections.sort(list); |
これを活用するためにアプリケーションクラスSortTest1.javaでCollectionsクラスのstaticなメソッドであるsort()を呼び出します。
【実行結果】
id Name Score
——————–
2 John 90
1 Mark 87
3 Jaxon 87
4 Alice 75
一つのキーのみでソートする場合はこれでいいのですが、同じ点数の場合「名前の昇順で並べ替える」などのように第2キーがある場合はこのままではうまくいきません。
もう一度compareTo()メソッドの内容を振り返ると、「マイナスの値が出たときに順序を入れ替える(昇順)」というところに注目します。
数値の大小にかかわらず返す値は「マイナスの値」あるいは「プラスの値」であれば何でも構わないので、自オブジェクトのscoreの値と他のオブジェクトのscoreの値を比較して、他オブジェクトのほうが小さい場合は+1を、自オブジェクトのほうが小さい場合は-1を、そして、同じ場合は0を返すようにしておけば昇順にソートできます。
今回のように降順の場合はプラスとマイナスを逆にします。
compareTo()メソッド改造1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
@Override public int compareTo(Student s) { if(this.score > s.getScore()) { return -1; }else if(this.score < s.getScore()) { return 1; }else { return 0; } } |
今回の第2キーは「名前の昇順」だったので、これを満たすように修正します。文字列の辞書順比較なので、String#compareTo()メソッドを使いました。この改造で先ほどと同じ動作になりますが、elseに注目してください。elseに制御が移るということはscoreが同じであったということになりますので、ここで第2キーの並びを考え、同じような比較やメソッドの呼び出しあるいは演算を行えばいいということになります。
compareTo()メソッド改造2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@Override public int compareTo(Student s) { if(this.score > s.getScore()) { return -1; }else if(this.score < s.getScore()) { return 1; }else { return this.name.compareTo(s.getName()); } } |
【実行結果】
id Name Score
——————–
同じ点数でも順序が入れ替わった |
2 John 90
3 Jaxon 87
1 Mark 87
4 Alice 75
2.Comparatorインターフェースの利用
Collectionsクラスのsort()メソッドはオーバーロードされていて、APIではこのようになっています。
第2引数にjava.util.Comparatorインターフェースを実装するクラス(並べ替えを実装するクラス)のインスタンスを指定することによりソートを実現します。
Comparableインターフェースの利用では、JavaBeansの方に定義する必要がありましたが、こちらのケースはComparatorインターフェースを実装した並べ替え専用のクラスを別途定義するので、JavaBeansを修正する必要がありません。
Studentクラスを元に戻した形でStudent2クラスを定義します。(Student2.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package jp.kenschool.blog; import java.io.Serializable; public class Student2 implements Serializable{ private int id; private String name; private int score; public Student2() {} public Student2(int id, String name, int score) { this.id = id; this.name = name; this.score = score; } /* 基本的なアクッセッサーは省略 */ } |
java.util.Comparatorインターフェースを実装したクラスを作り、抽象メソッド通常のJavaBeansの形です。
compare()を実装します。
内容はComparable#compareTo()とほぼ同じです。scoreフィールドが別のクラスになるのでどちらもgetScore()及びgetName()メソッドを使うところが違うだけです。
並べ替え用のクラスと実装するメソッド(MyComparator.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package jp.kenschool.blog; import java.util.Comparator; public class MyComparator implements Comparator<Student2>{ @Override public int compare(Student2 s1, Student2 s2) { if(s1.getScore() > s2.getScore()) { return -1; }else if(s1.getScore() < s2.getScore()) { return 1; }else { return s1.getName().compareTo(s2.getName()); } } } |
アプリケーションクラスのソートメソッドはCollections.sort(list,[MyComparatorクラスのオブジェクト])となります。
アプリケーションクラス(SortTest2.java)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
package jp.kenschool.blog; import java.util.ArrayList; import java.util.Collections; public class SortTest2 { public static void main(String[] args) { ArrayList<Student2> list = new ArrayList<>(); list.add(new Student2(1,"Mark",87)); list.add(new Student2(2,"John",90)); list.add(new Student2(3,"Jaxon",87)); list.add(new Student2(4,"Alice",75)); /*ここで並べ替えたい*/ Collections.sort(list,new MyComparator()); display(list); } private static void display(ArrayList<Student2> al) { System.out.println(" id Name Score"); System.out.println("--------------------"); for(Student2 st : al) { System.out.printf("%2d %-8s%5d\n",st.getId(),st.getName(),st.getScore()); } } } |
MyComparatorクラスを別途定義せずに、匿名クラスで処理をするとこうなります。結果はComparableインターフェースの利用と同様になります。
アプリケーションクラス(SortTest2_1.java)※ソートの部分のみ抜粋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Collections.sort(list,new Comparator<Student2>(){ @Override public int compare(Student2 s1, Student2 s2) { if(s1.getScore() > s2.getScore()) { return -1; }else if(s1.getScore() < s2.getScore()) { return 1; }else { return s1.getName().compareTo(s2.getName()); } } }); |
Comparatorインターフェースはcompare()メソッドのみが定義された関数型インターフェースなので、ラムダ式が使用可能です。これらの方法を実装すれば、ソートの内容、どのフィールドでソートするか、昇順・降順はどうするかなど、アプリケーションクラスを設計するときに自由に決めることができる、というメリットがあります。
※ソートの部分のみ抜粋
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Collections.sort(list,(s1 , s2) ->{ if(s1.getScore() > s2.getScore()) { return -1; }else if(s1.getScore() < s2.getScore()) { return 1; }else { return s1.getName().compareTo(s2.getName()); } }); |
なお、Object型の配列の場合も同じように処理できますが、Collentions.sort()ではなくArrays.sort()となります。