1. 序章

オブジェクトの比較は、オブジェクト指向プログラミング言語の重要な機能です。

このチュートリアルでは、オブジェクトを比較できるJava言語の機能のいくつかを探ります。 また、外部ライブラリのそのような機能についても見ていきます。

2.2。 == != 演算子

== および!= 演算子から始めましょう。これらの演算子は、2つのJavaオブジェクトがそれぞれ同じかどうかを判断できます。

2.1. プリミティブ

プリミティブタイプの場合、同じであるということは、等しい値を持つことを意味します。

assertThat(1 == 1).isTrue();

自動アンボックスのおかげで、これはプリミティブ値を対応するラッパータイプと比較するときにも機能します。

Integer a = new Integer(1);
assertThat(1 == a).isTrue();

2つの整数の値が異なる場合、==演算子はfalseを返し、!=演算子はtrueを返します。

2.2. オブジェクト

同じ値を持つ2つのIntegerラッパータイプを比較するとします。

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a == b).isFalse();

2つのオブジェクトを比較することにより、 それらのオブジェクトの値は1ではありません。 むしろ、それはスタック内のそれらのメモリアドレスです両方のオブジェクトがを使用して作成されるため、これらは異なります新着オペレーター。 abに割り当てた場合、結果は異なります。

Integer a = new Integer(1);
Integer b = a;

assertThat(a == b).isTrue();

次に、 Integer#valueOfファクトリメソッドを使用するとどうなるかを見てみましょう。

Integer a = Integer.valueOf(1);
Integer b = Integer.valueOf(1);

assertThat(a == b).isTrue();

この場合、それらは同じと見なされます。 これは、 valueOf()メソッドが Integer をキャッシュに格納して、同じ値でラッパーオブジェクトが多すぎないようにするためです。 したがって、このメソッドは、両方の呼び出しに対して同じIntegerインスタンスを返します。

Javaは、Stringに対してもこれを行います。

assertThat("Hello!" == "Hello!").isTrue();

ただし、 new 演算子を使用して作成された場合、それらは同じにはなりません。

最後に、 2つのnull参照は同じと見なされますが、null以外のオブジェクトはnullとは異なると見なされます。

assertThat(null == null).isTrue();

assertThat("Hello!" == null).isFalse();

もちろん、等式演算子の動作は制限される可能性があります。 異なるアドレスにマップされた2つのオブジェクトを比較したいが、それらの内部状態に基づいてそれらが等しいと見なされた場合はどうなりますか? これを行う方法については、次のセクションで説明します。

3.3。 Object#equals 方法

次に、 equals()メソッドを使用したより広い等式の概念について説明します。

このメソッドはObjectクラスで定義されているため、すべてのJavaオブジェクトがこのメソッドを継承します。 デフォルトでは、の実装はオブジェクトのメモリアドレスを比較するため、==演算子と同じように機能します。 ただし、オブジェクトにとって同等性が何を意味するかを定義するために、このメソッドをオーバーライドできます。

まず、Integerのような既存のオブジェクトに対してどのように動作するかを見てみましょう。

Integer a = new Integer(1);
Integer b = new Integer(1);

assertThat(a.equals(b)).isTrue();

両方のオブジェクトが同じである場合でも、メソッドはtrueを返します。

null オブジェクトをメソッドの引数として渡すことはできますが、メソッドを呼び出すオブジェクトとして渡すことはできないことに注意してください。

equals()メソッドを独自のオブジェクトで使用することもできます。 Personクラスがあるとしましょう。

public class Person {
    private String firstName;
    private String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

このクラスのequals()メソッドをオーバーライドして、内部の詳細に基づいて2つのPersonを比較できます。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Person that = (Person) o;
    return firstName.equals(that.firstName) &&
      lastName.equals(that.lastName);
}

詳細については、このトピックに関するの記事をご覧ください。

4.4。 Objects#equals 静的メソッド

