从另一个案例类扩展案例类

时间:2018-12-31 11:29:37

标签: scala

我有两个案例类 Person Employee

case class Person(identifier: String) {}

case class Employee (salary: Long) extends Person {}

我遇到以下错误:

Unspecified value parameters: identifier: String
Error: case class Employee has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes

我是Scala的新手,无法理解我该怎么做。

版本: 斯卡拉:2.11

3 个答案:

答案 0 :(得分:3)

不幸的是,恐怕案例类无法扩展另一个案例类。

“普通”类中的继承如下所示:

class Person(val identifier: String) {}

class Employee(override val identifier: String, salary: Long)
  extends Person(identifier) {}

val el = new Employee("abc-test", 999)
println(el.identifier) // => "abc-test"

如果您想通过case类来达到类似的效果,则需要联系trait s:

trait Identifiable {
  def identifier: String
}

case class Person(identifier: String) extends Identifiable {}

case class Employee(identifier: String, salary: Long)
  extends Identifiable {}

val el = Employee("abc-test", 999)
println(el.identifier) // => "abc-test"

定义提取器

Extractor提供了一种定义模式匹配中使用的匹配语句的方法。在object方法中的unaply中定义。

让我们再次考虑第一个示例,它增加了对提取器的支持:

class Person(val identifier: String)

class Employee(override val identifier: String, val salary: Long)
  extends Person(identifier)

object Person {
  def unapply(identifier: String): Option[Person] = {
    if (identifier.startsWith("PER-")) {
      Some(new Person(identifier))
    }
    else {
      None
    }
  }
}

object Employee {
  def unapply(identifier: String): Option[Employee] = {
    if (identifier.startsWith("EMP-")) {
      Some(new Employee(identifier, 999))
    }
    else {
      None
    }
  }
}

现在,让我们定义一个使用这些提取器定义模式匹配的方法:

def process(anInput: String): Unit = {
  anInput match {
    case Employee(anEmployee) => println(s"Employee identified ${anEmployee.identifier}, $$${anEmployee.salary}")
    case Person(aPerson) => println(s"Person identified ${aPerson.identifier}")
    case _ => println("Was unable to identify anyone...")
  }
}

process("PER-123-test") // => Person identified PER-123-test
process("EMP-321-test") // => Employee identified EMP-321-test, $999
process("Foo-Bar-Test") // => Was unable to identify anyone...

答案 1 :(得分:2)

从案例类继承(即使使用常规的非案例类,这也是不被禁止的)是一个坏主意。查看this answer,了解原因。

Person不必是案例类。实际上,它根本不需要是一个类:

trait Person {
   def identifier: String
}
case class Employee(identifier: String, salary: Long) extends Person

答案 2 :(得分:2)

Scala中的案例类添加了几个不同的功能,但是通常您实际上只使用其中的一些功能。因此,您需要回答的主要问题是您真正需要哪些功能。以下是基于the spec的列表:

  • 无需在字段名称/构造函数参数之前输入val
  • 通过向伴侣对象添加new方法来消除对apply的需求
  • 通过将unapply方法添加到伴随对象来支持模式匹配。 (Scala的优点之一是模式匹配是通过非魔术方式完成的,您可以将其实现为任何数据类型,而无需将其设为case class
  • 根据所有字段添加equalshashCode实现
  • 添加toString个实现
  • 添加copy方法(之所以有用,是因为case class在默认情况下是不可变的)
  • 实现Product特性

case class Person(identifier: String)的等效值的合理猜测是

class Person(val identifier: String) extends Product  {

  def canEqual(other: Any): Boolean = other.isInstanceOf[Person]

  override def equals(other: Any): Boolean = other match {
    case that: Person => (that canEqual this) && identifier == that.identifier
    case _ => false
  }

  override def hashCode(): Int = identifier.hashCode

  override def toString = s"Person($identifier)"

  def copy(newIdentifier: String): Person = new Person(newIdentifier)

  override def productElement(n: Int): Any = n match {
    case 0 => identifier
    case _ => throw new IndexOutOfBoundsException(s"Index $n is out of range")
  }

  override def productArity: Int = 1
}

object Person {
  def apply(identifier: String): Person = new Person(identifier)

  def unapply(person: Person): Option[String] = if (person eq null) None else Some(person.identifier)
}

case class Employee(override val identifier: String, salary: Long) extends Person(identifier) {}

实际上,反对从case class继承(尤其是使case class继承另一个)的主要反对意见是Product特性copyequals / { {1}},因为它们会引起歧义。添加hashCode可以部分缓解最后一个问题,但不能缓解第一个问题。另一方面,在像您这样的层次结构中,您可能根本不需要canEqual方法或copy实现。如果在模式匹配中不使用Product,则也不需要Person。您可能unapply真正需要的全部是caseapplytoString / hashCode / equals