A core part of coding in an object-oriented language is, well, creating objects. Plain Old Java Objects (POJOs) are the standard Java object, and they usually look something like this:
public class Color {
private String rgb;
public Color(String rgb) {
this.rgb = rgb;
}
public String getRgb() {
return rgb;
}
}
Nice, simple, straightforward. POJOs are fine for modelling things, but they have a problem: they’re mutable.
The burden of a mutable object is that you have to keep track of its state across all code that touches the object. This isn’t a big deal in small projects, but it becomes hard to manage and reason about in large, multi-team code bases.
The way around this is to take an idea from functional programming—use immutable objects. In this series, we’ll look at how we can use the Immutables library to effectively use immutables when modelling java objects. This first part introduces the Immutables library and shows some basic use cases.
An Example
Let’s first take a look at how mutability can be troublesome. Say we want to create an image editing app. At some point we’ll need to build a color picker, and that color picker will need to offer some base colors for the user to choose from.
public enum BaseColors {
BLACK("000000"),
RED("FF0000"),
GREEN("00FF00"),
BLUE("0000FF");
private final Color color;
private BaseColors(String rgb) {
this.color = new Color(rgb);
}
public Color getColor() {
return color;
}
}
Now, we’ll add a tool to help with color selection. We’ll let our users lighten their selected color. To add this, we’ll need a lighten method.
public class Color {
// ...
public void lighten() {
this.rgb = // math to lighten this color
}
}
Finally, our color picker:
public class ColorPicker {
private Color currentColor = BaseColors.BLACK;
public void selectColor(BaseColors baseColor) {
this.currentColor = baseColor.getColor();
}
public void lighten() {
currentColor.lighten();
}
}
It’s not much, but our user can now select one of our base colors and lighten their current color. But there’s a problem—when our user lightens a base color, they are actually mutating the backing color in the BaseColors
enum! Ultimately, this happens because Color#lighten
modifies the color’s rgb value.
In this case, it’s not too hard to fix this bug. We just need to make the Color
class immutable. We do so by making changing rgb
to be final and by returning a new Color
instance when we want to lighten.
public class Color {
private final String rgb;
// ...
public Color lighten() {
return new Color(/* math to lighten this color */);
}
}
Our color picker changes slightly as well:
public class ColorPicker {
// ...
public void lighten() {
currentColor = currentColor.lighten();
}
}
Now our users can’t accidentally modify our base colors and we’re happy.
The Immutables Library
We custom-made an immutable object in our example above. But what happens when we have something more complicated than our simple Color
class? It would be nice if we could easily enforce immutability, especially when other developers might touch our objects in the future.
That’s where the Immutables library comes in—it provides an annotation processor that generates immutable objects for you.
Basic Usage
To use it, we define our object as an abstract class or interface. We tag it with a @Value.Immutable
annotation, which tells our annotation processor that it should generate an immutable implementation of our abstract class.
Our original Color
class would now look like this:
@Value.Immutable
public abstract class Color {
public abstract String rgb();
}
When our annotation processor runs it generates an ImmutableColor
class. You can then use that class to create a Color
instance:
Color black =
ImmutableColor.builder()
.rgb("000000")
.build();
Let’s unpack this a bit.
We changed our Color
class to be abstract and added the @Value.Immutable
annotation. This tells the Immutables annotation processor that we want to generate an immutable implementation of Color
. This generated implementation is the ImmutableColor
class.
Our Color
class has a single attribute—rgb
. Any abstract, zero argument method with a non-void return type is considered to be an attribute. The ImmutableColor
class has a backing field for rgb
. It also ensures that this field can’t be changed.
Non-abstract methods can also be attributes, which we’ll cover in a bit. Common collection types, such as lists and sets, can also be attribute types. They get copied to and wrapped in an unmodifiable collection class. Although the collection itself cannot be modified, you still need to take care that the objects in the collection are also immutable.
We construct instances of our immutable class with a builder pattern, instead of a constructor. As we’ll see in part 2, we can also use static factory methods to create our immutables.
Now, we add our lighten
method back in:
@Value.Immutable
public abstract class Color {
public abstract String rgb();
public Color lighten() {
return ImmutableColor.builder()
.rgb(/* math to lighten this color */)
.build();
}
}
Our Color
class is now effectively the same as our hand-made immutable Color
. The only remaining thing to do is to update our other code that creates Color
instances.
public enum BaseColors {
BLACK("000000"),
RED("FF0000"),
GREEN("00FF00"),
BLUE("0000FF");
private final Color color;
private BaseColors(String rgb) {
this.color =
ImmutableColor.builder()
.rgb(rgb)
.build();
}
public Color getColor() {
return color;
}
}
We’ve now completely replaced our hand-made immutable Color
with our generated version! In fact, the generated version is a bit nicer—the Immutables annotation processor has also generated hashCode
, equals
, and toString
methods on our ImmutableColor
class. We get object equality for free!
Stay tuned for part 2, where we’ll take a look at how we can improve our Immutables usage with some useful features!