JavaにおけるComparableとComparatorの例を比較してください。
Javaにおいて、ComparableとComparatorはオブジェクトのコレクションをソートする際に非常に便利です。Javaは、プリミティブ型の配列やラッパークラスの配列、リストをソートするための組み込みメソッドを提供しています。まずは、プリミティブ型とラッパークラスの配列/リストをソートする方法を学び、その後、java.lang.Comparableとjava.util.Comparatorインターフェースを使用してカスタムクラスの配列/リストをソートする方法を見てみましょう。以下に、プリミティブ型やオブジェクトの配列やリストを簡単なプログラムでソートする方法を示します。
package com.scdev.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) {
//sort primitives array like int array
int[] intArr = {5,9,1,10};
Arrays.sort(intArr);
System.out.println(Arrays.toString(intArr));
//sorting String array
String[] strArr = {"A", "C", "B", "Z", "E"};
Arrays.sort(strArr);
System.out.println(Arrays.toString(strArr));
//sorting list of objects of Wrapper classes
List<String> strList = new ArrayList<String>();
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.scdev.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
//this is overridden to print the user-friendly information about the Employee
public String toString() {
return "[id=" + this.id + ", name=" + this.name + ", age=" + this.age + ", salary=" +
this.salary + "]";
}
}
私が使用したコードは、従業員オブジェクトの配列をソートするためのものです。
//sorting object array
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);
//sorting employees array using Comparable interface implementation
Arrays.sort(empArr);
System.out.println("Default Sorting of Employees list:\n"+Arrays.toString(empArr));
このコードを実行しようとしたところ、次のランタイム例外が発生しました。
Exception in thread "main" java.lang.ClassCastException: com.scdev.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.scdev.sort.JavaSorting.main(JavaSorting.java:41)
Comparable は、オブジェクトの自然な順序付けが可能なインターフェースです。
Comparator は、オブジェクトのカスタムな順序付けが可能なインターフェースです。
Javaでは、ArraysやCollectionsのソートメソッドを使用するためには、カスタムクラスごとにComparableインターフェースを実装する必要があります。Comparableインターフェースには、ソートメソッドによって使用されるcompareTo(T obj)メソッドがあります。これを確認するためには、ラッパークラス、文字列クラス、または日付クラスを確認することができます。このメソッドを上書きして、引数として渡されるオブジェクトよりも「this」オブジェクトが小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数を返すようにする必要があります。EmployeeクラスにComparableインターフェースを実装した後、次のようなEmployeeクラスが得られます。
package com.scdev.sort;
import java.util.Comparator;
public class Employee implements Comparable<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
public int compareTo(Employee emp) {
//let's sort the employee based on an id in ascending order
//returns a negative integer, zero, or a positive integer as this employee id
//is less than, equal to, or greater than the specified object.
return (this.id - emp.id);
}
@Override
//this is required to print the user-friendly information about the 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番目の引数より小さい場合に負の整数を返し、等しい場合にはゼロを返し、最初の引数が2番目の引数よりも大きい場合には正の整数を返すように実装する必要があります。 「Comparable」と「Comparator」インターフェースは、コンパイル時の型チェックのためにジェネリックスを使用しており、Javaのジェネリクスについて詳しく学ぶことができます。どのようにして「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 employees array using Comparator by Salary
Arrays.sort(empArr, Employee.SalaryComparator);
System.out.println("Employees list sorted by Salary:\n"+Arrays.toString(empArr));
//sort employees array using Comparator by Age
Arrays.sort(empArr, Employee.AgeComparator);
System.out.println("Employees list sorted by Age:\n"+Arrays.toString(empArr));
//sort employees array using Comparator by Name
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.scdev.sort;
import java.util.Comparator;
public class Employee implements Comparable<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
public int compareTo(Employee emp) {
//let's sort the employee based on an id in ascending order
//returns a negative integer, zero, or a positive integer as this employee id
//is less than, equal to, or greater than the specified object.
return (this.id - emp.id);
}
@Override
//this is required to print the user-friendly information about the Employee
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<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インターフェースの別のクラス実装です。この実装では、Employeeオブジェクトのidを比較し、それが同じ場合にはnameによって比較します。
package com.scdev.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;
}
}
Javaを使用してオブジェクトを比較するさまざまな方法を使用して、テストクラスを以下に示します。ComparableとComparatorを使用しています。
package com.scdev.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) {
//sorting custom object array
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);
//sorting employees array using Comparable interface implementation
Arrays.sort(empArr);
System.out.println("Default Sorting of Employees list:\n"+Arrays.toString(empArr));
//sort employees array using Comparator by Salary
Arrays.sort(empArr, Employee.SalaryComparator);
System.out.println("Employees list sorted by Salary:\n"+Arrays.toString(empArr));
//sort employees array using Comparator by Age
Arrays.sort(empArr, Employee.AgeComparator);
System.out.println("Employees list sorted by Age:\n"+Arrays.toString(empArr));
//sort employees array using Comparator by Name
Arrays.sort(empArr, Employee.NameComparator);
System.out.println("Employees list sorted by Name:\n"+Arrays.toString(empArr));
//Employees list sorted by ID and then name using Comparator class
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でオブジェクトのソートを提供するために使用できる強力なインターフェースです。
比較可能性 vs 比較器
-
- Comparableインターフェースは、単一のソート方法を提供するために使用され、Comparatorインターフェースは異なるソート方法を提供するために使用されます。
-
- Comparableを使用するには、クラスはそれを実装する必要がありますが、Comparatorを使用する場合はクラスに変更を加える必要はありません。
-
- Comparableインターフェースはjava.langパッケージにあり、Comparatorインターフェースはjava.utilパッケージに存在します。
- Comparableを使用する場合、クライアント側でのコード変更は必要ありません。Arrays.sort()やCollection.sort()メソッドは、自動的にクラスのcompareTo()メソッドを使用します。Comparatorを使用する場合、クライアントはcompare()メソッドで使用するComparatorクラスを提供する必要があります。
Comparator引数を受け取るCollections.sort()メソッドがストラテジーパターンに従っていることを知っていますか?
弊社のGitHubリポジトリから、完全なコードやさらに多くのJavaのコア例を確認することができます。