private int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
21 June 2015
private int x;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
We all have seen a lot of code like this in Java projects. So, what problems does it have?
Its a lot of boilerplate code.
Its akward to use: Calling many setters to initialize a data object costs many lines of code.
And what are its advantages?
It is adhereing to the Java beans standard.
All Java developers know it and use it.
Java beans can be used with many tools.
Interface (the getter and setter method) and implementation (the integer field itself) are decoupled.
The most simple way to fight all the boilerplate code is to simply use public fields:
public int x;
But now we loose all of the advantages mentioned above.
The other way to solve this is lombok. This is a really great solution for developers of services and apps. It doesn’t work well for developers of libraries because it would force lombok on all of the users of the library. In those cases it is better to generate the boilerplate code.
private int x;
public Data(int x) {
this.x = x;
}
The easiest path to a concise API are constructors. Unfortunately the you have to remember the exact order of the constructor arguments. And these can be very many for big data transfer objects. This simply doesn’t work if many arguments are of the same type (e.g. strings or integers).
private int x;
public Data withX(int x) {
this.x = x;
return this;
}
The modern way to solve this in the Java world is a fluent API (builder pattern). The data classes have a default constructor and the data is set with so called 'withers' (named this way because the methods start with 'with'). Their main disadvantage is that they doesn’t work nicely with inheritance. To get the correct return type in all cases very ugly generics have to be used.
Now lets see how the different possible solutions compare with each other.
Feature | void setX | Data setX | Data(int x) | Data withX |
---|---|---|---|---|
standard Java bean |
yes |
no |
if added |
if added |
concise |
no |
yes |
yes |
yes |
scales well |
yes |
yes |
no |
yes |
easy to read |
yes |
yes |
no |
yes |
no boilerplate code |
can be lomboked/generated |
can be lomboked/generated |
can be lomboked/generated |
can be lomboked/generated |
popular |
yes |
no |
some usages with mixed results |
some usages (e.g. lombok) |
In the end the withers are the clear winners if added to the setters. So this is what we do at flowdev even though it adds even more (generated) boilerplate code. The exception to this rule is that we don’t declare withers in interfaces since the correct return type would require really ugly generics. Additionally we don’t do fancy things in the withers of base classes. So these should be called after the withers of the derived classes.