You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Scala 2.12.17中如何编译期自动生成Case Class的Data参数Apply方法?

可以做到,推荐用Scala Macro Paradise实现自定义注解来批量生成所需的apply方法,以下是具体步骤和代码示例:

方案一:自定义注解批量生成Apply方法

这个方案适合你有多个case class的场景,一次注解就能自动生成支持Data参数的apply方法。

1. 配置SBT依赖

Scala 2.12需要引入Macro Paradise编译器插件,在build.sbt中添加:

addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full)
libraryDependencies += "org.scalamacros" %% "resetallattrs" % "1.0.0"

2. 实现注解与宏逻辑

创建一个单独的文件(比如DataAwareMacro.scala),定义注解和编译期宏:

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

// 自定义注解,标注需要生成Data参数apply方法的case class
class DataAwareCaseClass extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro DataAwareCaseClassMacro.impl
}

// 宏实现:编译期解析case class并生成额外的apply方法
object DataAwareCaseClassMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    annottees.head match {
      // 匹配仅含单个String参数的case class
      case q"case class $className( $paramName: String )" =>
        // 生成接收Data参数的apply方法,内部调用loadData转换
        val dataApplyMethod = q"""
          object ${className.toTermName} {
            def apply(data: Data): $className = $className(loadData(data))
          }
        """
        // 合并原case class定义和新的伴生对象方法
        q"""
          $className
          $dataApplyMethod
        """
      // 不匹配时抛出编译错误
      case _ =>
        c.abort(c.enclosingPosition, "@DataAwareCaseClass仅适用于单个String参数的case class")
    }
  }
}

3. 使用注解生成方法

在你的业务代码中,给目标case class加上注解即可:

// 你的原有Data定义和loadData函数
trait Data
case class DoubleInt(a: Int, b: Int) extends Data
case class SingleInt(n: Int) extends Data

def loadData(data: Data): String = data match {
  case DoubleInt(a, b) => s"$a,$b"
  case SingleInt(n) => n.toString
}

// 给case class添加注解,自动生成Data参数的apply方法
@DataAwareCaseClass
case class TestOne(s: String)

@DataAwareCaseClass
case class TestTwo(s: String)

// 测试使用
val data1: Data = DoubleInt(10, 20)
val test1 = TestOne(data1) // 自动执行loadData(data1),生成TestOne("10,20")
println(test1) // 输出:TestOne(10,20)
方案二:手动宏扩展(单类处理)

如果只有少数几个case class,也可以手动为每个类生成apply方法,不需要注解:

import scala.language.experimental.macros
import scala.reflect.macros.blackbox

object DataApplyGenerator {
  def createApply[T]: Unit = macro createApplyImpl[T]

  def createApplyImpl[T: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
    import c.universe._
    val tpe = weakTypeTag[T].tpe
    val className = tpe.typeSymbol.name.toTermName

    // 校验case class是否符合要求
    val constructor = tpe.decl(termNames.CONSTRUCTOR).asMethod
    if (constructor.paramLists.flatten.size != 1 || constructor.paramLists.flatten.head.typeSignature != typeOf[String]) {
      c.abort(c.enclosingPosition, s"${tpe.typeSymbol.name}必须是仅含单个String参数的case class")
    }

    // 生成apply方法
    q"""
      object $className {
        def apply(data: Data): $tpe = $className(loadData(data))
      }
    """
  }
}

// 使用示例:在case class的伴生对象中调用宏
case class TestThree(s: String)
object TestThree extends DataApplyGenerator.createApply[TestThree]

// 测试
val data2: Data = SingleInt(5)
val test3 = TestThree(data2) // 生成TestThree("5")
注意事项
  • 宏代码需要在编译期执行,如果是多模块项目,建议把宏代码放在单独的模块,主模块依赖该模块。
  • 确保loadData函数在宏代码的可见作用域内(比如放在公共包下),否则宏无法引用到它。
  • 宏会在编译期做校验,如果case class不符合“单个String参数”的要求,会直接抛出编译错误,提前发现问题。

内容的提问来源于stack exchange,提问作者Ares

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 09:43:36