博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Scala元编程:实现lombok.Data
阅读量:6871 次
发布时间:2019-06-26

本文共 3992 字,大约阅读时间需要 13 分钟。

如果你读完了,理论上你已经具备实现lombok.Data的能力了。

所以,我建议你不要阅读本文,直接自己尝试。

定义 lombok.Data 的 Scala 版

@dataclass A {  var x: Int = _  var y: String = _}复制代码

我们希望通过@data这个注释,自动生成如下代码:

class A {  var x: Int = _  var y: String = _  def getX(): Int = x  def setX(paramX: Int): Unit = { x = paramX }  def getY(): Int = x  def setY(paramY: String): Unit = { y = paramY }}复制代码

为什么要生成这样的代码呢?就个人而言,我是为了在Spring Boot和Scala混合编写的项目中无缝地使用MyBatis。在使用Java时,我们可以很方便地使用lombok.Data生成我们所需的Getter和Setter。而在Scala生态中,已经有了case class,这种写法其实对于Pure Scala的程序员来说,是相当离经叛道的。

在用Scala和Java混合编程的时候,我觉得其实最重要的一点是选择。实现一个功能的方式可能有100(二进制哦)种,但是最适合的方式,永远只有一种。

我选择了MyBatis,不采用元编程的手段(其实这段时间我刚刚学会),我是这样做的:

import scala.beans.BeanPropertyclass A {  @BeanProperty var x: Int = _  @BeanProperty var y: String = _}复制代码

用Vim列编辑,其实也还好。但是我内心其实一直在对自己说:DO NOT REPEAT YOURSELF

参考实现

// ...      annottees.map(_.tree).toList match {        case q"""              class $name {                ..$vars              }              """ :: Nil =>          // Generate the Getter and Setter from VarDefs          val beanMethods = vars.collect {            case q"$mods var $name: $tpt = $expr" =>              val getName = TermName("get" + name.encodedName.toString.capitalize)              val setName = TermName("set" + name.encodedName.toString.capitalize)              println(getName)              val ident = Ident(name)              List (                q"def $getName: $tpt = $ident",                q"def $setName(paramX: $tpt): Unit = { $ident = paramX }"              )          }.flatten          // Insert the generated Getter and Setter          q"""             class $name {               ..$vars               ..$beanMethods             }           """        case _ =>          throw new Exception("Macro Error")      }    }    // ...复制代码

单元测试

上一篇元编程相关的文章实际上主要是为了强调构建,所以我贴了两次构建定义的代码。

test("generate setter and getter") {    @data    class A {      var x: Int = _      var y: String = _    }    val a = new A    a.setX(12)    assert(a.getX === 12)    a.setY("Hello")    assert(a.getY === "Hello")}复制代码

lombok在IntelliJ Idea中有专门的插件,去处理Idea无法定位到的程序自动生成的Getter和Setter。如果我们只是为了让MyBatis能够识别和使用,我们就没有必要再去为我们的Scala版lombok.Data专门定制一个插件。在我们自己的代码中,没有必要使用Getter和Setter,因为Scala在语言级别已经支持了(如果你一脸懵逼,我建议你先阅读一下《快学Scala》和《Scala实用指南》的样章)。

test("handle operator in the name") {    @data    class B {      var op_+ : Int = _    }    val b = new B    b.setOp_+(42)    assert(b.getOp_+ === 42)  }复制代码

这个地方也涉及到了一个Scala相关的知识点,我记得在《快学Scala》中看到过。在参考实现中,与这个单测相关的代码是这两行:

val getName = TermName("get" + name.encodedName.toString.capitalize)val setName = TermName("set" + name.encodedName.toString.capitalize)复制代码

这里就不展开了。

单元测试的风格

Scala项目的单测,我一直用ScalaTest,但是ScalaTest官网的例子给的是FlatSpec:

"A Stack" should "pop values in last-in-first-out order" in {    val stack = new Stack[Int]    stack.push(1)    stack.push(2)    stack.pop() should be (2)    stack.pop() should be (1)  }复制代码

大概是这种代码风格。我们需要在两个地方填入一些信息,有点烦人。所以,我推荐FunSuite这种风格:

test("A Stack pop values in last-in-first-out order"){    val stack = new Stack[Int]    stack.push(1)    stack.push(2)    stack.pop() should be (2)    stack.pop() should be (1)  }复制代码

我只需要填一句话,不需要考虑主语,对IDE也更加友好。

编译期与运行时

这是元编程里面两个特别重要的概念。广义上讲,实际上,这些概念都在试图提醒我们,注意一下是谁(那台机器上的那个进程)在运行我们的代码。

提交代码的时候,不小心忘记把调试用的println(getName)清理掉,索性就不去清理了。

使用sbt去运行我们的单元测试:

$ sbtsbt:paradise-study> test// 编译期开始[info] Compiling 1 Scala source to $HOME/github/paradise-study/lombok/target/scala-2.12/test-classes ...getXgetYgetOp_$plus[info] Done compiling.// 编译期结束// 运行[info] DataSuite:[info] - generate setter and getter[info] - handle operator in the name[info] Run completed in 437 milliseconds.[info] Total number of tests run: 2[info] Suites: completed 1, aborted 0[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0[info] All tests passed.[success] Total time: 4 s, completed 2019-1-1 16:15:43复制代码

小结

本文是Scala元编程的一个Case Study,完整的工程见:。

最近几天刚刚学习Scala元编程,直觉告诉我,Scala元编程并不难,当然,这取决于相关的Domain Knowledge有没有提前储备好。

转载于:https://juejin.im/post/5c2b24a15188252dcb31306e

你可能感兴趣的文章
我的友情链接
查看>>
Unity手动调用物理引擎Update
查看>>
linux 命令
查看>>
JAVA8新特性之:Stream 详解
查看>>
RHEL vsftpd多个虚拟用户访问不同目录问题
查看>>
CENTOS7 Python3.7 为jupyter notebook 安装python2.7内核
查看>>
control userpasswords2实现xp的自动登陆
查看>>
CKEDITOR使用与配置
查看>>
Linux课程第十六天学习笔记
查看>>
Redis作者谈Redis应用场景
查看>>
数据库外键的使用以及优缺点
查看>>
解决oracle set auto trace on 错误
查看>>
Step2:Apply NLS patch
查看>>
jsp---语句对象Statement
查看>>
java进阶之路
查看>>
优化Android Studio
查看>>
zabbix二次开发-flask-获取告警
查看>>
我的友情链接
查看>>
java实现MD5加密处理
查看>>
实用JVM参数总结
查看>>