LoginSignup
6
2

More than 5 years have passed since last update.

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

Last updated at Posted at 2017-04-26

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

6
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2