Javaでオブジェクトを任意のキー(複数)で並び替え(ソート)する方法まとめ

javaのListやArrayListにクラスオブジェクトを格納した後、それを任意の項目(キー)で並べ替えて処理を行いたい場合によく使うTipsです。

結構使用頻度が多いのですが、そのたびに調べることになるので、まとめておこうと思います。

どのような方法があるか?

方法としては、

  • ①Comparableインターフェースを実装したクラスをListに格納し、それをソートする。
  • ②Comparatorインターフェースを実装するクラスにソート条件を記述し、それとソート対象のクラスを引数にソートする。
  • ③Apache Commons CollectionsのComparatorChainを利用する。

などがあげられます。

①と②の方法は、Javaに限らず、いろいろな言語でも共通で使える原理なので、覚えておくと便利です。

最近では、Java8からstreamAPIとラムダ式を使用して、簡便に記述できるようになりましたが、Java8は普及がまだまだなので、次回ということで。

①Comparableインターフェースを実装したクラスをListに格納し、それをソートする。

Listに格納するソート対象のクラスがComparableインターフェースを実装していると、Collections.sort(list)メソッドを使用できます。

そのため、Comparableインターフェースを実装したクラス(JavaBeans)を作成します。

Comparableインターフェースで実装しなければいけないメソッドは、「compareTo」で、この中でソート対象のクラスの項目(キー)の比較を行い、以下のようにint型の値を戻します。

・このインスタンスが、compareToの引数として渡されたObjectより小さい(前に並ぶ)場合・・・負の値

・このインスタンスが、compareToの引数として渡されたObjectより大きい(後ろに並ぶ)場合・・・正の値

・同じなら、0を返す。

以下が、Comparableインターフェースを実装したソート対象のクラスの例です。

public class MyComparableObject implements Comparable {
     
  private int age;         //年齢
  private String userName; //名前
       
  public MyComparableObject(int age, String userName) {
    this.age = age;
    this.userName = userName;
  }
 
  public int getAge() {
    return age;
  }
 
  public void setAge(int age) {
    this.age = age;
  }
 
  public String getUserName() {
    return userName;
  }
 
  public void setUserName(String userName) {
    this.userName = userName;
  }
 
  /**
  * @return このインスタンスが引数として渡されたオブジェクトより前に並び替えられる場合負の値。
  * このインスタンスが引数として渡されたオブジェクトと同じ順序の場合0。
  * このインスタンスが引数として渡されたオブジェクトより後ろに並び替えられる場合正の値。
  */
  public int compareTo(MyComparableObject passedObj) {
         
    //このインスタンスが、compareToの引数として渡されたObjectより小さい(前に並ぶ)なら負の値、
    //このインスタンスが、compareToの引数として渡されたObjectより大きい(後ろに並ぶ)なら正の値を返す。
    //同じなら、0を返せばよい。
    if(this.age<passedObj.age){ 
      return -1; 
    }else if(this.age>passedObj.age){
      return 1;
    }else{
      return 0;
    }
 
  }
}

