129

Lets say I have a lombok annotated class like

@Builder
class Band {
   String name;
   String type;
}

I know I can do:

Band rollingStones = Band.builder().name("Rolling Stones").type("Rock Band").build();

Is there an easy way to create an object of Foo using the existing object as a template and changing one of it's properties?

Something like:

Band nirvana = Band.builder(rollingStones).name("Nirvana");

I can't find this in the lombok documentation.

Mustafa
  • 4,080
  • 3
  • 20
  • 36

3 Answers3

272

You can use the toBuilder parameter to give your instances a toBuilder() method.

@Builder(toBuilder=true)
class Foo {
   int x;
   ...
}

Foo f0 = Foo.builder().build();
Foo f1 = f0.toBuilder().x(42).build();

From the documentation:

If using @Builder to generate builders to produce instances of your own class (this is always the case unless adding @Builder to a method that doesn't return your own type), you can use @Builder(toBuilder = true) to also generate an instance method in your class called toBuilder(); it creates a new builder that starts out with all the values of this instance.

Disclaimer: I am a lombok developer.

Paul Grime
  • 14,517
  • 4
  • 31
  • 55
Roel Spilker
  • 25,620
  • 8
  • 59
  • 54
  • 15
    @Mustafa There's also `@Wither`, which is more efficient for single field changes: `Foo f1 = f0.withX(42)`. – maaartinus Nov 04 '17 at 13:09
  • @maaartinus I think `@Wither` generates with* methods that always returns a new object, instead of setting the field of the existing object. This is of low efficiency. – MGhostSoft Mar 20 '19 at 17:09
  • 3
    @MGhostSoft I'm obviously assuming that creating a new object is rhe goal. Thus us pretty common as immutable objects are used more and more. ++× For changing a single field, `@Wither`is best. Fot more than two, `toBuilder` wins. See my answer below. – maaartinus Mar 20 '19 at 19:50
  • 2
    And for zero fields (i.e. an object copy without any changes), `@Wither` won't work at all but `.toBuilder().build()` would. – M. Justin Aug 29 '19 at 21:57
  • flawless answer! – Gaurav Apr 21 '21 at 11:55
47

Is there an easy way to create an object of Foo using the existing object as a template and changing one of it's properties? (emphasis mine)

If you really want to change a single property, then there's a nicer and more efficient way:

@With
class Band {
   String name;
   String type;
}

Band nirvana = rollingStones.withName("Nirvana");

The wither creates no garbage, but it can change just a single field. For changing many fields, you could use

withA(a).withB(b).withC(c)....

and produce tons of garbage (all intermediate results) but than toBuilder is more efficient and more natural.

NOTE: Older versions of lombok have used @Wither annotation. See beginning of documentation.

Lubo
  • 987
  • 10
  • 21
maaartinus
  • 40,991
  • 25
  • 130
  • 292
  • 1
    would it really create that much garbage? I think it's all shallow copies except for the field you're replacing (relying on the notion that you will make the object immutable already if that's what is intended). The "garbage" will mostly be the discarded top level object's references (though I guess a lot of primitives could result in more garbage as well). – jm0 Apr 17 '19 at 18:07
  • 1
    @jm0 Sure, it's all shallow copies. By "tons of garbage" I meant `n-1` objects for a series of `n` calls to `withSomething`. An object costs something like a few bytes plus 4 or 8 bytes per reference plus 1 to 8 bytes per primitive. So we're talking about tens of bytes per call. No big deal, usually. – maaartinus Apr 20 '19 at 02:50
0

You might also want do a copy of the object using com.fasterxml.jackson.databind.ObjectMapper

@AllArgsConstructor
@Setter
class Band {
    String name;
    String type;
}

ObjectMapper objectMapper = new ObjectMapper(); //it's configurable
objectMapper.configure( DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false );
objectMapper.configure( SerializationFeature.FAIL_ON_EMPTY_BEANS, false );

Band rollingStones = new Band("Rolling Stones", "Rock Band");

Band nirvana = objectMapper.convertValue( rollingStones, Band.class);
nirvana.setName("Nirvana");

it can be easily wrapped in some utility method to be used all over the project like ConvertUtils.clone(rollingStones, Band.class)

ITisha
  • 525
  • 2
  • 8
  • 12