先日の投稿で、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学習に精進したいと思います。