Scala:如何为继承抽象类的样例类实现通用copy方法?
问题分析
你的代码编译失败的核心原因有两个:
- 抽象类
Foo没有定义copy方法,而Bar和Baz的copy是编译器为样例类自动生成的专属方法,泛型参数T <: Foo无法直接感知到这些子类的copy方法。 - 原
Foo的构造参数没有加val,导致子类的a/b字段和父类的参数并非继承关系,无法通过Foo类型的实例统一访问这些字段。
解决方案
方案一:基于F-bounded泛型的抽象copy方法
这是最直接的方式,通过在父类定义抽象copy方法,让子类(样例类)自动实现的copy方法匹配签名,同时用F-bounded泛型保证返回类型是子类本身。
1. 修改父类和子类定义
sealed trait Alphabet case class ABC() extends Alphabet case class DEF() extends Alphabet abstract class Foo[T <: Foo[T]]( val a: Option[ABC], val b: Option[DEF] ) { // 定义抽象copy方法,返回子类类型T def copy(a: Option[ABC] = this.a, b: Option[DEF] = this.b): T } case class Bar( override val a: Option[ABC] = None, override val b: Option[DEF] = None, ) extends Foo[Bar](a, b) // 绑定泛型参数为Bar自身 case class Baz( override val a: Option[ABC] = None, override val b: Option[DEF] = None, ) extends Foo[Baz](a, b) // 绑定泛型参数为Baz自身
2. 实现通用copyBetter方法
def copyBetter[T <: Foo[T]](alpha: Alphabet, x: T): T = { alpha match { case cat: ABC => x.copy(a = Some(cat)) case cat: DEF => x.copy(b = Some(cat)) } }
这样调用时,编译器能正确推导子类的copy方法,返回类型也严格是输入的子类类型(Bar或Baz)。
方案二:使用类型类(Type Class)
如果不想修改原有的Foo/Bar/Baz定义(或者需要更灵活的扩展),可以用类型类来实现通用逻辑,不需要依赖父类的抽象方法。
1. 定义类型类和实例
// 先修正Foo的参数为val,保证字段可访问 abstract class Foo( val a: Option[ABC], val b: Option[DEF] ) case class Bar( override val a: Option[ABC] = None, override val b: Option[DEF] = None, ) extends Foo(a, b) case class Baz( override val a: Option[ABC] = None, override val b: Option[DEF] = None, ) extends Foo(a, b) // 定义类型类,描述"可以用Alphabet复制"的行为 trait AlphaCopyable[T <: Foo] { def copyWithAlpha(alpha: Alphabet, instance: T): T } // 为Bar和Baz实现类型类 object AlphaCopyable { implicit val barCopyable: AlphaCopyable[Bar] = (alpha, bar) => alpha match { case cat: ABC => bar.copy(a = Some(cat)) case cat: DEF => bar.copy(b = Some(cat)) } implicit val bazCopyable: AlphaCopyable[Baz] = (alpha, baz) => alpha match { case cat: ABC => baz.copy(a = Some(cat)) case cat: DEF => baz.copy(b = Some(cat)) } }
2. 实现通用方法
import AlphaCopyable._ def copyBetter[T <: Foo](alpha: Alphabet, x: T)(implicit ev: AlphaCopyable[T]): T = { ev.copyWithAlpha(alpha, x) }
调用时编译器会自动查找对应的类型类实例,实现类型安全的通用复制。
方案三:反射(不推荐)
如果以上两种方式都无法满足需求,还可以用Scala反射来调用样例类的copy方法,但反射会丢失编译时类型检查,且性能较差,仅作为兜底方案:
import scala.reflect.runtime.universe._ def copyBetter[T <: Foo](alpha: Alphabet, x: T): T = { val mirror = runtimeMirror(getClass.getClassLoader) val instanceMirror = mirror.reflect(x) val copyMethod = instanceMirror.symbol.typeSignature.decl(TermName("copy")).asMethod val params = alpha match { case cat: ABC => List(Some(cat), x.b) case cat: DEF => List(x.a, Some(cat)) } instanceMirror.reflectMethod(copyMethod)(params: _*).asInstanceOf[T] }
内容的提问来源于stack exchange,提问作者flame1481
相关产品推荐
相关产品推荐

