有没有办法让 @Builder 注释适用于不可变类?

2024-01-02

我正在尝试在 Groovy 中开发一个项目,我一直在查看我的代码并尝试找到可以用更惯用的 Groovy 替换的区域,直到找到解决方案我遇到的另一个问题 https://stackoverflow.com/questions/28355773/in-groovy-why-does-the-behaviour-of-change-for-interfaces-extending-compar.

我开始更深入地研究 AST 转换注释的使用 - 它们帮助显着减少了我在某些地方必须编写的代码量。但是,我在使用时遇到问题groovy.transform.builder.Builder使用我的不可变值类之一进行注释。该注释的来源已托管here https://github.com/groovy/groovy-core/blob/master/src/main/groovy/transform/builder/Builder.java.

问题在于,注释似乎使构建器直接设置构建者的值,而不是存储值的副本并将它们传递给构建者的构造函数。这导致ReadOnlyPropertyException当您尝试将它与不可变类一起使用时。

您可以使用此注释选择四种可能的构建器策略,其中我已经尝试过DefaultStrategy, ExternalStrategy and InitializerStrategy。然而,所有这些都引发了问题。

ExternalStrategy看起来是四个中最有前途的一个,您可以找到一个基于它的 SSCCE,详细说明了该问题here https://groovyconsole.appspot.com/script/5641206004449280.

下面还包含示例的源代码:

import groovy.transform.Immutable
import groovy.transform.builder.Builder as GBuilder
import groovy.transform.builder.ExternalStrategy

/*
* Uncommenting the below causes a failure:
* 'groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: value for class: Value'
*/
//@Immutable
class Value {

    @GBuilder(forClass = Value, prefix = 'set', builderStrategy = ExternalStrategy)
    static class Builder { }

    int value
    String toString() { "Value($value)" }
}

def builder = new Value.Builder()
println builder.setValue(1).build()

似乎也有关于此事的相关 JIRA 讨论here http://jira.codehaus.org/browse/GROOVY-6484.

Edit
我尝试使用下面 CFrick 的答案,使用InitializerStrategy而不是ExternalStrategy.

现在一切都可以编译,但是当我尝试执行测试时,我在运行时收到以下错误:

java.lang.IllegalAccessError: tried to access class com.github.tagc.semver.version.BaseVersion from class com.github.tagc.semver.version.BaseVersion$com.github.tagc.semver.version.BaseVersionInitializer
    at java.lang.Class.getDeclaringClass(Class.java:1227)
    at java.beans.MethodRef.set(MethodRef.java:46)
    at java.beans.MethodDescriptor.setMethod(MethodDescriptor.java:117)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:72)
    at java.beans.MethodDescriptor.<init>(MethodDescriptor.java:56)
    at java.beans.Introspector.getTargetMethodInfo(Introspector.java:1163)
    at java.beans.Introspector.getBeanInfo(Introspector.java:426)
    at java.beans.Introspector.getBeanInfo(Introspector.java:173)
    at com.github.tagc.semver.version.VersionFactory.createBaseVersion(VersionFactory.groovy:34)
    at com.github.tagc.semver.test.util.TestSetup.<clinit>(TestSetup.groovy:77)
    at java.lang.Class.forName(Class.java:344)
    at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to patch-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:24)

随后出现一系列异常,如下所示:

java.lang.NoClassDefFoundError: Could not initialize class com.github.tagc.semver.test.util.TestSetup
    at java.lang.Class.forName(Class.java:344)
    at com.github.tagc.semver.version.SnapshotDecoratorSpec.#decoratedVersion should be considered equal to minor-bumped #releaseVersion snapshot(SnapshotDecoratorSpec.groovy:36)

我现在拥有的是BaseVersion类如下:

/**
 * A concrete, base implementation of {@link com.github.tagc.semver.version.Version Version}.
 *
 * @author davidfallah
 * @since v0.1.0
 */
@Immutable
@Builder(prefix = 'set', builderStrategy = InitializerStrategy)
@PackageScope
final class BaseVersion implements Version {
    // ...

    /**
     * The major category of this version.
     */
    int major = 0

    /**
     * The minor category of this version.
     */
    int minor = 0

    /**
     * The patch category of this version.
     */
    int patch = 0

    /**
     * Whether this version is a release or snapshot version.
     */
    boolean release = false

    // ...
}

生产这些实例的工厂:

/**
 * A factory for producing base and decorated {@code Version} objects.
 *
 * @author davidfallah
 * @since v0.5.0
 */
class VersionFactory {

    // ...

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param major the major category value of the version instance
     * @param minor the minor category value of the version instance
     * @param patch the patch category value of the version instance
     * @param release the release setting of the version instance
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(int major, int minor, int patch, boolean release) {
        return new BaseVersion(major, minor, patch, release)
    }

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param m a map of parameter names and their corresponding values corresponding to the
     *        construction parameters of {@code BaseVersion}.
     *
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(Map m) {
        return new BaseVersion(m)
    }

