【问题标题】:In Scala, Refer to Subclass in Abstract Super Class在 Scala 中,参考抽象超类中的子类
【发布时间】:2016-02-26 20:08:16
【问题描述】:

有没有办法让超类中的this 关键字引用该类的子类?具体来说,我正在尝试执行以下操作(Json 指的是 Play 的 Json 库):

abstract class A() {
  def toJson[T](implicit writes: Writes[T]): JsValue = Json.toJson(this)
}

case class B(myProperty: String) extends A
object B { implicit val bFormat = Json.format[B] }

这给出了错误No Json serializer found for type A. Try to implement an implicit Writes or Format for this type.。所以说它不能序列化A 类型的对象,这是有道理的。然而,目标是让Json.toJson(this) 中的this 引用子类(在本例中为B)。

有没有办法做到这一点?如果没有,有没有其他方法可以在超类中实现Json.toJson(...) 方法而不必在子类(以及A 的所有其他子类)中实现?

【问题讨论】:

标签: scala inheritance implicit play-json


【解决方案1】:

从父类引用当前子类的常用技巧是使用F-bounded polymorphism

// Here `T` refers to the type of the current subclass
abstract class A[T <: A[T]]() { 
  this: T =>
  def toJson(implicit writes: Writes[T]): JsValue = Json.toJson(this)
}

// We have to specify the current subclass in `extends A[B]`
case class B(myProperty: String) extends A[B]
object B { implicit val bFormat = Json.format[B] }

println(B("foo").toJson)

这将不允许您为任何通用 A 调用 toJson

val a: A[_] = B("foo")
println(a.toJson)      // Doesn't compile with: 
                       //   No Json serializer found for type _$1. 
                       //   Try to implement an implicit Writes or Format for this type.

要解决此问题,您必须在创建对象时为子类型保存 Writes

abstract class A[T <: A[T]](implicit writes: Writes[T]) { 
  this: T =>
  def toJson: JsValue = Json.toJson(this)
}

或者使用context bound 表示法:

abstract class A[T <: A[T] : Writes] { 
  this: T =>
  def toJson: JsValue = Json.toJson(this)
}

而且由于这个 F 有界多态性只是一个实现细节,并且始终将通用 A 引用为 A[_] 是相当样板的,因此您可以将此代码移至中间 abstract class

所以一个完整的例子看起来像这样:

abstract class A() {
  def toJson: JsValue
}

abstract class AImpl[T <: AImpl[T] : Writes] extends A { 
  this: T =>
  def toJson: JsValue = Json.toJson(this)
}

case class B(myProperty: String) extends AImpl[B]
object B { implicit val bFormat: Format[B] = Json.format[B] }

val a: A = B("foo")
println(a.toJson)

【讨论】:

  • 为什么AAImpl 是抽象类而不是特征?
  • @PimVerkerk OP 有一个抽象类。此外,虽然A 可以很容易地成为一个特征,但将AImpl 实现为一个抽象类来让它接受一个隐式参数会更方便。使AImpl 成为特征需要在每个子类中手动创建此隐式Writes 参数。
  • 谢谢。有趣的是,如果不明确设置 bFormat 属性的类型,这将无法编译。如,implicit val bFormat = Json.format[B] 不会编译。你知道为什么会这样吗?为什么不能从Json.format[B]表达式中推断出它的类型?
  • @socom1880 我现在找不到链接,但基本上这是一个旧的错误/功能(它不会被修复),在某些情况下,您必须在类本身(即把伴生对象放在它的类之前),或者在我的示例中明确指定隐式的类型(或同时指定两者)。
  • @socom1880 我不知道有什么简单的方法可以避免它。 Play Json 使用宏生成Format/Writes 实现,这些宏在伴随对象中查找applyunapply 函数,您不能真正以一般方式提供它们。只是一个猜测,但也许来自 shapeless 的东西会起作用,比如github.com/milessabin/shapeless/blob/master/examples/src/main/… 但我通常只是认为这些东西是不可避免的样板,然后把它们写出来。您可以尝试就此提出一个单独的问题。
猜你喜欢
  • 1970-01-01
  • 2018-08-01
  • 1970-01-01
  • 2017-06-22
  • 1970-01-01
  • 2013-12-03
  • 2021-07-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多