15

Let's say I have a class Foo in Java that has immutable data:

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...
}

Now I'd like to add a utility method that creates a "sibling" value with identical state but with a new value of x. I could call it setX():

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

but the semantics of setX() are different than the standard setter convention for mutable bean objects, so somehow this doesn't feel right.

What's the best name for this method?

Should I call it withX() or newX() or something else?


edit: additional priority in my case: I have scripting clients (through JSR-223 and an object model I export) that can easily obtain a Foo object. It's cumbersome, however, to call constructors or create builders or whatever. So it's desirable for me to provide this method as a convenience for scripting clients.

Jason S
  • 184,598
  • 164
  • 608
  • 970
  • A related question: http://stackoverflow.com/questions/521893/whats-the-best-name-for-a-non-mutating-add-method-on-an-immutable-collection. (That's the same as this, but for `add` instead of `setX`.) – ruakh Mar 05 '12 at 16:28

4 Answers4

15

Original article: Immutable Setters: Naming Conventions (from Programming.Guide)


withX(...)

This is the de facto standard naming convention for immutable setters. This is for example the default name for setters generated by the Immutables framework. Here's an example:

Foo newFoo = foo.withX(1047);

There is a @Value.Style option to change this pattern, but the option itself is called with="...", which emphasizes what the default convention is.

Being the most widespread convention, it's easy to find examples of this. Guava and the Java time package being two.

Just x(...)

Another approach is to not have a prefix at all. You see this in for example builders generated by the Immutables framework:

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

If you use this approach directly on the immutable class (that is, no builder involved) you'd typically have it as an overload to the getter:

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

This convention is used in for example the Java Spark framework.

setX(...)

Some APIs use the same naming convention as for setters in mutable classes. This has the obvious drawback that it can be surprising when you're new to a code base. Working with BigInteger and writing…

bigInt.setBit(2);

…would for example be a mistake, since the returned object is discarded. With this naming pattern you have to get used to writing

BigInteger newBigInt = bigInt.setBit(2);

deriveX(...)

To highlight the fact that the new value is derived from the existing object, you could use deriveX(...). The immutable Font class in the Java API follows this pattern. If you want to create a new font with, for example, a specific size you use

Font newFont = font.deriveFont(newSize);

The Font class has been around since the beginning of time. This convention is not very common as of today.

Immutable object being an operand

When the immutable object is itself an operand to the transformation it's not really a setter in the traditional sense, and there's no need to have a prefix for the method. For example…

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

…has the same signature as a setter, but multiply is clearly a better method name than any other alternative.

Same with String.substring, Path.resolve, etc.

aioobe
  • 413,195
  • 112
  • 811
  • 826
14

withX() sounds OK because it's a convention used for some Builder patterns.

This is more of a "partial clone" or "builder" than a "setter"...

If you look at java.lang.String (also immutable) there are all sorts of methods that return a new String based on the old one (substring, toLowerCase(), etc)...

Update: See also answer from aioobe [deriveFoo()] which I like - it's perhaps clearer, especially to anyone not familiar with Builder patterns.

Jeff Axelrod
  • 27,676
  • 31
  • 147
  • 246
DNA
  • 42,007
  • 12
  • 107
  • 146
3

I would call it withX(value). It says that it will be something with x = value.

If the class had a lot of fields, I would be afraid of:

obj.withX(1).withY(2).withZ(3).withU(1)...

So I would maybe use the builder pattern—introduce a mutable variant of the given class with only data and methods to create the original class with its current state. And there I would call these methods x(), y(), z(), and make them return this. So it would look like:

Immutable im2 = new Mutable(im1).x(1).y(2).z(3).build();
Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
Alpedar
  • 1,314
  • 1
  • 8
  • 12
  • 1
    An approach which can sometimes be useful to make the `with` style methods be efficient even when the underlying data items are large and expensive to copy is to add an extra level of indirection to the underlying abstract object type, and have that abstract type include a virtual "flatten" method. A `WithXX` method may then return a new wrapper which points to a `WrapWithXX` object that would generally hold a reference to the original object and the value of the new property, unless it detected that the "chain" of objects was getting excessively long in which case... – supercat Jul 02 '12 at 17:37
  • ...it would call `Flatten()`. The `Flatten` method would generate a new underlying object instance, taking into account any `WithXX` methods that it could parse. Calling `Flatten` on an object would not change its *observable* state, but would replace its realization with a less "complicated" form. Note that the extra level of indirection would not be without cost, but would also not be without advantages. For example, if two wrappers are found to have distinct objects which compare identical, one or both wrappers could be changed to point to a "canonical" instance, expediting comparisons. – supercat Jul 02 '12 at 17:40
1

It's definately not a setter, since it actually constructs and returns a new object. I think the factory semantics would be the more appropriate option in this case

public Foo newFooWith(int x) {
   return new Foo(x, other);
}

The alternative might be a variant of the copy constructor

class Foo {
    public Foo(Foo foo, int x) {
      return new Foo(x, foo.getOtherStuff());
    }
}
Johan Sjöberg
  • 47,929
  • 21
  • 130
  • 148