    /**
     * Returns an instance of {@link com.github.tagc.semver.version.BaseVersion BaseVersion} constructed
     * with the given parameters.
     *
     * @param l a list of parameter values corresponding to the construction parameters of {@code BaseVersion}.
     *
     * @return an instance of {@code BaseVersion}
     */
    static BaseVersion createBaseVersion(List l) {
        return new BaseVersion(l)
    }

    /**
     * Returns a builder for {@link com.github.tagc.semver.version.BaseVersion BaseVersion} to specify
     * the construction parameters for the {@code BaseVersion} incrementally.
     *
     * @return an instance of {@code BaseVersion.Builder}
     */
    static Object createBaseVersionBuilder() {
        return BaseVersion.builder()
    }

    // ...
}

测试规范类Version对象:

/**
 * Test specification for {@link com.github.tagc.semver.version.Version Version}.
 *
 * @author davidfallah
 * @since 0.1.0
 */
@Unroll
class VersionSpec extends Specification {

    static exampleVersions = [
        VersionFactory.createBaseVersion(major:1, minor:2, patch:3),
        VersionFactory.createBaseVersion(major:0, minor:0, patch:0),
        VersionFactory.createBaseVersion(major:5, minor:4, patch:3),
        VersionFactory.createBaseVersion(major:1, minor:16, patch:2),
        VersionFactory.createBaseVersion(major:4, minor:5, patch:8),
        ]

    // ...
}

以及其他尝试创建实例的类BaseVersion失败的,例如TestSetup.


你的代码失败了,因为选择的策略基本上是这样的:

 def v = new Value().with{ setValue(1); return it }

这不能在@Immutable对象。

根据docs http://docs.groovy-lang.org/docs/next/html/documentation/#xform-Builder, 只有InitializerStrategy,可以明确地应对@Immutable.

您可以将InitializerStrategy 与@Canonical 和@Immutable 结合使用。如果您的 @Builder 注释没有显式包含或排除注释属性,但您的 @Canonical 注释有,则 @Canonical 中的注释将被 @Builder 重新使用。

E.g.

import groovy.transform.*
import groovy.transform.builder.*

@Immutable
@ToString
@Builder(prefix='set', builderStrategy=InitializerStrategy)
class Value {
    int value
}

def builder = Value.createInitializer().setValue(1)
assert new Value(builder).toString()=='Value(1)'

根据您的目的,这是相当丑陋的语法,您最好使用基于映射的 c'tors。即使没有例如@TypeChecked a new Value(vlaue: 666)将生成错误并保留参数(对于具有多个属性的类)将保留它们null.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

