JavaのComparableおよびComparatorの例

JavaのComparableとComparatorは、オブジェクトのコレクションをソートするのに非常に便利です。Javaには、プリミティブ型の配列やラッパークラスの配列、リストをソートするためのいくつかの組み込みメソッドが用意されています。まず、プリミティブ型とラッパークラスの配列/リストをソートする方法を学び、その後、java.lang.Comparablejava.util.Comparatorインターフェースを使用して、カスタムクラスの配列/リストをソートします。単純なプログラムを使用して、プリミティブ型またはオブジェクトの配列やリストをソートする方法を見てみましょう。

package com.journaldev.sort;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class JavaObjectSorting {

    /**
     * This class shows how to sort primitive arrays, 
     * Wrapper classes Object Arrays
     * @param args
     */
    public static void main(String[] args) {
        //int配列のようにプリミティブ型の配列をソートする
        int[] intArr = {5,9,1,10};
        Arrays.sort(intArr);
        System.out.println(Arrays.toString(intArr));
        
        //String配列をソートする
        String[] strArr = {"A", "C", "B", "Z", "E"};
        Arrays.sort(strArr);
        System.out.println(Arrays.toString(strArr));
        
        //Wrapperクラスのオブジェクトのリストをソートする
        List strList = new ArrayList();
        strList.add("A");
        strList.add("C");
        strList.add("B");
        strList.add("Z");
        strList.add("E");
        Collections.sort(strList);
        for(String str: strList) System.out.print(" "+str);
    }
}

上記のプログラムの出力は次のとおりです:

[1, 5, 9, 10]
[A, B, C, E, Z]
 A B C E Z

次に、オブジェクトの配列をソートしてみましょう。

package com.journaldev.sort;

public class Employee {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    //このメソッドは、Employeeに関するユーザーフレンドリーな情報を表示するためにオーバーライドされます。
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

}

以下は、Employeeオブジェクトの配列をソートするために使用したコードです。

//オブジェクトの配列をソートする
Employee[] empArr = new Employee[4];
empArr[0] = new Employee(10, "Mikey", 25, 10000);
empArr[1] = new Employee(20, "Arun", 29, 20000);
empArr[2] = new Employee(5, "Lisa", 35, 5000);
empArr[3] = new Employee(1, "Pankaj", 32, 50000);

//Comparableインターフェースの実装を使用してemployees配列をソートする
Arrays.sort(empArr);
System.out.println("Default Sorting of Employees list:\n"+Arrays.toString(empArr));

これを実行しようとすると、次の実行時例外が発生します。

Exception in thread "main" java.lang.ClassCastException: com.journaldev.sort.Employee cannot be cast to java.lang.Comparable
	at java.util.ComparableTimSort.countRunAndMakeAscending(ComparableTimSort.java:290)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:157)
	at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
	at java.util.Arrays.sort(Arrays.java:472)
	at com.journaldev.sort.JavaSorting.main(JavaSorting.java:41)

ComparableとComparator

Javaは、Comparableインターフェースを提供しており、ArraysCollectionsのソートメソッドを使用する場合には、任意のカスタムクラスでこれを実装する必要があります。Comparableインターフェースには、ソートメソッドによって使用されるcompareTo(T obj)メソッドがあります。このメソッドは、Wrapperクラス、Stringクラス、またはDateクラスなどを確認することができます。このメソッドをオーバーライドする必要があります。オーバーライドする際には、このメソッドが「this」オブジェクトが引数として渡されたオブジェクトより小さい、等しい、または大きい場合にそれぞれ負の整数、ゼロ、または正の整数を返すようにします。EmployeeクラスでComparable インターフェースを実装した後、以下は結果となるEmployeeクラスです。

package com.journaldev.sort;

import java.util.Comparator;

public class Employee implements Comparable {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public int compareTo(Employee emp) {
        //従業員をIDの昇順でソートしてみましょう
        //この従業員のIDが指定されたオブジェクトより小さい、等しい、または大きい場合に
        //それぞれ負の整数、ゼロ、または正の整数を返す必要があります。
        return (this.id - emp.id);
    }

    @Override
    //これは、Employeeに関するユーザーフレンドリーな情報を表示するために必要です。
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

}

以上のスニペットを実行して従業員の配列をソートし、結果を出力すると、以下のような出力が得られます。

Default Sorting of Employees list:
[[id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000]]