次に、 Objects#equals staticmethodを見てみましょう。 最初のオブジェクトの値としてnullを使用できないことは前述しました。そうしないと、NullPointerExceptionがスローされます。

Objectsヘルパークラスのequals()メソッドは、その問題を解決します。 2つの引数を取り、それらを比較し、null値も処理します。

Personオブジェクトをもう一度比較してみましょう。

Person joe = new Person("Joe", "Portman");
Person joeAgain = new Person("Joe", "Portman");
Person natalie = new Person("Natalie", "Portman");

assertThat(Objects.equals(joe, joeAgain)).isTrue();
assertThat(Objects.equals(joe, natalie)).isFalse();

説明したように、このメソッドはnull値を処理します。 したがって、両方の引数が null、の場合は、 true を返し、一方だけが null の場合は、false[を返します。 X140X]。

これは本当に便利です。 オプションの誕生日をPersonクラスに追加するとします。

public Person(String firstName, String lastName, LocalDate birthDate) {
    this(firstName, lastName);
    this.birthDate = birthDate;
}

次に、 equals()メソッドを更新する必要がありますが、null処理を使用します。 これを行うには、 equals()メソッドに条件を追加します。

birthDate == null ? that.birthDate == null : birthDate.equals(that.birthDate);

ただし、クラスにnull許容フィールドを追加しすぎると、非常に煩雑になる可能性があります。 equals()実装で Objects#equals メソッドを使用すると、はるかにクリーンになり、読みやすさが向上します。

Objects.equals(birthDate, that.birthDate);

5.5。 同程度のインターフェース

比較ロジックを使用して、オブジェクトを特定の順序で配置することもできます。 Comparableインターフェースを使用すると、オブジェクトが別のオブジェクトよりも大きいか、等しいか、または小さいかを判別することにより、オブジェクト間の順序を定義できます

Compareable インターフェイスは汎用であり、 compareTo()という1つのメソッドしかありません。このメソッドは、汎用型の引数を取り、intを返します。 戻り値は、 this が引数よりも小さい場合は負、等しい場合は0、それ以外の場合は正です。

たとえば、 Person クラスで、Personオブジェクトを名前で比較したいとします。

public class Person implements Comparable<Person> {
    //...

    @Override
    public int compareTo(Person o) {
        return this.lastName.compareTo(o.lastName);
    }
}

compareTo()メソッドは、thisよりも大きい姓を持つPersonで呼び出された場合、負の int を返し、同じ姓で、それ以外の場合は正です。

詳細については、このトピックに関するの記事を参照してください。

6.6。 コンパレータインターフェース

コンパレータインターフェイスは汎用であり、その汎用タイプの2つの引数を取り、整数を返すcompareメソッドを備えています。 このパターンは、Compareableインターフェースですでに見ました。

コンパレータも同様です。 ただし、クラスの定義からは分離されています。 したがって、クラスに必要な数のコンパレータを定義できます。この場合、提供できるコンパレータの実装は1つだけです。

テーブルビューでユーザーを表示するWebページがあり、ユーザーが名前ではなく名前で並べ替えることができるようにしたいとします。 現在の実装も維持したい場合、 Compareable ではこれは不可能ですが、独自のコンパレータを実装することはできます。

Person コンパレータを作成して、名前だけで比較します。

Comparator<Person> compareByFirstNames = Comparator.comparing(Person::getFirstName);

それでは、そのコンパレータを使用している人々のリストを並べ替えましょう。

Person joe = new Person("Joe", "Portman");
Person allan = new Person("Allan", "Dale");

List<Person> people = new ArrayList<>();
people.add(joe);
people.add(allan);

people.sort(compareByFirstNames);

assertThat(people).containsExactly(allan, joe);

コンパレータインターフェイスには、 compareTo()の実装で使用できる他のメソッドもあります。

@Override
public int compareTo(Person o) {
    return Comparator.comparing(Person::getLastName)
      .thenComparing(Person::getFirstName)
      .thenComparing(Person::getBirthDate, Comparator.nullsLast(Comparator.naturalOrder()))
      .compare(this, o);
}

