Java8 の新機能およびソース コード分析と組み合わせた Comparator インターフェイスの使用方法

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 Integer 値が返されます。

//   people -> people.getAge(); 转换为下面方法引用
Function<People, Integer> getAge = People::getAge;

2 番目のパラメータ keyComparatorは理解しやすく、使用される比較ルールを表します。

はい c1c2最初のパラメータで指定されたルールに従って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

では、このソースコードを順を追って説明していきます。

  1. <T, U>: これはジェネリック メソッドの定義です。このメソッドには 2 つのジェネリック型パラメータ と がありTU比較する要素の型とキー値を抽出する型を表すために使用されることを示します。

  2. comparingこのメソッドは 2 つのパラメータを受け入れます。

    • keyExtractorTパラメーターは、 type のオブジェクトを受け入れ、Utype のキー値を返す関数です。この関数は、オブジェクトから比較のキー値を抽出するために使用されます。
    • keyComparatorパラメータは、 type のキー値を比較するコンパレータですU
  3. Objects.requireNonNull(keyExtractor)and : これら 2 行のコードは、Objects.requireNonNull(keyComparator)受信パラメータkeyExtractorがであるkeyComparatorかどうかを確認しnull、そうであればnullスローするために使用されますNullPointerException

  4. 戻り値: このメソッドの戻り値はコンパレータ ( 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 つのオブジェクトを受け入れc1c2まずkeyExtractor関数を使用してこれら 2 つのオブジェクトからキー値を抽出し、次に関数を使用してkeyComparator2 つのキー値を比較し、比較結果を返します。これは、キーと値に基づいたオブジェクト コンパレータを実装します。
    • (Comparator<T> & Serializable): これは、ラムダ式を型に変換する型変換であり、コンパレータをシリアル化できるようにComparator<T>マークされています。Serializable

このメソッドの主な目的は、抽出されたキー値とキー コンパレータに基づいてオブジェクトを並べ替えることができるコンパレータを作成することです。このアプローチにより、並べ替えがより柔軟になり、オブジェクト自体を直接比較するのではなく、オブジェクトの特定の属性に基づいて比較できるようになります。

<? super T、? extends U>説明

<? super T, ? extends U>ワイルドカード

  • ? super Tワイルドカードの下限 (下限ワイルドカード) を表します。これは、タイプTまたは のTスーパータイプのオブジェクトが受け入れられることを意味します。これにより、Tより一般的なオブジェクト タイプを引数として渡すことができます。たとえば、Tis の場合Number、 、または の他のスーパータイプを引数として受け入れる? super Tことができます。NumberObjectNumber

  • ? extends Uワイルドカードの上限 (上限ワイルドカード) を表します。これは、タイプ サブタイプUまたはのUオブジェクトが受け入れられることを意味します。Uこれにより、より具体的なオブジェクト タイプを引数として渡すことができます。たとえば、Uis の場合Integer、 、または の他のサブタイプを引数として受け入れる? extends Uことができます。IntegerNumberInteger

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;
    }
}

注釈の説明:

  1. Person名前や年齢の属性を含む、人を表すクラスを作成します。

  2. このメソッドでは、オブジェクトを含むリストmainを作成しますPersonpeople

  3. このメソッドを使用して、オブジェクトを経過時間によって比較するコンパレータComparator.comparingを作成します。以下は、年齢を比較のキー値として抽出するために使用されるキー抽出関数です。ageComparatorPersonperson -> person.getAge()

  4. sortedメソッドを使用してpeopleリストをソートしageComparator、ソートされたリストを取得しますsortedPeople

  5. 最後に、sortedPeopleリストを反復処理し、年齢によって昇順にソートされたソート結果を出力します。

この例では、メソッドを使用してcomparingコンパレータを作成し、キー値に基づいてオブジェクトを並べ替える方法を示します。ワイルドカードを使用すると、メソッドをさまざまなタイプのオブジェクトやキーに適応させること<? super T, ? extends U>ができますcomparingこの場合、キーは age で、オブジェクトは ですPerson

メソッドのソースコード分析例の比較

この例の比較メソッドは 1 つの関数パラメータのみを渡しFunction<? super T, ? extends U>、メソッドの宣言は次のこと<T, U extends Comparable<? super U>>を示しています。

  • メソッドまたはクラスは、2 つのジェネリック型パラメーターTと を受け入れますU
  • UComparableインターフェイスを実装する型である必要がありますが、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と を含みUUジェネリック型を修飾します。

このコード スニペットが何を意味するのかを順を追って説明します。

  1. <T, U>T: この部分は、メソッドまたはクラスで使用される型プレースホルダーである 2 つのジェネリック型パラメーターおよびの宣言を示しますUT通常、 はオブジェクトの 1 つのタイプを表し、U別のタイプを表し、比較のキーとして使用されます。

  2. U extends Comparable<? super U>: この部分はUジェネリック型を修飾します。これは、インターフェイスUを実装する型である必要があることを意味します。このインターフェースは、Java でオブジェクトを比較するために使用されるインターフェースであり、オブジェクトが比較ロジックをカスタマイズできるようにするメソッドを定義します。ComparableComparablecompareTo

    • Comparable<U>はインターフェイスUを直接実装する必要があることを意味しますが、より柔軟に定義するためにを使用しますインターフェイスの任意のスーパータイプを表しますこれにより、オブジェクトをそのサブクラスまたはスーパークラスのオブジェクトと比較できるようになります。Comparable<? super U>U<? super U>UComparableU

まとめると、<T, U extends Comparable<? super U>>この声明は次のように述べています。

  • メソッドまたはクラスは、2 つのジェネリック型パラメーターTと を受け入れますU
  • UComparableインターフェイスを実装する型である必要がありますが、Comparableインターフェイスの任意のスーパータイプにすることができます。

この宣言の目的は、メソッドまたはクラスをより一般的かつ柔軟にし、さまざまなタイプのオブジェクトに適応できるようにし、特定のタイプに限定されるのではなく比較演算を実行できるようにすることです。これは、一般的な並べ替えまたは比較ロジックを作成する場合に便利です。

スーパータイプとはどういう意味ですか?

Java では、クラスまたはインターフェイスは別のクラスまたはインターフェイスを実装でき、実装されたクラスまたはインターフェイスはスーパータイプと呼ばれます。スーパータイプは、クラスまたはインターフェイスが別のクラスまたはインターフェイスの親クラスまたは親インターフェイスであることを示すために使用される広範な概念です。

「U は Comparable インターフェイスの任意のスーパータイプにできる」という場合、ジェネリック型パラメーターは、インターフェイスを直接実装するクラスだけでなく、インターフェイスを実装するクラスUまたはインターフェイスにもなり得ることを意味します。Comparable Comparable

Comparableこれはメソッドの柔軟性を高め、インターフェイスを実装するクラスだけでなく、インターフェイスを実装するComparableクラスのサブクラスからもオブジェクトを受け入れることができるため、非常に便利です。これは、サブクラスもスーパータイプの動作を継承するため、スーパータイプの一種とみなすことができるためです。

たとえば、インターフェイスFruitを実装するクラスがあり、さらに のサブクラスであるクラスがあるとします。インターフェイスを実装するオブジェクトを受け入れるメソッドがある場合、このメソッドの宣言は次のように記述できます。ComparableAppleFruitComparable

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 では、インターフェイスを実装する型である必要があるcompareObjects2 つのジェネリック パラメーターを受け入れるジェネリック メソッドを定義し、ジェネリック型またはそのスーパータイプがインターフェイスを実装する必要があることを示すために を使用します。TComparableextends Comparable<? super T>TComparable

これにより、compareObjectsメソッドは整数だけでなく、さまざまなタイプのオブジェクトを比較できるようになります。これを使用して整数と文字列を比較することができ、compareToオブジェクトのメソッドに基づいて比較を実行して、普遍的な比較結果を生成します。

generic sum を通じてComparable、さまざまなタイプのオブジェクトを比較するために使用できる汎用比較メソッドを実装します。これにより、メソッドの汎用性と柔軟性が向上します。これは、ジェネリックスと型パラメーター修飾の強力な側面です。

メソッド型パラメータの例

型パラメーター宣言は、メソッドまたはクラスをより一般化し、さまざまな型のデータに適応できるようにするために、メソッドまたはクラスで使用されるジェネリック型パラメーターを指定するために使用されます。以下に、メソッドの型パラメーター宣言を示すいくつかの例を示します。

  1. シンプルな汎用メソッド

    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型パラメータです。整数、文字列、カスタム オブジェクトなどの任意の参照型を指定できます。

  2. 複数の型パラメータを使用する

    public <T, U> boolean areEqual(T obj1, U obj2) {
          
          
        return obj1.equals(obj2);
    }
    

    この例には 2 つの型パラメータがあります<T, U>このメソッドは 2 つのパラメータを受け入れます。1 つは型のTオブジェクトobj1、もう 1 つは型のUオブジェクトですobj2このメソッドは、2 つのオブジェクトが等しいかどうかを比較します。

  3. ジェネリッククラスの型パラメータ

    public class Box<T> {
          
          
        private T value;
        
        public Box(T value) {
          
          
            this.value = value;
        }
        
        public T getValue() {
          
          
            return value;
        }
    }
    

    Box<T>これはジェネリック クラスの例です。Tは、ボックスに格納される値の型を表す型パラメーターです。このクラスは、さまざまなタイプの値を格納するために使用できます。

  4. 汎用インターフェイスの型パラメータ

    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());

おすすめ

転載: blog.csdn.net/qq_41398619/article/details/132991549
おすすめ