有没有办法让 @Builder 注释适用于不可变类? 的相关文章

  • 使用 Jacoco 从 Sonar 的条件覆盖范围中排除 groovy slf4j 日志记录

    我们使用 SonarQube 5 1 和 Jacoco maven 插件 0 7 4 以及我们所有的 slf4j 日志记录语句 例如log debug Something happened 表明仅覆盖了 2 个分支中的 1 个 我明白这是因
  • 将结果行强制转换为对象

    有没有办法将调用存储过程获得的结果行强制转换为特定对象 以便我可以仅将该对象的列表传递到视图中 我知道我可以使用 Node list 之类的东西来做到这一点 但我最终将用一个相当复杂的存储过程替换 getnodes 该存储过程创建临时表并执
  • 如何在 Groovy 中执行引用等于?

    Groovy 将 映射到 eq uals 通常很方便 但是当我想按同一性进行比较时该怎么办 例如 GPathResult通过调用text 来实现equals 对于大多数内部节点来说它是空的 我正在尝试识别根节点 但通过该实现这是不可能的 如
  • 其中哪些在 Python 中是不可变的?

    我试图弄清楚以下内容在 Sage 中是否是不可变的 它是基于 Python 构建的 所以我相信如果它在 python 中是不可变的 我相信在大多数情况下它在 Sage 中也是不可变的 下面是对象 e f g i class e pass f
  • 重载“+”的不可变列表有意义吗?

    它当然没有脱离 NET框架的标准实践 当我看到一个a b我总是假设会有新的东西被创造出来 static void Main string args var list BuildList ImmutableList
  • Groovy 错误地使用了封闭类的构造函数?

    Given static class Question stuff List
  • 从属性文件加载属性并使其在整个作业/管道中可用 - Jenkins 声明性语法

    我的要求很简单 我只是想外部化一些 值 以使我的 Jenkinsfile 更可重用 为此我需要从 Jenkinsfile 旁边的文件加载属性 并确保这些属性是可以在管道中的任何地方使用 我对 Groovy 和 Jenkins 代码仍然很陌生
  • Python:我可以修改元组吗?

    我有一个 2 D 元组 实际上我以为 它是一个列表 但错误说它是一个元组 但无论如何 该元组的形式为 浮点数 val prod id 现在我有一个字典 其中包含 key gt prod id 和 value prod name 现在 我想将
  • 不可变子类

    我目前正在开发一个多线程框架 为了避免副作用 我想要求框架操作的所有数据都必须是不可变的 那么Java中是否存在一种方法来指定我希望给定类的所有子类或实现给定接口的所有类都是不可变的 我建议调查一下变异性检测器 http code goog
  • Groovy 中的嵌套“each”循环

    需要有关 groovy 中嵌套循环语法的指导 如何使用迭代器在这里打印 a name的值 b name的值 的值 List a a each print it name List b something b each print value
  • 如何在 Groovy 2.3 中使用 @SourceURI 注释检索脚本文件的完整路径?

    我需要在运行时检索 Groovy 2 3 中脚本文件的完整路径 实际上我遇到了与这里描述的问题相同的问题 在运行时获取 groovy 源文件的路径 https stackoverflow com questions 11958185 get
  • Groovy 安装 HTTPBuilder 现在 Codehaus 关闭了?

    我是一个 groovy n00b 并尝试使用 http builder 但网络上的所有示例都引用相同的内容 Grab声明不起作用 我认为这是因为 codehaus org 不再托管常规内容 我尝试过从以下位置下载源代码github http
  • groovy 无法解析 kotlin 类

    由于我无法解决我遇到的问题kotlin 我决定回到groovy为了实现该插件 但是我遇到了这个问题 我有这样的项目结构 为了不重写所有类 我决定重用中实现的类kotlin 然而我的课FileProcessingCoreGroovy Down
  • Jenkins 管道和 java.nio.file.* 方法的问题

    我正在尝试使用 java nio file 中的方法在 Jenkins 管道中执行一些基本文件操作 无论代码存在于哪个节点块中 代码都在主节点上执行 在管道中 我已经验证了各个节点块都是正确的 它们唯一地标识了特定的节点 但是 pathEx
  • 我可以在 Java 枚举上使用构建器模式吗

    我正在重写一些代码 并且我已经决定了重新创建类的方法 因为有固定数量的工作表 我将它们创建为枚举 这是基于构建器模式与伸缩构造器的可读性的决定 我的代码获取一些 xls 文件 添加标题 并从其他 xls 文件中读取一些 也许还有一些子表 然
  • Grails - 如何对 addTo* 进行单元测试

    是否可以在 Grails 中对 addTo 函数进行单元测试 感谢您的帮助 文档第 9 1 节中说 http grails org doc latest guide 9 20Testing html 9 1 20Unit 20Testing
  • 与赋值“=”和括号 { } 的使用混淆

    我是 gradle 的新手 对于 groovy 也是如此 我几乎不了解某些概念 并且真的不知道它是否与 groovy 或 gradle 相关 以及需要 google 哪些关键字来获取帮助 我经常被这样的事情绊倒 android some c
  • 抑制 Jenkins 文件中的管道输出

    当我在詹金斯上运行构建时 它总是在执行詹金斯步骤时打印管道 控制台当前输出 Build context CI clean app clean BUILD SUCCESSFUL in 22s 2 actionable tasks 2 exec
  • 反思 Groovy 脚本中声明的函数

    有没有一种方法可以获取 Groovy 脚本中声明的函数的反射数据 该脚本已通过GroovyShell目的 具体来说 我想枚举脚本中的函数并访问附加到它们的注释 Put this到 Groovy 脚本的最后一行 它将作为脚本的返回值 a la
  • Apache Spark 两个 RDD 之间的差异

    假设我有这个示例作业 在带有 Java API 的 Groovy 中 def set1 def set2 0 upto 10 set1 lt lt it 8 upto 20 set2 lt lt it def rdd1 context pa