この場合、最初に姓を比較し、次に名を比較します。 次に、生年月日を比較しますが、null許容型であるため、その処理方法を指定する必要があります。これを行うには、2番目の引数を指定して、自然な順序に従って比較する必要があります。 null値が最後になります。

7. Apache Commons

ApacheCommonsライブラリを見てみましょう。 まず、Maven依存関係をインポートしましょう。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

7.1。 ObjectUtils#notEqual 方法

まず、 ObjectUtils#notEqualメソッドについて説明します。 独自のequals()メソッドの実装に従って、等しくないかどうかを判断するには、2つのObject引数が必要です。 また、null値も処理します。

Stringの例を再利用しましょう。

String a = new String("Hello!");
String b = new String("Hello World!");

assertThat(ObjectUtils.notEqual(a, b)).isTrue();

ObjectUtilsにはequals()メソッドがあることに注意してください。 ただし、 Objects#equalsが登場したJava7以降は非推奨です。

7.2。 ObjectUtils#compare 方法

次に、オブジェクトの順序を ObjectUtils#compareメソッドと比較してみましょう。 これは、そのジェネリック型の2つのComparable引数を取り、整数を返すジェネリックメソッドです。

もう一度Stringsを使用して見てみましょう。

String first = new String("Hello!");
String second = new String("How are you?");

assertThat(ObjectUtils.compare(first, second)).isNegative();

デフォルトでは、メソッドはnull値をより大きく考慮して処理します。 また、ブール引数を使用して、その動作を反転し、それらをより少なく考えることを提供するオーバーロードバージョンも提供します。

8. グアバ

Guavaを見てみましょう。 まず、依存関係をインポートしましょう。

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>31.0.1-jre</version>
</dependency>

8.1。 Objects#equal 方法

Apache Commonsライブラリと同様に、Googleは、2つのオブジェクトが等しいかどうかを判断するメソッド Objects#equalを提供しています。 実装は異なりますが、同じ結果が返されます。

String a = new String("Hello!");
String b = new String("Hello!");

assertThat(Objects.equal(a, b)).isTrue();

非推奨としてマークされていませんが、Java7はObjects#equals メソッドを提供するため、このメソッドのJavaDocは非推奨と見なす必要があると述べています。

8.2. 比較方法

Guavaライブラリは2つのオブジェクトを比較するメソッドを提供していませんが(次のセクションでそれを達成するために何ができるかを見ていきます)、プリミティブ値を比較するメソッドを提供します Ints ヘルパークラスを取り上げて、その compare()メソッドがどのように機能するかを見てみましょう。

assertThat(Ints.compare(1, 2)).isNegative();

いつものように、 integer を返します。これは、最初の引数が2番目の引数よりも小さい、等しい、または大きい場合、それぞれ負、ゼロ、または正になる可能性があります。 bytes を除いて、すべてのプリミティブタイプに同様のメソッドが存在します。

8.3。 ComparisonChain クラス

最後に、Guavaライブラリは ComparisonChain クラスを提供します。これにより、一連の比較を通じて2つのオブジェクトを比較できます。 2つのPersonオブジェクトを、名前と名前で簡単に比較できます。

Person natalie = new Person("Natalie", "Portman");
Person joe = new Person("Joe", "Portman");

int comparisonResult = ComparisonChain.start()
  .compare(natalie.getLastName(), joe.getLastName())
  .compare(natalie.getFirstName(), joe.getFirstName())
  .result();

assertThat(comparisonResult).isPositive();

基礎となる比較はcompareTo()メソッドを使用して行われるため、 compare()メソッドに渡される引数はプリミティブまたはCompareableのいずれかである必要があります。

9. 結論

この記事では、Javaでオブジェクトを比較するさまざまな方法を学びました。 同一性、平等性、順序付けの違いを調べました。 また、ApacheCommonsおよびGuavaライブラリの対応する機能についても調べました。

いつものように、この記事の完全なコードはGitHubにあります。