Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Too much boilerplate, how can I reduce my POJO builders?

I have several different POJOs that use a builder pattern, but after adding a builder for each one and generating Object.toString, Object.hashCode and Object.equals, my classes end up being around 100 lines of code. There has to be a better way to handle this. I think having some sort of a reflective builder would help out a lot, but I'm not sure this would be good practice and I'm also not sure how I'd exactly make it happen. In other words, is there a way to implement a builder like this?

A simple POJO:

public class Foo {

    public int id;
    public String title;
    public boolean change;
    ...

}

Then some sort of reflective builder:

Foo = ReflectiveBuilder.from(Foo.class).id(1).title("title").change(false).build();
like image 491
buildpattern Avatar asked Sep 23 '14 07:09

buildpattern


2 Answers

Short answer no. What you ask for is not possible. Reflection looks at the code at runtime and invokes methods dynamically, it cannot generate actual methods.

What you could do would be:

Foo foo = ReflectiveBuilder.from(Foo.class).
              set("id", 1).
              set("title", "title").
              build();

This has three massive problems:

  1. the fields are Strings - a typo causes a runtime error rather than a compile time one,
  2. the values are Objects - the wrong type causes a runtime error rather than a compile time one, and
  3. it would be much slower than the alternative as Reflection is very slow.

So a reflection based solution, whilst possible (see Apache Commons BeanUtils BeanMap) is not at all practical.

Long answer, if you're willing to allow some compile time magic, you can use Project Lombok. The idea behind Lombok is to generate boilerplate code from annotations using the Java annotation preprocessor system.

The really magical thing is that all IDEs, well the big 3 at least, understand annotation preprocessing and code completion will still function correctly even though the code doesn't really exist.

In the case of a POJO with a Builder you can use @Data and @Builder

@Data
@Builder
public class Foo {

    public int id;
    public String title;
    public boolean change;
    ...

}

The @Data annotation will generate:

  • a required arguments constructor (that takes all final fields),
  • equals and hashCode methods that use all fields (can be configured with the @EqualsAndHashCode annotation)
  • a toString method on all fields (can be configured with the @ToString annotation and
  • public getters and setters for all fields (can be configured using the @Getter / @Setter annotations on fields).

The @Builder annotation will generate an inner class called Builder that can be instantiated using Foo.builder().

Do make sure you configure the equals, hashCode and toString methods as if you have two classes with Lombok that have references to each other then you will end up with an infinite loop in the default case as both classes include the other in these methods.

There is also a new configuration system that allows you to use, for example, fluent setters so you can more of less do away with the builder if your POJO is mutable:

new Foo().setId(3).setTitle("title)...

For another approach you can look at Aspect-oriented programming (AOP) and AspectJ. AOP allows you do chop your classes up into "aspects" and then stick them together using certain rules using a pre-compiler. For example you could implement exactly what Lombok does, using custom annotations and an aspect. This is a fairly advanced topic however, and might well be overkill.

like image 53
Boris the Spider Avatar answered Sep 22 '22 10:09

Boris the Spider


Maybe Project Lombok (yes the website is ugly) is an option for you. Lombok injects code into your classes based on annotations.

With Lombok you use the @Data annotations to generated getters, setters, toString(), hashCode() and equals():

@Data
public class Foo {
    public int id;
    public String title;
    public boolean change;
}

Have a look at the example on the @Data documentation section to see the generated code.

Lombok also provides a @Builder that generates a builder for your class. But be aware that this is an experimental feature:

@Builder
public class Foo {
    public int id;
    public String title;
    public boolean change;
}

Now you can do:

Foo foo = Foo.builder()
  .id(123)
  .title("some title")
  .change(true)
  .build();
like image 30
micha Avatar answered Sep 21 '22 10:09

micha