そして以下が、そのクラスを利用して、実際に並べ替えを行うサンプルです。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class SampleSortTest {
 
  public static void main(String[] args) {
         
    List list = new ArrayList();
    list.add(new MyComparableObject(20, "あさだ"));
    list.add(new MyComparableObject(20, "あさくら"));
    list.add(new MyComparableObject(18, "たなか"));
    list.add(new MyComparableObject(32, "さくま"));
         
    for(MyComparableObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
         
    Collections.sort(list);
    System.out.println("--Sort--");
         
    for(MyComparableObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
  }
 
}

実行すると、以下のような出力結果となります。

②Comparatorインターフェースを実装するクラスにソート条件を記述し、それとソート対象のクラスを引数にソートする。

既にクラスが存在していて、変更できない場合は、Comparatorインターフェースを実装したクラスを作成し、そこでソートの条件を記述します。

すると、「Collections.sort(list, <ソート条件を記述したクラス>)」のようにListと条件を渡してソートできるようになります。 たとえば、以下のようなJavaBeansクラスが、すでに存在していたとします。v

public class MyObject {
     
  private int age;         //年齢
  private String userName; //名前
         
  public MyObject(int age, String userName) {
    this.age = age;
    this.userName = userName;
  }
 
  public int getAge() {
    return age;
  }
 
  public void setAge(int age) {
   this.age = age;
  }
 
  public String getUserName() {
    return userName;
  }
 
  public void setUserName(String userName) {
    this.userName = userName;
  }
 
}

このクラスはそのままにしておき、下記のようなComparatorインターフェースを実装したクラスを作成します。

メソッド「compare()」は2つの引数をとり、メソッド内でそれらのオブジェクトの項目の比較を行っています。

import java.util.Comparator;
 
public class MyObjComparator implements Comparator {
 
  public int compare(MyObject obj1, MyObject obj2) {
         
    if(obj1.getAge()<obj2.getAge()){ 
      return -1; 
    }else if(obj1.getAge()>obj2.getAge()){
      return 1;
    }else{
      return 0;
    }
  }
     
}

この原理を使うと、複数の項目で並べ替えを行う条件を記述することもできます。

以下は、ageという項目に加えて、userNameでも比較を行っています。

import java.util.Comparator;
 
public class MyObjComparatorMulti implements Comparator {
 
  public int compare(MyObject obj1, MyObject obj2) {
         
    if(obj1.getAge()<obj2.getAge()){ 
      return -1; 
    }else if(obj1.getAge()>obj2.getAge()){
      return 1;
    }else{
      //ここに来るということは、年齢が同じだということなので、次の条件で比較する。
      //これを繰り返せば、何個でも大丈夫。
      //String.compareToは2 つの文字列を辞書的に比較するメソッド。
      return obj1.getUserName().compareTo(obj2.getUserName());
    }
  }
     
}

以下が、それらのクラスを利用して、実際に並べ替えを行うサンプルです。

最初に、ageという1項目でソートを行い、次に、MyObjComparatorMultiクラスで2項目によるソートを行っています。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
public class SampleSortTest2 {
 
  public static void main(String[] args) {
 
    List list = new ArrayList();
    list.add(new MyObject(20, "あさだ"));
    list.add(new MyObject(20, "あさくら"));
    list.add(new MyObject(18, "たなか"));
    list.add(new MyObject(32, "さくま"));
 
    for(MyObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
    Collections.sort(list, new MyObjComparator());
    System.out.println("--Sort--");
 
    for(MyObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
    Collections.sort(list, new MyObjComparatorMulti());
    System.out.println("--Sort--");
 
    for(MyObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
  }
 
}

実行すると、以下のような出力結果となります。

③Apache Commons CollectionsのComparatorChainを利用する。

上記の②の理論で汎用的に使えるようにしたのが、Apache Commons CollectionsのComparatorChainというユーティリティクラスです。

ComparatorChainクラスの「addComparator」というメソッドで、ソートのキーをどんどん追加していくことができます。

そのため、複数のキー条件によるソートが比較的簡単に実現できます。

下記のクラスは、最初にage、次にuserNameでソートを行う場合の記述です。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
 
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections.comparators.ComparatorChain;
 
public class SampleSortTest3 {
 
  public static void main(String[] args) {
 
    List list = new ArrayList();
    list.add(new MyObject(20, "あさだ"));
    list.add(new MyObject(20, "あさくら"));
    list.add(new MyObject(18, "たなか"));
    list.add(new MyObject(32, "さくま"));
 
    for(MyObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
    ComparatorChain comparator = new ComparatorChain();
    comparator.addComparator(new BeanComparator("age"));
    comparator.addComparator(new BeanComparator("userName"));
 
    Collections.sort(list, comparator);
    System.out.println("--Sort--");
 
    for(MyObject obj:list){
      System.out.println(obj.getAge() + ""+ obj.getUserName());
    }
 
  }
 
}

必要なjarは以下のページから取得してください。

Exception in thread “main” java.lang.NoClassDefFoundError: org/apache/commons/collections/comparators/ComparableComparator

というエラーが出る場合は、CollectionsとBeanUtilsのバージョンが合っているかを確認するといいかもしれません。

commons-beanutils-1.9.2はcommons-collections-3.2系に依存していますので、私がcommons-collections-4系を使ったときは、上記のエラーが出てしまいました。