1. 概要

このチュートリアルでは、Scalaの平等の概念を確認します。 また、Javaでの等式の仕組みとの比較も行います。

2. 演算子==および!=

Scalaでは、単一パラメーターメソッドは中置記法をサポートしているため、演算子と呼ばれることがよくあります。

たとえば、 == != は、Scalaクラス階層のルートであるクラスAnyのメソッドにすぎません。 Any の別のインスタンスをパラメーターとして受け取り、Booleanの結果を返します。 !=== の否定であるため、等式演算子がどのように機能するかを理解するだけで十分です。

Javaプリミティブタイプに対応するAnyValインスタンスの場合、演算子は、Javaの場合と同様に、2つのプリミティブが同じであるかどうかを比較します。

val intAnyVal = 4
assert(intAnyVal == 2 * 2)

参照タイプの場合、状況は異なります。 対応するJavaとは異なり、等式演算子は対応する参照を比較しませんが、AnyRef のインスタンスに対するnullセーフなequals()呼び出しになります。

val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
val thirdString = null
val fourthString = null
assert(firstString == secondString)
// Unlike in java, the following lines of code do not cause NullPointerExceptions
assert(thirdString != secondString)
assert(fourthString == thirdString)

これはScalaの慣用的なアプローチではありませんが、 equals()を明示的に呼び出すこともできます。

val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
assert(firstString.equals(secondString))

3. 演算子eqおよびne

equals()のオーバーライドされたバージョンは、構造的同等性の概念を定義します。

ただし、場合によっては、次のようなより強い条件を確認する必要があります。 2つの参照がヒープ内の同じオブジェクトを指しているかどうか。 これは、参照の平等として知られています。

演算子eqとそれに対応する否定neは、Scalaでこの目的を果たします。

val firstString = new String("AnyRef")
val secondString = new String("AnyRef")
val thirdString = secondString
assert(firstString ne secondString)
assert(thirdString eq secondString)
// Both operators are null-safe
assert(null eq null)
assert(null ne firstString)

4. equals()および hashcode()オーバーライド

型推論とシンタックスシュガーによりScalaはJavaよりも冗長性が低くなりますが、すぐに使用できる適切な等価セマンティクスはありません。

ケースクラスを定義しない限り、必要に応じて equals()および hashcode()バージョンがコンパイラーによって生成されます。通常のScalaクラスでそれらをオーバーライドするのは私たちの責任です[ X200X]。 そうしないと、期待どおりに平等が機能しない可能性があります。

それを説明するために、簡単なクラスを定義しましょう。

class PersonSimpleClass(val name: String, val age: Int)

メソッドequals()をオーバーライドしないため、PersonSimpleClassインスタンスでは等式は機能しません。

val firstSimpleClassInstance = new PersonSimpleClass("Donald", 66)
val secondSimpleClassInstance = new PersonSimpleClass("Donald", 66)
assert(firstSimpleClassInstance != secondSimpleClassInstance)

それを修正するために、適切にオーバーライドされたメソッドを使用してクラスを作成しましょう。

class PersonClassWithOverrides(val name: String, val age: Int) {
  override def equals(other: Any): Boolean = other match {
    case person: PersonClassWithOverrides =>
      this.name == person.name && this.age == person.age
    case _ => false
  }

  override def hashCode(): Int = if (name eq null) age else name.hashCode + 31 * age
}

PersonSecond equals()の実装は、Javaアナログと比較して非常にコンパクトであることに注意してください。 主に、これはパターンマッチングと等式演算子のnull-safetyの結果です。 この場合、演算子==は期待どおりに機能します。

val firstClassWithOverridesInstance = new PersonClassWithOverrides("Donald", 66)
val secondClassWithOverridesInstance = new PersonClassWithOverrides("Donald", 66)
assert(firstClassWithOverridesInstance == secondClassWithOverridesInstance)

最後に、平等の問題を処理し、ケースクラスを定義する最も簡単な方法を示しましょう。

case class PersonCaseClass(name: String, age: Int)

他の便利な機能に加えて、caseクラスにはScalaコンパイラーによって生成された hashcode()および equals()実装があります。 したがって、同等性をテストするときに安全に使用できます。

val firstCaseClassInstance = PersonCaseClass("Donald", 66)
val secondCaseClassInstance = PersonCaseClass("Donald", 66)
assert(firstCaseClassInstance == secondCaseClassInstance)

5. 結論

この記事では、Scalaの平等性について考察し、Javaからのシームレスな移行に関連する機能をリストしました。

いつものように、この記事のすべてのコードはGitHubで入手できます。