従業員の配列がidで昇順に並べ替えられていることがわかります。しかし、ほとんどの実際のシナリオでは、異なるパラメータに基づいてソートしたい場合があります。例えば、CEOとして、私は給与に基づいて従業員をソートしたいと思いますし、人事部の場合は年齢に基づいてソートしたいと思うでしょう。このような状況では、Java Comparatorインターフェースを使用する必要があります。なぜなら、Comparable.compareTo(Object o)メソッドの実装はデフォルトのソートを提供し、動的に変更することはできないからです。一方、Comparatorを使用すると、異なるソート方法を持つ複数のメソッドを定義し、要件に応じてソート方法を選択することができます。

Java Comparator

Comparatorインターフェースのcompare(Object o1, Object o2)メソッドは、2つのオブジェクト引数を受け取るように実装する必要があります。このメソッドは、最初の引数が2番目の引数よりも小さい場合に負の整数を返し、等しい場合には0を返し、最初の引数が2番目の引数よりも大きい場合には正の整数を返します。ComparableおよびComparatorインターフェースは、コンパイル時の型チェックにジェネリックスを使用しています。詳細はJava Genericsを参照してください。ここでは、Employeeクラス内で異なるComparatorの実装方法を示します。

/**
     * Comparator to sort employees list or array in order of Salary
     */
    public static Comparator<Employee> SalaryComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return (int) (e1.getSalary() - e2.getSalary());
        }
    };

    /**
     * Comparator to sort employees list or array in order of Age
     */
    public static Comparator<Employee> AgeComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getAge() - e2.getAge();
        }
    };

    /**
     * Comparator to sort employees list or array in order of Name
     */
    public static Comparator<Employee> NameComparator = new Comparator<Employee>() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getName().compareTo(e2.getName());
        }
    };

すべての上記のComparatorインターフェースの実装は無名クラスです。これらの比較子を使用して、ArraysとCollectionsクラスのsort関数に引数を渡すことができます。

//給与によるComparatorを使用して従業員配列を並べ替え
Arrays.sort(empArr, Employee.SalaryComparator);
System.out.println("Employees list sorted by Salary:\n"+Arrays.toString(empArr));

//年齢によるComparatorを使用して従業員配列を並べ替え
Arrays.sort(empArr, Employee.AgeComparator);
System.out.println("Employees list sorted by Age:\n"+Arrays.toString(empArr));

//名前によるComparatorを使用して従業員配列を並べ替え
Arrays.sort(empArr, Employee.NameComparator);
System.out.println("Employees list sorted by Name:\n"+Arrays.toString(empArr));

以下は、上記のコードスニペットの出力です:

Employees list sorted by Salary:
[[id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000]]
Employees list sorted by Age:
[[id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000]]
Employees list sorted by Name:
[[id=20, name=Arun, age=29, salary=20000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000]]

Javaオブジェクト配列またはリストを並べ替えたい場合は、デフォルトの並べ替えを提供するためにjava Comparableインターフェースを実装する必要があり、異なる並べ替え方法を提供するためにJava Comparatorインターフェースを実装する必要があることがわかりました。また、Comparatorインターフェースを実装する別のクラスを作成して使用することもできます。これがJavaで ComparableとComparator について説明している最終のクラスです。

package com.journaldev.sort;

import java.util.Comparator;

public class Employee implements Comparable {

    private int id;
    private String name;
    private int age;
    private long salary;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public long getSalary() {
        return salary;
    }

    public Employee(int id, String name, int age, int salary) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    @Override
    public int compareTo(Employee emp) {
        //IDの昇順で従業員を並べ替えましょう
        //この従業員IDで返される負の整数、ゼロ、または正の整数
        //指定されたオブジェクトよりも小さい、等しい、または大きい
        return (this.id - emp.id);
    }

    @Override
    //これは、従業員についてのユーザーフレンドリーな情報を印刷するために必要です。
    public String toString() {
        return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
                this.salary + "]";
    }

    /**
     * Comparator to sort employees list or array in order of Salary
     */
    public static Comparator SalaryComparator = new Comparator() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return (int) (e1.getSalary() - e2.getSalary());
        }
    };

    /**
     * Comparator to sort employees list or array in order of Age
     */
    public static Comparator AgeComparator = new Comparator() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getAge() - e2.getAge();
        }
    };

    /**
     * Comparator to sort employees list or array in order of Name
     */
    public static Comparator NameComparator = new Comparator() {

        @Override
        public int compare(Employee e1, Employee e2) {
            return e1.getName().compareTo(e2.getName());
        }
    };
}

