case class inheritance

devos發表於2015-04-23

Scala 禁止case class inheritance

case class Person(name: String, age: Int)
case class FootballPlayer(name: String, age: Int, number: Int) extends Person(name, age)

在編譯時會報出以下錯誤:

Error:(5, 12) case class FootballPlayer has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes.
case class FootballPlayer(name: String, age: Int, number: Int) extends Person(name, age)
^

原因有挺多,下邊的兩篇文章講了下理由,但我還是沒明白其中的必要性。

http://tech.schmitztech.com/scala/caseclassinheritence.html

http://stackoverflow.com/questions/11158929/what-is-so-wrong-with-case-class-inheritance


 

我們可以使得兩個case class繼承同一個trait來實際於case class繼承的行為,但是這樣就得自己寫extractor了。比如

  sealed trait Person{
    def age: Int
    def name: String
  }
  case class FootballPlayer(name: String, age: Int, number: Int) extends Person
  val torres = FootballPlayer("Fernando Torres", 31, 19)
  val Person(name, age) = torres // can't resolve symbal Person

會編譯出錯, 因為Person是個trait,它並不支援extractor的語法。

需要給Person加個Companion object,像這樣

  object Person{
    def unapply(p: Person) = Some((p.name, p.age))
  }

這時就能用extractor了

  val torres = FootballPlayer("Fernando Torres", 31, 19)
  val Person(name, age) = torres

 

編譯器會為case class生成equals方法,但普通類就不會了。

  sealed trait Person{
    def age: Int
    def name: String
  }

  case class FootballPlayer(name: String, age: Int, number: Int) extends Person
  class Doctor(val name: String, val age: Int) extends Person
  val torresA = FootballPlayer("Fernando Torres", 31, 19)
  val torresB = FootballPlayer("Fernando Torres", 31, 19)
  println(torresA == torresB)//true

  val docA = new Doctor("C", 30)
  val docB = new Doctor("C", 30)
  println(docA == docB)//false

這時,當兩個FootballPlayer的構造引數相同,它們就相等。但是對於Doctor類來說不是這樣了。

當給FootballPlayer這個case class的父類Person定義了equals方法之後,就不是這樣了。

 sealed trait Person{self =>
    def age: Int
    def name: String
    override def equals(that: Any):Boolean = {
      that match{
        case p: Person => p.age == self.age && p.name == self.name
        case _ => false
      }
    }
    override def hashCode: Int = {
      var hash = 1
      hash = hash * 31 + age
      hash = hash * 31 + {if(name !=null) name.hashCode else 0}
      hash
    }
  }
  case class FootballPlayer(name: String, age: Int, number: Int) extends Person
  class Doctor(val name: String, val age: Int) extends Person
  val torresA = FootballPlayer("Fernando Torres", 31, 19)
  val torresB = FootballPlayer("Fernando Torres", 31, 19)
  torresA.equals(torresB)
  println(torresA == torresB)//true

  val docA = new Doctor("C", 30)
  val docB = new Doctor("C", 30)
  println(docA == docB) //true

  val footballPlayerC = FootballPlayer("C", 30, 30)
  println(footballPlayerC == docA) //true
  println(footballPlayerC.hashCode())//1958
  println(docA.hashCode())//1958

貌似這時候case class就不會生成equals方法了, 轉而使用父類Person的equals方法。並且要記得同時在Person中覆蓋hashCode方法,不然就破壞了equals對hashCode的要求。

相關文章