Java:不可变类的伪 setter 方法

2022-09-03 10:02:16

假设我在Java中有一个类Foo,它具有不可变的数据:

class Foo {
    final private int x;
    public int getX() { return this.x; }
    final private OtherStuff otherstuff;
    public Foo(int x, OtherStuff otherstuff) { 
       this.x = x;
       this.otherstuff = otherstuff;
    }   
    // lots of other stuff...
}

现在,我想添加一个实用程序方法,该方法创建一个具有相同状态但具有新值 x 的“同级”值。我可以称之为:setX()

class Foo
{
    ...
    Foo setX(int newX) { return new Foo(newX, this.otherstuff); }
    ...
}

但是 的语义与可变 Bean 对象的标准 setter 约定不同,所以不知何故,这感觉不对。setX()

此方法的最佳名称是什么?

我应该叫它还是别的什么?withX()newX()


编辑:在我的情况下,额外的优先级:我有脚本客户端(通过JSR-223和我导出的对象模型),可以轻松获取对象。但是,调用构造函数或创建生成器或其他任何东西都很麻烦。因此,我希望提供此方法以方便编写客户端脚本。Foo


答案 1

原始文章:不可变 Setters:命名约定(来自 Programming.Guide)


withX(...)

这是不可变 setter 事实上的标准命名约定。例如,这是由不可变框架生成的 setter 的默认名称。下面是一个示例:

Foo newFoo = foo.withX(1047);

有一个选项可以更改此模式,但该选项本身称为 ,它强调了默认约定是什么。@Value.Stylewith="..."

作为最广泛的惯例,很容易找到这方面的例子。番石榴和Java时间包是两个。

x(...)

另一种方法是完全没有前缀。例如,您可以在由 Immutables 框架生成的构建器中看到这一点:

Foo foo = ImmutableFoo.builder()
                      .x(1047)
                      .y("Hello World")
                      .build();

如果直接在不可变类(即不涉及生成器)上使用此方法,则通常会将其作为 getter 的重载:

Foo newFoo = foo.x(5);  // setter - one argument
int x = newFoo.x();     // getter - no arguments

例如,此约定用于 Java Spark 框架。

setX(...)

某些 API 使用与可变类中的 setter 相同的命名约定。这有一个明显的缺点,当你刚接触代码库时,它可能会令人惊讶。与BigInteger合作并编写...

bigInt.setBit(2);

...例如,这将是一个错误,因为返回的对象将被丢弃。有了这个命名模式,你必须习惯于写作

BigInteger newBigInt = bigInt.setBit(2);

deriveX(...)

要突出显示新值派生自现有对象的事实,可以使用 。Java API 中的不可变 Font 类遵循此模式。如果要创建新字体,例如,使用您使用的特定大小deriveX(...)

Font newFont = font.deriveFont(newSize);

这门课从一开始就存在了。截至今天,该公约并不常见。Font

不可变对象作为操作数

当不可变对象本身是转换的操作数时,它实际上不是传统意义上的 setter,并且不需要为该方法设置前缀。例如。。。

BigDecimal newBigDec = bigDec.multiply(BigDecimal.TEN);

...具有与 setter 相同的签名,但显然是比任何其他替代方法更好的方法名称。multiply

与 、 等相同。String.substringPath.resolve


答案 2

withX()听起来不错,因为它是用于某些生成器模式的约定。

这更像是“部分克隆”或“建造者”,而不是“设置者”......

如果你看一下(也是不可变的),有各种各样的方法可以基于旧字符串(子字符串,toLowerCase()等)返回新字符串...java.lang.String

更新:另请参阅我喜欢的aiiobe []的答案 - 它可能更清晰,特别是对于不熟悉Builder模式的人来说。deriveFoo()