JavaRush /Java Blog /Random-JA /Javaのコンパレータ
Viacheslav
レベル 3

Javaのコンパレータ

Random-JA グループに公開済み
Java で Comparator と比較について書いていないのは怠け者だけです。私は怠け者ではありませんので、もう 1 つのバリエーションを気に入っていただけるようお願いします。余計なものにならないことを願っています。そして、はい、この記事は「メモリからコンパレータを作成できますか?」という質問に対する答えです。この記事を読んだ後、誰もが記憶に基づいてコンパレーターを書けるようになることを願っています。
Java のコンパレータ - 1
はじめに Java はオブジェクト指向言語として知られています。そのため、Java ではオブジェクトを操作するのが一般的です。しかし、遅かれ早かれ、何らかの原則に従ってオブジェクトを比較するというタスクが発生します。したがって、次のようになります。 Message クラスで記述されたメッセージがあります。
public static class Message {
    private String message;
    private int id;

    public Message(String message) {
        this.message = message;
        this.id = new Random().nextInt(1000);
    }
    public String getMessage() {
        return message;
    }
    public Integer getId() {
        return id;
    }
    public String toString() {
        return "[" + id + "] " + message;
    }
}
このクラスをTutorialspoint Java コンパイラ に追加しましょう。インポートを追加することも忘れないでください。
import java.util.Random;
import java.util.ArrayList;
import java.util.List;
main メソッドでは、いくつかのメッセージを作成します。
public static void main(String[] args){
    List<Message> messages = new ArrayList();
    messages.add(new Message("Hello, World!"));
    messages.add(new Message("Hello, Sun!"));
    System.out.println(messages);
}
比較したい場合はどうすればよいか考えてみましょう。たとえば、ID で並べ替えたいとします。そして、順序を作成するには、どのオブジェクトが前にある (つまり、小さい) か、どのオブジェクトが次 (つまり、大きい) かを理解するために、何らかの方法でオブジェクトを比較する必要があります。java.lang.Objectのようなクラスから始めましょう。ご存知のとおり、すべてのクラスはこの Object クラスを暗黙的に継承します。そしてこれは論理的です、なぜなら これは本質的に「すべてはオブジェクトである」という概念を表しており、すべてのクラスに共通の動作を提供します。そして、このクラスは、各クラスが 2 つのメソッドを持つことを定義します。 → hashCode hashCode メソッドは、オブジェクトの数値 (int) 表現をクラスのインスタンスとして返します。それはどういう意味ですか?これは、クラスの 2 つの異なるインスタンスを作成した場合、インスタンスが異なるため、ハッシュコードも異なるはずであることを意味します。メソッドの説明には次のように書かれています。「合理的に実用的である限り、クラス Object によって定義された hashCode メソッドは、個別のオブジェクトに対して個別の整数を返します。」つまり、これらが 2 つの異なるインスタンスである場合、それらは異なるものを持つ必要があります。ハッシュコード。つまり、この方法は比較には適していません。→等しい equals メソッドは、「オブジェクトは等しいか」という質問に答え、ブール値を返します。このメソッドにはデフォルトのコードがあります。
public boolean equals(Object obj) {
    return (this == obj);
}
つまり、オブジェクトのこのメソッドをオーバーライドすることなく、このメソッドは基本的にオブジェクトへの参照が一致するかどうかを示します。これは私たちのメッセージには適していません。オブジェクトへのリンクには興味がなく、メッセージ ID に興味があるからです。そして、equals メソッドをオーバーライドしたとしても、得られる最大値は「それらは等しい」または「それらは等しくない」です。しかし、これだけでは順序を決定するのに十分ではありません。

Java のコンパレータと比較可能

私たちに合ったものは何でしょうか?「比較する」という単語を翻訳ツールで英語に翻訳すると、「compare」という翻訳が得られます。なるほど、それなら比較してくれる人が必要ですね。このコンペアを比較する場合、比較するのはコンパレータです。Java APIを開いて、そこにあるComparatorを見つけてみましょう。そして実際、そのようなインターフェイスがあります - java.util.Comparator java.util.Comparator および java.lang.Comparable ご覧のとおり、そのようなインターフェイスがあります。それを実装するクラスは「オブジェクトを比較する関数を実装しています」と述べています。本当に覚えておくべき唯一のことは、次のように表現される比較契約です。

