Using @Builder with generics

12,807 views
Skip to first unread message

dnno

unread,
Jul 2, 2015, 6:16:30 AM7/2/15
to project...@googlegroups.com
I really like the @Builder annotation provided by lombok. However the created builder classes don't respect generics. Let me illustrate.

This is what the annotation creates:

@Builder
public class Entity<T> {

T body;

public static void test() {
Entity entity = Entity.builder().body("body").build();
}

}

This is what it should look like:

@Builder
public class Entity<T> {

T body;

public static void test() {
Entity<String> entity = Entity.builder().body("body").build();
}

}

Both ways you get warnings, complaining about "unchecked calls to body()" and in the second case "unchecked assignment to entity".

I've de-lomboked the generated code, this is what works:

public class Entity<T> {

T body;

Entity(T body) {
this.body = body;
}

public static void test() {
Entity<String> entity = Entity.<String>builder().body("body").build();
}

public static <K> EntityBuilder<K> builder() {
return new EntityBuilder();
}

public static class EntityBuilder<L> {
private L body;

EntityBuilder() {
}

public Entity.EntityBuilder<L> body(L body) {
this.body = body;
return this;
}

public Entity<L> build() {
return new Entity<>(body);
}

public String toString() {
return "com.audi.mobility.vehicleaccessservice.baimos.model.Entity.EntityBuilder(body=" + this.body + ")";
}
}
}

Is there any way to dynamically generate that code? Or is there anything that I am missing which makes it possible to use generics with @Builder?

Thanks!

Ryan Schmitt

unread,
Jul 2, 2015, 12:09:13 PM7/2/15
to project...@googlegroups.com
I can't follow your code samples. Is the third example the de-lomboked code, or a modified version that fixes the problem you're seeing? Are you on JDK8?

dnno

unread,
Jul 3, 2015, 12:30:37 PM7/3/15
to project...@googlegroups.com
Sorry, my third example is a modified version of the de-lomboked code, that fixes the problem. This is JDK8, yes.

Ryan Schmitt

unread,
Jul 4, 2015, 2:54:52 PM7/4/15
to project...@googlegroups.com
Just for convenience, can you also post the actual de-lomboked code? And maybe some sort of formatted diff somewhere showing exactly what the changes are?

dnno

unread,
Jul 6, 2015, 11:26:55 AM7/6/15
to project...@googlegroups.com
Surly I can:

public class Entity<T> {

T body;

    @java.beans.ConstructorProperties({"body"})

Entity(T body) {
this.body = body;
}

    public static EntityBuilder builder() {
return new EntityBuilder();
}

public static class EntityBuilder {
private T body;

EntityBuilder() {
}

public Entity.EntityBuilder<T> body(T body) {

this.body = body;
return this;
}

        public <T> Entity<T> build() {
return new Entity<T>(body);
}

public String toString() {
return "com.audi.mobility.booking.model.Entity.EntityBuilder(body=" + this.body + ")";
}
}
}

As you can see, it does use a type definition in the de-lomboked builder, but it uses the same type parameter name <T>, which the compiler then complains about since that one is defined in the context of the Entity class and access from the context of the static builder class.

Two changes are necessary:

1. The created builder class needs to define its own type parameter and use it in the generated fluid builder methods:

public static class EntityBuilder<K> {

    private K body;

EntityBuilder<K>() {
}

public Entity.EntityBuilder<
K> body(K body) {
this.body = body;
return this;
}

public Entity<K> build() {
return new Entity<>(body);
}

public String toString() {
return "com.audi.mobility.vehicleaccessservice.baimos.model.Entity.EntityBuilder(body=" + this.body + ")";
}
}

2. The created builder() method needs to define its own type parameter:

public static <K> EntityBuilder<K> builder() {
return new EntityBuilder<>();
}


My example deals with a single type parameter. You could imagine that someone uses more than one parameter, though. I'm not sure how the code generation works with lombok, but a solution to this problem will probably have to keep track of every type parameter individually. In order to avoid naming conflicts I would suggest a naming scheme. For example if the entity has a type parameter of name T, the builder could use a type parameter of name TT.

Here's an additional example covering two parameters:

public class Entity<K, V> {

K key;
V value;

@java.beans.ConstructorProperties({"key", "value"})
Entity(K key, V value) {
this.key = key;
this.value = value;
}

public static <KK, VV> EntityBuilder<KK, VV> builder() {
return new EntityBuilder<>();
}

public static class EntityBuilder<KK, VV> {
private KK key;
private VV value;

EntityBuilder() {
}

public Entity.EntityBuilder<KK, VV> key(KK key) {
this.key = key;
return this;
}

public Entity.EntityBuilder<KK, VV> value(VV value) {
this.value = value;
return this;
}

public Entity<KK, VV> build() {
            return new Entity<>(key, value);
}

public String toString() {
return "com.audi.mobility.booking.model.Entity.EntityBuilder(key=" + this.key + ", value=" + this.value + ")";
}
}
}

Let me know if I can provide additional information, I'm happy to help!

Cheers,
Reinhard

Martin Grajcar

unread,
Jul 7, 2015, 11:29:09 AM7/7/15
to project...@googlegroups.com
In order to avoid naming conflicts I would suggest a naming scheme. For example if the entity has a type parameter of name T, the builder could use a type parameter of name TT.

There are no naming conflicts as the builder is static and can't access the enclosing T.


When this feature gets implemented, you'll still unable to use

Entity<String> entity =
    Entity.builder().body("body").build();

as is, since Java can't infer the types. But simply

Entity<String> entity =
    Entity.<String>builder().body("body").build();

would help.

Reinier Zwitserloot

unread,
Jul 13, 2015, 4:30:27 PM7/13/15
to project-lombok
@Builder is supposed to work that way. If it doesn't, that's a bug. I'm investigating it now.


 --Reinier Zwitserloot

--
You received this message because you are subscribed to the Google Groups "Project Lombok" group.
To unsubscribe from this group and stop receiving emails from it, send an email to project-lombo...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reinier Zwitserloot

unread,
Jul 13, 2015, 4:35:00 PM7/13/15
to project-lombok
I have checked this and I don't understand OP at all. Lombok ALREADY works this way and as far as I know has since the inception of Builder. The delombok output you say you get is not something I can reproduce. What version of lombok do you have? Maybe we've fixed this already and I simply can't remember.

Going to consider this pilot error until more feedback shows up.


 --Reinier Zwitserloot

dnno

unread,
Jul 13, 2015, 5:03:06 PM7/13/15
to project...@googlegroups.com
Thanks for your answer, I really appreciate you taking the time for it.

I have looked at it again and I think the problem I faced is actually two-fold and not a Lombok problem:

1. I have used the builder incorrecty. When using EntityBuilder.<String>builder().build() I can assign without trouble to Entity<String> entity. This should have been obvious to me.
2. The delomboked code I have been referring to is created by the Lombok Intellij plugin. This code doesn't correctly use type variable names. Which is what confused me.

I'm sorry I only found out after you all put time into this. Thanks a lot for your help!

dnno

unread,
Jul 14, 2015, 4:44:07 AM7/14/15
to project...@googlegroups.com
Maybe the @Builder documentation could use a specific example for that to avoid such confusion in the future. Is there a way I could contribute such a thing?
Reply all
Reply to author
Forward
0 new messages