随机推荐

  • 如何显示玻璃鱼文件夹中的图像?

    我正在使用 JSF 和 glassfish 4 0 我知道项目的resources文件夹下的images目录下的所有图片都可以直接访问 我想知道如何显示位于 glassfish 文件夹中的图像 我在 glassfish 域下创建了一个图像文
  • 如何从 PendingIntent 获取 Intent

    我可以得到Intent from a PendingIntent 这是场景 我创建一个Intent 我们称之为myIntent 我用字符串添加了一些额外信息 称之为myInfo 我创建一个PendingIntent myPendingInt
  • 具有唯一约束的 ebean 单向 @OneToOne 关系

    我有一个用户类 Entity public class User extends Model Id public Long id public String email public String name public String pa
  • missViewControllerAnimated() 不会关闭视图控制器

    我正在使用 xcode 6 我的故事板中有一个 取消 按钮 当我单击它时 我想关闭我的视图控制器并返回到我来自的地方 因此 在我的故事板中 我将取消按钮连接到我的视图控制器 并使用 Touch up 连接到我的 handleCancel 方
  • 如何在 C++ 中读取图像文件并将其存储在内存(std::string)中?

    我今天大部分时间都在网上进行了研究 但找不到答案 所以我转向 stackoverflow 寻求一些建议 基本上 我有一个 C 库 它使用curl 执行 PUT 方法来上传图像文件 现在这个库采用 std string 作为数据 我的本地磁盘
  • 是否可以定义一个 std::thread 并稍后初始化它?

    我的目标是保持std thread对象作为数据成员 并在需要时初始化它 我无法执行此操作 如下面的代码所示 因为std thread类被删除 还有其他方法吗 class MyClass public MyClass DiskJobThrea
  • AS3 中的隐式与显式 getter/setter,使用哪个以及为什么?

    自从 AS3 出现以来 我一直这样工作 private var loggy String public function getLoggy String return loggy public function setLoggy loggy
  • Netbeans 安装找不到 JDK [关闭]

    Closed 这个问题是无关 help closed questions 目前不接受答案 在我的 Windows 计算机上安装 Netbeans 6 0 1 时 我发现以下错误 Even if I my enviroment variabl
  • 如何堵住此类孔 2

    跟随从here https stackoverflow com questions 58032008 how to plug this type hole 58032339 58032339 我已将 main 中的代码重构为它自己的函数 我
  • 对文件发出 json/jsonp xhr 请求:协议

    我正在编写一个 javascript 应用程序 该应用程序将托管在file 协议 即 应用程序只是位于我硬盘上某个位置的 html css 和 javascript 的文件夹 当我尝试正常的 XHR 请求时 它们由于同源策略问题而失败 所以
  • DataGridView 以编程方式排序

    我创建了从 DataGridViewTextBoxColumn 类派生的自定义 DataGridViewNumericTextBoxColumn 当我打电话时 this HeaderDataGridView Sort Sort Progra
  • 写入套接字输出流而不关闭它

    我想向服务器写入一些消息 每次 仅对于传输 我都会关闭输出流 并在必须发送下一条消息时重新打开它 os write msgBytes os write r n getBytes os flush os close 如何保持该 Socket
  • 主机不更新报告中的数据

    我正在尝试在 Raspberry Pi Pico 上使用 TinyUSB 开发自定义 UPS 并让 UPower 在我的 Ubuntu 主机上发现它 我试图向主机发出充电状态信号 但主机将电池报告为空 UPower 而不是充电 等离子桌面电
  • 如何 JSON.stringify dom 元素?

    如 title 所示 如何对 dom 元素进行 JSON stringify 并将 json 改回 dom 元素 有哪位知道怎么办吗 谢谢 Here is the code var container document querySelec
  • 如何使用 Tkinter after() 方法?

    我在使用时遇到问题after中的方法Tkinter 计划是打印i间隔一秒 我检查了是否after方法合适 但具体不知道 这是代码 coding utf 8 from Tkinter import import time root Tk ro
  • 卡尔曼滤波器:如何在没有“状态转换模型”的情况下使用它?

    我正在使用 Android 手机开发加速度计 我希望过滤掉加速度计返回记录手机移动的可怕噪音 我正在阅读卡尔曼滤波器 因为低通滤波器还不够 但我没有从ACCELERATION k 1 to ACCELERATION k 因为它是用户的动作
  • android 从url下载文件并保存到内存盘

    我尝试从 url 下载文件并将其保存到内存购物车 但我无法理解我的错误是什么 我的代码是 URL url new URL imageURL File file new File fileName long startTime System
  • 即使位于堆栈顶部,有时也会创建 singleTop Activity

    我有一个 Activity 其 launchMode 为 singleTop 在清单中 我的理解是 如果一个 Activity 是 singleTop 并且它位于 Activity 堆栈的顶部 那么使用新 Intent 启动 Activit
  • Python Windows 路径斜杠[重复]

    这个问题在这里已经有答案了 我在 python 脚本中使用目录路径面临一个非常基本的问题 当我从 Windows 资源管理器复制路径时 它使用反斜杠作为路径分隔符 这导致了问题 gt gt gt x D testfolder gt gt g
  • 有没有办法让 @Builder 注释适用于不可变类?

    我正在尝试在 Groovy 中开发一个项目 我一直在查看我的代码并尝试找到可以用更惯用的 Groovy 替换的区域 直到找到解决方案我遇到的另一个问题 https stackoverflow com questions 28355773 i