Reputation: 1299
I am new to groovy and i am just trying to learn here. I have a simple pojo as below, i am trying to have a builder pattern here. Now the class is annotated with @Builder annotaion.
@Builder
class Invert {
String color = 'Green'
String code
}
In my main class if create a object of Invert class, variable color is always null.
Invert in = new Invert.builder().code('0000').build()
Object is created with code as 0000 but color as null. Is it expected, if so is there any workaround for this or i am missing something here ?
Upvotes: 0
Views: 2483
Reputation: 7868
The Builder API expects you to initialize each property using the builder syntax. By not specifying the value for the 'color' property, you are in effect silently specifying that the value should be null, thus overriding your 'Green' default value.
You can adjust your code to something like the following, which is a simple groovy script I ran to test:
import groovy.transform.builder.*
import groovy.transform.*
@Canonical
@Builder
class Invert {
String color
String code
}
def invert = new Invert().builder().code('000').color('Green').build()
println invert
Not that I explicitly set the color attribute, using the Builder syntax, with results:
Invert(Green, 000)
Also notice the @Canonical annotation used. This is a nice annotation that combines several annotations:
@EqualsAndHashCode, @ToString and @TupleConstructor
Besides providing a nice toString() output, which you can see in the output of running the script, the TupleConstructor is (in my opinion) much more preferable to using the @Builder type pattern.
With the @TupleConstructor, you can simple construct your Invert object like this:
def invert = new Invert(code:'000', color:'Green')
In addition, the TupleConstructor allows you to keep your default value you had set for the color and if you don't set it in the Constructor, it will not be overridden:
import groovy.transform.*
@Canonical
class Invert {
String color = 'Green'
String code
}
def invert = new Invert(code:'000')
println invert
With results:
Invert(Green, 000)
So, if it were me, I would remove the @Builder and use @Canonical instead as a best practice. For more information on @Canonical, see:
http://docs.groovy-lang.org/next/html/gapi/groovy/transform/Canonical.html
Upvotes: 0
Reputation: 9895
First, let me show you what's going on. I'll start with the Invert
class:
@groovy.transform.builder.Builder
class Invert {
String color = 'Green'
String code
}
So that's the same class from your example, with the only difference being a fully-qualified name for @Builder
. But check out what happens when Groovy compiles the code (this is the relevant code from the AST viewer in the Groovy console).
@groovy.transform.builder.Builder
public class Invert implements groovy.lang.GroovyObject extends java.lang.Object {
private java.lang.String color
private java.lang.String code
...
public Invert() {
color = 'Green'
metaClass = /*BytecodeExpression*/
}
public static Invert$InvertBuilder builder() {
return new Invert$InvertBuilder()
}
...
}
Nothing surprising, although note that the color
is set to green
in the constructor.
public static class Invert$InvertBuilder implements groovy.lang.GroovyObject extends java.lang.Object {
private java.lang.String color
private java.lang.String code
...
public Invert$InvertBuilder color(java.lang.String color) {
this .color = color
return this
}
public Invert$InvertBuilder code(java.lang.String code) {
this .code = code
return this
}
public Invert build() {
Invert _theInvert = new Invert()
_theInvert .color = color
_theInvert .code = code
return _theInvert
}
...
}
The Invert$InvertBuilder
class is created by the @Builder
AST. It's what provides the fluent API.
Did you catch the source of the problem? The builder contains its own color
and code
fields. Then, when build()
is called:
Invert
is created. At this point, the instance's color
is green
.color
and code
fields to the Invert
instance. At this point the color
is changed to null because that's the default in the builder.To solve this issue with a builder, use the ExternalStrategy
to define your own builder class, in which you can set the color
as you'd like. Here's a working example:
class Invert {
String color
String code
static InvertBuilder builder() {
new InvertBuilder()
}
}
@groovy.transform.builder.Builder(builderStrategy=groovy.transform.builder.ExternalStrategy, forClass=Invert)
class InvertBuilder {
InvertBuilder() {
color = 'Green'
}
}
Invert invert = Invert.builder().code('0000').build()
assert invert.color == 'Green'
assert invert.code == '0000'
Groovy provides some alternative building methods you may be interested in.
Invert invert1 = new Invert(code: '0000', color: 'Blue') // Using the Map-based constructor
Invert invert2 = new Invert().with { // using Object.with(Closure)
code = '0000'
color = 'Blue'
return delegate
}
Upvotes: 2