以下は、2つのEmployeesオブジェクトを最初にIDで比較し、同じ場合は名前で比較するComparatorインターフェースの別のクラス実装です。

package com.journaldev.sort;

import java.util.Comparator;

public class EmployeeComparatorByIdAndName implements Comparator<Employee> {

    @Override
    public int compare(Employee o1, Employee o2) {
        int flag = o1.getId() - o2.getId();
        if(flag==0) flag = o1.getName().compareTo(o2.getName());
        return flag;
    }

}

これは、ComparableとComparatorを使用してJavaでオブジェクトを異なる方法で並べ替えるテストクラスです。

package com.journaldev.sort;

import java.util.Arrays;

public class JavaObjectSorting {

    /**
     * This class shows how to sort custom objects array/list
     * implementing Comparable and Comparator interfaces
     * @param args
     */
    public static void main(String[] args) {

        //オブジェクト配列のソート
        Employee[] empArr = new Employee[4];
        empArr[0] = new Employee(10, "Mikey", 25, 10000);
        empArr[1] = new Employee(20, "Arun", 29, 20000);
        empArr[2] = new Employee(5, "Lisa", 35, 5000);
        empArr[3] = new Employee(1, "Pankaj", 32, 50000);
        
        //Comparableインターフェースの実装による従業員の配列のソート
        Arrays.sort(empArr);
        System.out.println("Default Sorting of Employees list:\n"+Arrays.toString(empArr));
        
        //Comparatorを使用して給与で従業員の配列をソート
        Arrays.sort(empArr, Employee.SalaryComparator);
        System.out.println("Employees list sorted by Salary:\n"+Arrays.toString(empArr));
        
        //Comparatorを使用して年齢で従業員の配列をソート
        Arrays.sort(empArr, Employee.AgeComparator);
        System.out.println("Employees list sorted by Age:\n"+Arrays.toString(empArr));
        
        //Comparatorを使用して名前で従業員の配列をソート
        Arrays.sort(empArr, Employee.NameComparator);
        System.out.println("Employees list sorted by Name:\n"+Arrays.toString(empArr));
        
        //Comparatorクラスを使用してIDと名前で従業員リストをソート
        empArr[0] = new Employee(1, "Mikey", 25, 10000);
        Arrays.sort(empArr, new EmployeeComparatorByIdAndName());
        System.out.println("Employees list sorted by ID and Name:\n"+Arrays.toString(empArr));
    }

}

上記のプログラムの出力は次のとおりです。

Default Sorting of Employees list:
[[id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000]]
Employees list sorted by Salary:
[[id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000]]
Employees list sorted by Age:
[[id=10, name=Mikey, age=25, salary=10000], [id=20, name=Arun, age=29, salary=20000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000]]
Employees list sorted by Name:
[[id=20, name=Arun, age=29, salary=20000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000]]
Employees list sorted by ID and Name:
[[id=1, name=Mikey, age=25, salary=10000], [id=1, name=Pankaj, age=32, salary=50000], [id=5, name=Lisa, age=35, salary=5000], [id=10, name=Mikey, age=25, salary=10000]]

java.lang.Comparableおよびjava.util.Comparatorは、Javaでオブジェクトをソートするために使用できる強力なインターフェースです。

Comparable vs Comparator

  1. Comparableインターフェースは単一のソート方法を提供するために使用されますが、Comparatorインターフェースは異なるソート方法を提供するために使用されます。
  2. Comparableを使用するには、クラスがそれを実装する必要がありますが、Comparatorを使用するにはクラスに変更を加える必要はありません。
  3. Comparableインターフェースはjava.langパッケージにありますが、Comparatorインターフェースはjava.utilパッケージにあります。
  4. Comparableを使用する場合、クライアント側でのコード変更は必要ありません。Arrays.sort()Collection.sort()メソッドは、自動的にクラスのcompareTo()メソッドを使用します。Comparatorを使用する場合、クライアントはcompare()メソッドで使用するComparatorクラスを指定する必要があります。

Collections.sort()メソッドは、Comparator引数を受け取ることでStrategyパターンに従っています。

私たちのGitHubリポジトリから完全なコードとより多くのコアJavaの例をチェックアウトすることができます。

Source:
https://www.digitalocean.com/community/tutorials/comparable-and-comparator-in-java-example