Comparator возвращает int по следующей схеме: 
  • отрицательный int (первый an object отрицательный, то есть меньше)
  • положительный int (первый an object положительный, хороший, то есть больший)
  • ноль = an objectы равны
それではコンパレータを書いてみましょう。java.util.Comparatorをインポートする必要があります。インポート後、メソッドを main に追加します。 Comparator<Message> comparator = new Comparator<Message>(); 当然、これは機能しません。コンパレータはインターフェースです。したがって、括弧の後に中括弧を追加します{ }。これらの括弧内にメソッドを記述します。
public int compare(Message o1, Message o2) {
    return o1.getId().compareTo(o2.getId());
}
これを忘れずに書く必要さえありません。コンパレーターとは、比較を実行する、つまり比較を行う人です。比較されるオブジェクトがどのような順序で並んでいるのかという質問に答えるために、int を返します。実はそれだけです。シンプルかつ簡単に。例からわかるように、Comparator に加えて、別のインターフェイスjava.lang.Comparableがあり、これを実装するには、 compareToメソッドを定義する必要があります。このインターフェイスは、「インターフェイスを実装するクラスにより、クラスのインスタンスを比較できるようになります」と述べています。たとえば、Integer の CompareTo の実装は次のようになります。
(x < y) ? -1 : ((x == y) ? 0 : 1)
これらすべてのインターフェースをどのように覚えればよいでしょうか? 何のために?すべては英語から来ています。Compare - 比較することを意味し、比較する人は Comparator (たとえばレジストラとして。つまり、登録する人) であり、「比較される」という形容詞は Comparable です。さて、「Compare with」は「比較する」だけでなく「比較する」とも訳されます。それは簡単です。Java 言語は英語を話す人々によって書かれており、Java のすべてに名前を付ける際には、単純に英語に導かれ、その名前にはある種の論理がありました。そして、compareTo メソッドは、クラスのインスタンスを他のインスタンスと比較する方法を記述します。たとえば、文字列は辞書順に比較され、数値は値によって比較されます。
Java のコンパレータ - 2
Java 8 はいくつかの素晴らしい変更をもたらしました。Comparator インターフェイスをよく見ると、その上に注釈があることがわかります@FunctionalInterface。実際、この注釈は情報提供を目的としており、このインターフェイスが機能することを意味します。これは、このインターフェイスには実装のない抽象メソッドが 1 つだけあることを意味します。これは私たちに何をもたらすのでしょうか?次のようにコンパレータ コードを記述できます。
Comparator<Message> comparator = (o1, o2) -> o1.getId().compareTo(o2.getId());
括弧内は変数の名前の付け方です。Java 自体はそれを認識します。メソッドが 1 つしかない場合、どの入力パラメータが必要か、その数、タイプは明らかです。次に、コードのこのセクションに転送したいことを矢印で示します。さらに、Java 8 のおかげで、デフォルトのメソッドがインターフェイスに登場しました。これらは、インターフェイスを実装するときにデフォルトで (デフォルトで) 表示されるメソッドです。Comparator インターフェイスには、これらのいくつかがあります。たとえば、次のとおりです。
Comparator moreImportant = Comparator.reverseOrder();
Comparator lessImportant = Comparator.naturalOrder();
コードをきれいにする別の方法があります。コンパレータについて説明した上記の例を見てみましょう。彼は何をしているの?かなり原始的なんです。単純にオブジェクトを取得し、そこから比較可能な値を抽出します。たとえば、Integer は比較可能なものを実装しているため、メッセージ ID 値に対して CompareTo を実行できます。この単純なコンパレータ関数は次のように書くこともできます。
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
つまり、文字通り、「次のように比較する Comparator があります。オブジェクトを取得し、getId() メソッドを使用してそれらから Comparable を取得し、compareTo を使用して比較します。」ひどいデザインはもう必要ありません。そして最後に、もう 1 つの特徴について触れておきたいと思います。コンパレータは連鎖的に接続できます。例えば:
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
comparator = comparator.thenComparing(obj -> obj.getMessage().length());

