Edited at

==演算子とequalsメソッドの違い【Scala編】

More than 1 year has passed since last update.

先日の投稿で、Javaでの同一同値比較について書きました。そして現在の業務ではScalaを触ることもありまして、ついでにScalaについても少し調べてみました。


プリミティブ型と参照型

Scalaについてですが、調べたところプリミティブ型は存在しないとのことです。前回の投稿でshiracamusさんにいただいたコメントの中で知りましたがPythonも同様のようで、全てがオブジェクトとして扱われる言語のようです。そしてScalaでは全てのクラス、オブジェクトはAnyクラスを継承しています。その直下にはAnyValクラスとAnyRefクラスがあり、Javaでのプリミティブ型に近い役割を担っているクラスはAnyValクラスを、それ以外のクラスはAnyRefクラスを継承しています。

Any

├── AnyVal <- Javaでいうプリミティブ型に相当する
│ ├── Boolean
│ ├── Byte
│ ├── Char
│ ├── Double
│ ├── Float
│ ├── Int
│ ├── Long
│ └── Short
└── AnyRef <- Javaでいう参照型に相当する

.
.
.

ちなみにAnyValクラスを継承しているクラスにはnullを代入できないようです。


同値比較

ではScalaでの"==演算子"はどのような働きをするのでしょうか?実際に動かしてみました。

object Main {

def main(args: Array[String]) = {

val stringA = new String("文字列")
val stringB = new String("文字列")

println(stringA == stringB) // true
println(stringA.equals(stringB)) // true

}

}

"==演算子"も"equalsメソッド"も共にtrueを返しています。ここで一つ致命的な間違いを知るわけなんですが、そもそもScalaでは==は演算子ではなくメソッドだということです。全てがオブジェクトなので、メソッドだということにも納得がいきます。ちなみに"=="は ".==()"とも書くことができ、Anyクラスが持つメソッドです。そしてequalsメソッドを実装していればそちらに処理を委譲するそうです。つまり


  • stringA == stringB

  • stringA.==(stringB)

  • stringA.equals(stringB)

は全て同じ動作となります。ScalaのStringはJavaのStringを内部で持っていて、equalsメソッドはjava.lang.String#equalsを使用しています。

ではequalsメソッドを持たないクラスではどうなるのか、equalsメソッドを持たないクラスを作り、==メソッドを使ってみたいと思います。

class Value(val value: String)

object Main {

def main(args: Array[String]) = {

val valeuA = new Value("文字列")
val valeuB = new Value("文字列")

println(valeuA == valeuB) // false

}

}

equalsメソッドを実装していないと、java.lang.Object#equalsが呼ばれるようです。Object#equalsでは同一比較を行うので、falseになると。

ではequalsメソッドを実装して再度実行してみる

class Value(val value: String) {

def canEqual(a: Any) = a.isInstanceOf[Value]
override def equals(that: Any): Boolean =
that match {
case that: Value => that.canEqual(this) && this.hashCode == that.hashCode
case _ => false
}
override def hashCode: Int = {
val prime = 31
var result = 1
result = prime * result + (if (value == null) 0 else value.hashCode)
return result
}
}

object Main {

def main(args: Array[String]) = {

val valeuA = new Value("文字列")
val valeuB = new Value("文字列")

println(valeuA == valeuB) // true

}

}

実装したequalsメソッドを呼び出しtrueとなりました。

ちなみにcase classにすると自動でequalsメソッドが実装されます。

case class Value(value: String)

object Main {

def main(args: Array[String]) = {

val valeuA = new Value("文字列")
val valeuB = new Value("文字列")

println(valeuA == valeuB) // true

}

}


同一比較

オブジェクトに"=="を使うとJavaと違い、同値比較をすることがわかりました。では同一比較はどうするのかというと"eqメソッド"と"neメソッド"を使います。eqはequalの略(たぶん)で同一判定、neはnot equalの略で非同一判定を行います。

object Main {

def main(args: Array[String]) = {

val stringA = new String("文字列")
val stringB = new String("文字列")

println(stringA eq stringB) // false
println(stringA ne stringB) // true

}

}

"eq"と"ne"もそれぞれ".eq()"、".ne()"と表現することができます。これらはAnyRefクラスのメソッドで、参照型系のクラスで使用可能です。独自で定義したクラスも暗黙的にAnyRefクラスを継承するので使用可能です。


おまけ

同値比較をする"=="が、演算子ではなくメソッドだということがわかりましたが、nullに使用するとどうなるのか。Javaでは演算子なのでnull比較をすることができましが、Scalaではメソッドなので、NullPointerExceptionになってしまうんじゃないかという懸念があります。

object Main {

def main(args: Array[String]) = {

val blank = null
val string = "文字列"

println(blank == string) // false

}

}

無事実行できました。こちらの記事によると、コンパイルするとnullチェックを差し込むようになるそうです。ますますScalaが好きになりますね。


最後に

Scalaでは同値比較に"=="や".==()"、".equals()"と様々な記法がありますが、"=="に統一して書くのが良さそうです。単純に文字数が二文字でタイピングが楽だし、可読性も高いかと。JavaのようにNullPointerExceptionを気にする必要もないので。

ScalaはJavaと似ていて、Javaエンジニアなら何となくでコードが書けてしまうと思っていましたが、それは間違いでした。Javaのような感覚でコーディングしていると痛い目を見そうなので、これからもScala学習に精進したいと思います。