首页 > 解决方案 > 如何有效/干净地覆盖复制方法

问题描述

我有一个超类和一个子类,如下所示:

class Animal(var x: Int) {
   def greeting: String = "hi im an animal"
   def copy: Animal = new Animal(x)
}


class Lion(override var x: Int) extends Animal(x){
  override def greeting: String = "hi im a lion"
  override def copy: Lion = new Lion(x)
}

我希望它们都具有完全相同的复制功能(想象它比我给出的更大),除了返回类型,我希望 Lion 类在调用复制时返回一个 Lion。

如何在不重复代码的情况下干净地覆盖 Animal 复制方法?

标签: scalaobjectoverriding

解决方案


原则上,方法apply/ unapply, canEqual/ equals/ hashCode, toString, copy, productArity/ productElement/ productIterator/productPrefix可以通过点菜的无形案例类生成,尽管我不确定这是否适用于类层次结构。

无论如何,您可以apply使用宏注释生成

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class copy extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro CopyMacro.impl
}

object CopyMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    annottees match {
      case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
        val paramNamess = paramss.map(_.map {
          case q"$_ val $tname: $_ = $_" => tname
          case q"$_ var $tname: $_ = $_" => tname
        })

        val tparamNames = tparams.map {
          case q"$_ type $tpname[..$_] = $_" => tpname
        }

        val doesOverrideCopy = parents.map {
          case q"${parent@tq"$_[..$_]"}(...$_)" => parent
          case     parent@tq"$_[..$_]"          => parent
        }.exists(tree => 
          c.typecheck(tree.duplicate, mode = c.TYPEmode)
            .tpe
            .member(TermName("copy")) != NoSymbol
        )

        val copyMod = if (doesOverrideCopy) Modifiers(Flag.OVERRIDE) else NoMods

        q"""
           $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self =>
             ..$stats
             $copyMod def copy: $tpname[..$tparamNames] = new $tpname[..$tparamNames](...$paramNamess)
           }
           ..$tail
        """
    }
  }
}

用法:

@copy
class Animal(val x: Int) {
  def greeting: String = "hi im an animal"
}

@copy
class Lion(override val x: Int) extends Animal(x) {
  override def greeting: String = "hi im a lion"
}

//scalac: {
//  class Animal extends scala.AnyRef {
//    <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    def greeting: String = "hi im an animal";
//    def copy: Animal = new Animal(x)
//  };
//  ()
//}
//scalac: {
//  class Lion extends Animal(x) {
//    override <paramaccessor> val x: Int = _;
//    def <init>(x: Int) = {
//      super.<init>();
//      ()
//    };
//    override def greeting: String = "hi im a lion";
//    override def copy: Lion = new Lion(x)
//  };
//  ()
//}

或者,因为class Animal(val x: Int)case-class-like你可以尝试使用shapeless.Generic

implicit class CopyOps[A](a: A)(implicit generic: Generic[A]) {
  def copy: A = generic.from(generic.to(a))
}

推荐阅读