応用

コンパレータの宣言は非常に論理的であることがわかりましたね。次に、それをどのように、どのような場所で使用するかを確認する必要があります。→ Collections.sort (java.util.Collections) もちろん、この方法でコレクションをソートすることもできます。ただし、すべてではなく、リストだけです。そして、ここには何も珍しいものはありません、なぜなら... これは、インデックスによる要素へのアクセスを必要とするリストです。これにより、要素番号 2 を要素番号 3 と交換できるようになります。したがって、この方法での並べ替えはリストに対してのみ可能です。
Comparator<Message> comparator = Comparator.comparing(obj -> obj.getId());
Collections.sort(messages, comparator);
Arrays.sort (java.util.Arrays) 配列もソートに便利です。繰り返しますが、インデックスによって要素にアクセスするのと同じ理由です。→ java.util.SortedSet と java.util.SortedMap の子孫 ご存知のとおり、Set と Map はレコードの格納順序を保証しません。ただし、順序を保証する特別な実装があります。また、コレクション要素が java.lang.Comparable を実装していない場合は、そのようなコレクションのコンストラクターに Comparator を渡すことができます。
Set<Message> msgSet = new TreeSet(comparator);
ストリーム API Java 8 で登場したストリーム API では、コンパレータを使用してストリーム要素の作業を簡素化できます。たとえば、0 から 999 までの一連の乱数が必要です。
Supplier<Integer> randomizer = () -> new Random().nextInt(1000);
Stream.generate(randomizer)
    .limit(10)
    .sorted(Comparator.naturalOrder())
    .forEach(e -> System.out.println(e));
停止することもできますが、さらに興味深い問題があります。たとえば、キーがメッセージ ID であるマップを準備する必要があります。同時に、これらのキーを最小から最大の順に並べ替えたいと考えています。このコードから始めましょう:
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg));
ここで返されるのは実際には HashMap です。そしてご存知のとおり、それはいかなる順序も保証するものではありません。したがって、ID でソートされたレコードの順序が狂ってしまっただけです。良くない。コレクターを少し変更する必要があります。
Map<Integer, Message> collected = Arrays.stream(messages)
                .sorted(Comparator.comparing(msg -> msg.getId()))
                .collect(Collectors.toMap(msg -> msg.getId(), msg -> msg, (oldValue, newValue) -> oldValue, TreeMap::new));
コードは少し不気味に見えましたが、TreeMap の明示的な実装のおかげで問題は正しく解決されました。さまざまなグループについて詳しくは、こちらをご覧ください。 コレクターは自分で作成できます。詳細については、 「Java 8 でのカスタム コレクターの作成」を参照してください。ここでの議論を読むと役に立ちます: 「ストリームにマップする Java 8 リスト」
Java のコンパレータ - 3
Comparator と Comparable rake は 優れています。しかし、それらに関連する、覚えておく価値のあるニュアンスが 1 つあります。クラスはソートを実行するときに、クラスを Comparable にキャストできるかどうかを計算します。そうでない場合は、実行時にエラーが発生します。例を見てみましょう:
SortedSet<Message> msg = new TreeSet<>();
msg.add(new Message(2, "Developer".getBytes()));
ここには何も問題はないようです。しかし実際には、この例では、 java.lang.ClassCastException: Message cannot be cast to java.lang.Comparable 要素を並べ替えようとしたため、次のエラーが発生してクラッシュします (結局のところ、これは SortedSet です)。そして私にはできませんでした。SortedMap および SortedSet を使用するときは、これを覚えておく必要があります。 さらに 閲覧をお勧めします: Yuri Tkach: HashSet と TreeSet - コレクション #1 - 高度な Java
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION