目次
1 コンパレータの概要
英語のドキュメントを読むことをお勧めします
公式英語ドキュメント紹介
https://docs.oracle.com/ja/java/javase/20/docs/api/java.base/java/util/Comparator.html
Comparator は関数型インターフェイスであるため、ラムダ式またはメソッド参照の代入ターゲットとして使用できます。これは、Collections.sort や Arrays.sort など、自然な並べ替えを持たないコレクションを並べ替えるのによく使用されます。または、TreeSet や TreeMap など、順序付けされたデータ構造のソート ルールを宣言します。つまり、このインターフェイスは主にコレクションの並べ替えに使用されます。
1.1 関数宣言
関数宣言とは、 Java の関数プログラミングを使用して関数またはメソッドを宣言することを指します。関数型プログラミングは、関数を適用する方法として計算を扱い、関数の純粋性と不変性を強調するプログラミング パラダイムです。Java では、関数型プログラミングは通常、ラムダ式、メソッド参照、関数型インターフェイスなどの機能を使用して実装されます。提供したコードでは、ラムダ式は(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
関数宣言であり、コンパレータの定義を表し、オブジェクトの比較規則を関数に抽象化します。
要約すると、comparing
このメソッドは関数とコンパレータをパラメータとして受け取り、抽出されたキー値に基づいてコンパレータとオブジェクトを並べ替えるためのキー コンパレータを返す関数型プログラミングの例です。このアプローチにより、多くの比較ロジックを作成することなく、プロパティに基づいてオブジェクトを簡単に比較できます。
1.2 シンプルな小型ケース
List<People> peoples = new ArrayList<>();
// 中间省略
// 按照年龄从小到大排序
peoples.sort(Comparator.comparing(People::getAge));
2. コンパレータのメソッド
Comparator の関数インターフェイスとしては抽象メソッドが 1 つしかありませんが、デフォルトのメソッドが多数あるので、これらのメソッドを理解しましょう。
2.1 比較抽象メソッド
Comparator
唯一の抽象メソッドとしてint compare(T o1,T o2)
、このメソッドは 2 つのパラメーターのサイズを比較し、それぞれ o1<o2、o1=o2、o1>o2 を表す負の整数、ゼロ、正の整数を返します。通常、-1、0、または 1 はそれぞれ返されました。擬似式:
// 输入两个同类型的对象 ,输出一个比较结果的int数字
(x1,x2)-> int
例
自分でやれ
public class SortedDemo {
public static void main(String[] args) {
List<String> words = Arrays.asList("watermelon","apple", "banana", "cherry");
// 使用比较器按字符串长度升序排序
words.sort((str2, str1) -> Integer.compare(str1.length(), str2.length()));
// words.sort((str2, str1) -> Integer.compare(str1.length(), str2.length()));//降序
System.out.println(words); // 输出: [apple, banana, cherry]
}
}
2.2 比較方法
Java 8からは、Comparator
関数型スタイルにより、Comparator
より強力で便利な機能を付与した一連の静的メソッドが提供されていますので、仮にcomparing
シリーズメソッドと呼ぶことにします。
比較メソッドとそのソース コード実装に注目してみましょう。比較メソッドにはさまざまなオーバーロードされたメソッドがあることに注意してください。これは、パラメーターを渡す方法の最も包括的な入門です。
ソースコード
public static <T, U> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor,
Comparator<? super U> keyComparator)
{
Objects.requireNonNull(keyExtractor);
Objects.requireNonNull(keyComparator);
return (Comparator<T> & Serializable)
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2));
}
参考説明
この方法は、この一連の方法の基本的な方法である。わかりにくそうに見えませんか?このメソッドを分析してみましょう。そのパラメータは両方とも関数インターフェイスです。
最初のパラメーターは、Function<? super T, ? extends U> keyExtractor
タイプ T のオブジェクトを入力し、タイプ U のオブジェクトを出力することを意味します。たとえば、People
オブジェクトを入力すると、その age Intege
r 値が返されます。
// people -> people.getAge(); 转换为下面方法引用
Function<People, Integer> getAge = People::getAge;
2 番目のパラメータ keyComparator
は理解しやすく、使用される比較ルールを表します。
はい c1
、c2
最初のパラメータで指定されたルールに従ってkeyExtractor
特徴が抽出され、2 番目のパラメータkeyComparator
で 2 つの特徴が比較されます。次の式は実際には 3.1 のように要約できます。(x1,x2)-> int
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
keyExtractor.apply(c2))
Comparator & Serializable は Java 8 の新機能であり、これら 2 つの型制約を同時に満たします。
この方法を理解すると、このシリーズの他の方法も理解しやすくなりますので、ここでは詳しく説明しません。現在、一連の比較方法がより広く使用されています。いくつかの例を挙げてみましょう。
List<People> peoples = new ArrayList<>();
// ………………
// 按照年龄从低到高排序
peoples.sort(Comparator.comparing(People::getAge));
// 按照年龄从高到低排序
peoples.sort(Comparator.comparing(People::getAge, (x, y) -> -x.compareTo(y)));
同様に、java.util.Collections または Stream によって提供される並べ替えメソッドを使用して Comparator を使用できます。
詳しい説明
java.util.Comparator
このソース コードは、クラス内にある Java 標準ライブラリのメソッドであり、指定された関数 ( Comparator
に基づいてオブジェクトFunction
) とキー コンパレータ (Comparator
では、このソースコードを順を追って説明していきます。
-
<T, U>
: これはジェネリック メソッドの定義です。このメソッドには 2 つのジェネリック型パラメータ と がありT
、U
比較する要素の型とキー値を抽出する型を表すために使用されることを示します。 -
comparing
このメソッドは 2 つのパラメータを受け入れます。keyExtractor
T
パラメーターは、 type のオブジェクトを受け入れ、U
type のキー値を返す関数です。この関数は、オブジェクトから比較のキー値を抽出するために使用されます。keyComparator
パラメータは、 type のキー値を比較するコンパレータですU
。
-
Objects.requireNonNull(keyExtractor)
and : これら 2 行のコードは、Objects.requireNonNull(keyComparator)
受信パラメータkeyExtractor
がであるkeyComparator
かどうかを確認しnull
、そうであればnull
スローするために使用されますNullPointerException
。 -
戻り値: このメソッドの戻り値はコンパレータ (
Comparator<T>
) です。keyExtractor
このコンパレータは、渡された関数とコンパレータに基づいてオブジェクトの比較を実行しますkeyComparator
。(Comparator<T> & Serializable) (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
(c1, c2) -> keyComparator.compare(keyExtractor.apply(c1), keyExtractor.apply(c2))
: これは、コンパレータの特定の実装を表すラムダ式です。2 つのオブジェクトを受け入れc1
、c2
まずkeyExtractor
関数を使用してこれら 2 つのオブジェクトからキー値を抽出し、次に関数を使用してkeyComparator
2 つのキー値を比較し、比較結果を返します。これは、キーと値に基づいたオブジェクト コンパレータを実装します。(Comparator<T> & Serializable)
: これは、ラムダ式を型に変換する型変換であり、コンパレータをシリアル化できるようにComparator<T>
マークされています。Serializable
このメソッドの主な目的は、抽出されたキー値とキー コンパレータに基づいてオブジェクトを並べ替えることができるコンパレータを作成することです。このアプローチにより、並べ替えがより柔軟になり、オブジェクト自体を直接比較するのではなく、オブジェクトの特定の属性に基づいて比較できるようになります。
<? super T、? extends U>説明
<? super T, ? extends U>
ワイルドカード
-
? super T
ワイルドカードの下限 (下限ワイルドカード) を表します。これは、タイプT
または のT
スーパータイプのオブジェクトが受け入れられることを意味します。これにより、T
より一般的なオブジェクト タイプを引数として渡すことができます。たとえば、T
is の場合Number
、 、または の他のスーパータイプを引数として受け入れる? super T
ことができます。Number
Object
Number
-
? extends U
ワイルドカードの上限 (上限ワイルドカード) を表します。これは、タイプ サブタイプU
またはのU
オブジェクトが受け入れられることを意味します。U
これにより、より具体的なオブジェクト タイプを引数として渡すことができます。たとえば、U
is の場合Integer
、 、または の他のサブタイプを引数として受け入れる? extends U
ことができます。Integer
Number
Integer
comparing
メソッドのソース コードでは、メソッドの柔軟性を高めるために、の種類keyExtractor
とワイルドカードを使用します。keyComparator
それらの型パラメータはそれぞれ です<? super T, ? extends U>
。ここで、T
はコンパレータの要素の型を表し、U
は比較されるキー値の型を表します。ワイルドカードを使用すると、comparing
メソッドがより広範囲の型パラメータを受け入れることができるため、さまざまなユースケースに適応する柔軟性が高まります。
コードサンプルの比較
comparing
package com.qfedu.Comparator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
/**
* public static <T, U> Comparator<T> comparing(
* Function<? super T, ? extends U> keyExtractor,
* Comparator<? super U> keyComparator)
* {
* Objects.requireNonNull(keyExtractor);
* Objects.requireNonNull(keyComparator);
* return (Comparator<T> & Serializable)
* (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
* keyExtractor.apply(c2));
* }
*/
public class ComparatorDemo {
public static void main(String[] args) {
// 创建一个包含Person对象的列表
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 35)
);
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
// 对列表进行排序
List<Person> sortedPeople = people.stream()
.sorted(ageComparator)
.collect(Collectors.toList());
// 输出排序后的结果
sortedPeople.forEach(person -> System.out.println(person.getName() + ": " + person.getAge()));
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
注釈の説明:
-
Person
名前や年齢の属性を含む、人を表すクラスを作成します。 -
このメソッドでは、オブジェクトを含むリスト
main
を作成します。Person
people
-
このメソッドを使用して、オブジェクトを経過時間によって比較するコンパレータ
Comparator.comparing
を作成します。以下は、年齢を比較のキー値として抽出するために使用されるキー抽出関数です。ageComparator
Person
person -> person.getAge()
-
sorted
メソッドを使用してpeople
リストをソートしageComparator
、ソートされたリストを取得しますsortedPeople
。 -
最後に、
sortedPeople
リストを反復処理し、年齢によって昇順にソートされたソート結果を出力します。
この例では、メソッドを使用してcomparing
コンパレータを作成し、キー値に基づいてオブジェクトを並べ替える方法を示します。ワイルドカードを使用すると、メソッドをさまざまなタイプのオブジェクトやキーに適応させること<? super T, ? extends U>
ができますcomparing
。この場合、キーは age で、オブジェクトは ですPerson
。
メソッドのソースコード分析例の比較
この例の比較メソッドは 1 つの関数パラメータのみを渡しFunction<? super T, ? extends U>
、メソッドの宣言は次のこと<T, U extends Comparable<? super U>>
を示しています。
- メソッドまたはクラスは、2 つのジェネリック型パラメーター
T
と を受け入れますU
。 U
Comparable
インターフェイスを実装する型である必要がありますが、Comparable
インターフェイスの任意のスーパータイプにすることができます。
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
<T、U は比較可能なものを拡張<? super U>> 讲解
このコード スニペットは<T, U extends Comparable<? super U>>
Java ジェネリック型パラメーター宣言であり、2 つのジェネリック型パラメーターT
と を含みU
、U
ジェネリック型を修飾します。
このコード スニペットが何を意味するのかを順を追って説明します。
-
<T, U>
T
: この部分は、メソッドまたはクラスで使用される型プレースホルダーである 2 つのジェネリック型パラメーターおよびの宣言を示しますU
。T
通常、 はオブジェクトの 1 つのタイプを表し、U
別のタイプを表し、比較のキーとして使用されます。 -
U extends Comparable<? super U>
: この部分はU
ジェネリック型を修飾します。これは、インターフェイスU
を実装する型である必要があることを意味します。このインターフェースは、Java でオブジェクトを比較するために使用されるインターフェースであり、オブジェクトが比較ロジックをカスタマイズできるようにするメソッドを定義します。Comparable
Comparable
compareTo
Comparable<U>
はインターフェイスU
を直接実装する必要があることを意味しますが、より柔軟に定義するためにを使用します。インターフェイスの任意のスーパータイプを表します。これにより、オブジェクトをそのサブクラスまたはスーパークラスのオブジェクトと比較できるようになります。Comparable
<? super U>
U
<? super U>
U
Comparable
U
まとめると、<T, U extends Comparable<? super U>>
この声明は次のように述べています。
- メソッドまたはクラスは、2 つのジェネリック型パラメーター
T
と を受け入れますU
。 U
Comparable
インターフェイスを実装する型である必要がありますが、Comparable
インターフェイスの任意のスーパータイプにすることができます。
この宣言の目的は、メソッドまたはクラスをより一般的かつ柔軟にし、さまざまなタイプのオブジェクトに適応できるようにし、特定のタイプに限定されるのではなく比較演算を実行できるようにすることです。これは、一般的な並べ替えまたは比較ロジックを作成する場合に便利です。
スーパータイプとはどういう意味ですか?
Java では、クラスまたはインターフェイスは別のクラスまたはインターフェイスを実装でき、実装されたクラスまたはインターフェイスはスーパータイプと呼ばれます。スーパータイプは、クラスまたはインターフェイスが別のクラスまたはインターフェイスの親クラスまたは親インターフェイスであることを示すために使用される広範な概念です。
「U は Comparable インターフェイスの任意のスーパータイプにできる」という場合、ジェネリック型パラメーターは、インターフェイスを直接実装するクラスだけでなく、インターフェイスを実装するクラスU
またはインターフェイスにもなり得ることを意味します。Comparable
Comparable
Comparable
これはメソッドの柔軟性を高め、インターフェイスを実装するクラスだけでなく、インターフェイスを実装するComparable
クラスのサブクラスからもオブジェクトを受け入れることができるため、非常に便利です。これは、サブクラスもスーパータイプの動作を継承するため、スーパータイプの一種とみなすことができるためです。
たとえば、インターフェイスFruit
を実装するクラスがあり、さらに のサブクラスであるクラスがあるとします。インターフェイスを実装するオブジェクトを受け入れるメソッドがある場合、このメソッドの宣言は次のように記述できます。Comparable
Apple
Fruit
Comparable
public <U extends Comparable<? super U>> void someMethod(U obj) {
// 方法体
}
このメソッドでは、U
表現はComparable
インターフェイスの任意のスーパータイプにすることができるため、Fruit
またはApple
オブジェクトをパラメーターとして受け入れることができます。これは、両方ともComparable
インターフェイスを実装しているためです。Comparable
このようにして、メソッドはより汎用になり、インターフェイスまたはそのスーパータイプを実装している限り、さまざまなタイプのオブジェクトを受け入れることができます。
ジェネリックにより柔軟性が向上する例
多機能性と柔軟性がどの程度あるのかよくわかりません。コード例を使用してそれを説明できますか? 比較したほうがよいでしょう。
ジェネリック メソッドやクラスを定義するときは、特定の型だけでなく、さまざまな型のデータを処理できるようにする必要があります。このようなジェネリック型パラメーター修飾を使用するとextends Comparable<? super U>
、さまざまな型のオブジェクトに適用できる一般的な比較方法を実装できます。
Comparable
以下に、ジェネリックの使用方法とジェネリック比較メソッドの作成方法の例を示します。まず、通常の比較方法を作成し、extends Comparable<? super U>
それをジェネリックで改良してより一般化します。
例 1: 一般的な比較方法
public static int compareIntegers(int a, int b) {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
}
public static void main(String[] args) {
int result = compareIntegers(5, 3);
System.out.println(result); // 输出 1
}
この例では、compareIntegers
メソッドは整数型のみを比較し、整数の結果を返します。
例 2:ジェネリックスとジェネリックスを使用したComparable
ジェネリック比較方法
public static <T extends Comparable<? super T>> int compareObjects(T a, T b) {
return a.compareTo(b);
}
public static void main(String[] args) {
int result1 = compareObjects(5, 3); // 使用整数比较
System.out.println(result1); // 输出 1
String str1 = "apple";
String str2 = "banana";
int result2 = compareObjects(str1, str2); // 使用字符串比较
System.out.println(result2); // 输出 -1
}
例 2 では、インターフェイスを実装する型である必要があるcompareObjects
2 つのジェネリック パラメーターを受け入れるジェネリック メソッドを定義し、ジェネリック型またはそのスーパータイプがインターフェイスを実装する必要があることを示すために を使用します。T
Comparable
extends Comparable<? super T>
T
Comparable
これにより、compareObjects
メソッドは整数だけでなく、さまざまなタイプのオブジェクトを比較できるようになります。これを使用して整数と文字列を比較することができ、compareTo
オブジェクトのメソッドに基づいて比較を実行して、普遍的な比較結果を生成します。
generic sum を通じてComparable
、さまざまなタイプのオブジェクトを比較するために使用できる汎用比較メソッドを実装します。これにより、メソッドの汎用性と柔軟性が向上します。これは、ジェネリックスと型パラメーター修飾の強力な側面です。
メソッド型パラメータの例
型パラメーター宣言は、メソッドまたはクラスをより一般化し、さまざまな型のデータに適応できるようにするために、メソッドまたはクラスで使用されるジェネリック型パラメーターを指定するために使用されます。以下に、メソッドの型パラメーター宣言を示すいくつかの例を示します。
-
シンプルな汎用メソッド
public <T> T findMax(T[] arr) { T max = arr[0]; for (T element : arr) { if (element.compareTo(max) > 0) { max = element; } } return max; }
この例では、
<T>
これは汎用配列を受け入れT[]
、配列内の最大の要素を返す汎用メソッドであることを意味します。T
型パラメータです。整数、文字列、カスタム オブジェクトなどの任意の参照型を指定できます。 -
複数の型パラメータを使用する
public <T, U> boolean areEqual(T obj1, U obj2) { return obj1.equals(obj2); }
この例には 2 つの型パラメータがあります
<T, U>
。このメソッドは 2 つのパラメータを受け入れます。1 つは型のT
オブジェクトobj1
、もう 1 つは型のU
オブジェクトですobj2
。このメソッドは、2 つのオブジェクトが等しいかどうかを比較します。 -
ジェネリッククラスの型パラメータ
public class Box<T> { private T value; public Box(T value) { this.value = value; } public T getValue() { return value; } }
Box<T>
これはジェネリック クラスの例です。T
は、ボックスに格納される値の型を表す型パラメーターです。このクラスは、さまざまなタイプの値を格納するために使用できます。 -
汎用インターフェイスの型パラメータ
public interface List<T> { void add(T item); T get(int index); }
これは、汎用リスト インターフェイスを表す汎用インターフェイスの例です
List<T>
。T
リスト内の要素のタイプを示すタイプパラメータです。具象リストの実装ではT
具象型を指定できます。
これらの例は、メソッドとクラスで型パラメーター宣言を使用して、さまざまな型のデータに適応する汎用性と柔軟性を実現する方法を示しています。型パラメーターを使用すると、異なる型が使用されるたびに異なるメソッドやクラスを作成する必要がなく、ジェネリック コードを作成できます。
ソートされたソースコード分析
Stream<T> sorted(Comparator<? super T> comparator);
sorted メソッドは、渡されたコンパレーターを使用してストリーム内の要素をソートし、コンパレーターで定義された順序で要素を含む新しいストリームを生成します。このメソッドは通常、コレクション内の要素を並べ替えるために使用されます。
// 使用comparing方法按年龄升序排序Person对象
Comparator<Person> ageComparator = Comparator.comparing(
person -> person.getAge() // 提取比较的键值:年龄
);
//sort(Comparator.comparing(People::getAge)); 也可以这么写
// 对列表进行排序
List<Person> sortedPeople = people.stream()
.sorted(ageComparator)
.collect(Collectors.toList());