如何在Scala中实现Groovy的with语法糖功能?
在Scala中实现类似Groovy
with 的语法糖 当然可以在Scala里实现和Groovy with 类似的便捷操作,而且能完美避开你提到的「作用域过宽」问题。先回顾下Groovy的with是怎么回事:它把目标对象作为闭包的上下文,让你不用重复写对象前缀就能直接操作属性,非常简洁。下面给你几种Scala里的实现方案:
方案1:使用Scala自带的tap方法(Scala 2.13+)
Scala 2.13及以上版本在Any类型上自带了tap方法,它会把对象传递给闭包执行操作,最后返回原对象,写法简洁且作用域封闭:
// 注意:要修改属性的话,case class的字段需要设为var case class Person(var name: String, var age: Int) val p = Person("", 0).tap { person => person.name = "Joe" person.age = 10 } println(p) // 输出:Person(Joe,10)
你也可以用下划线_简化闭包参数:
val p = Person("", 0).tap { _.name = "Joe" _.age = 10 }
方案2:自定义类似Groovy with 的扩展方法
如果你的Scala版本低于2.13,或者想要更贴近Groovy with 的命名,可以自己给所有类型扩展一个方法(注意Scala里with是关键字,这里用withBlock作为替代命名):
// 定义隐式扩展类,给任意类型添加withBlock方法 implicit class WithOps[A](val obj: A) extends AnyVal { def withBlock(f: A => Unit): A = { f(obj) obj } } // 使用示例 case class Person(var name: String, var age: Int) val p = Person("", 0).withBlock { _.name = "Joe" _.age = 10 } println(p) // 输出:Person(Joe,10)
这个方法的行为和Groovy的with几乎一致,而且闭包的作用域是局部的,完全不会污染外部命名空间,完美解决import p._的作用域问题。
额外说明:不可变对象的场景
如果你的场景更符合Scala的不可变编程风格(比如用默认的case class,字段是val),那可以用copy方法来创建新对象,这比修改原对象更符合Scala的设计理念:
case class Person(name: String, age: Int) // 字段默认是val,不可变 val p = Person("", 0).copy( name = "Joe", age = 10 ) println(p) // 输出:Person(Joe,10)
对比你之前用的import p._,上面这些方案既保留了Groovy with 的简洁性,又避免了作用域过宽导致的命名冲突问题,完全满足你的需求。
内容的提问来源于stack exchange,提问作者Andrii Abramov
相关产品推荐
相关产品推荐

