Many Java projects have settled on mutable JavaBeans or POJO (plain old Java object) conventions for representing data. Mutability brings complications though - why are immutable values a better choice, and how can we reduce the cost of mutability in a codebase?
As we discuss in “Bean Style”, JavaBeans were introduced to allow the development of drag-and-drop GUI builders in the Visual Basic style. A developer could drop a button onto a form, change its title and icon and then wire in an on-click handler. Behind the scenes the GUI builder would write code to instantiate a button object and then call setters for the properties that the developer had changed.
To define a JavaBean, a class needs a default (no-argument) constructor, getters for its properties and setters for its mutable properties.1 This makes a lot of sense for objects that have a lot of properties. GUI components typically have foreground and background colors, font, label, borders, size, alignments, paddings etc. Mostly the defaults for these properties are fine, so calling setters for just the special values minimizes the amount of code to be generated. In fact, if we were to write a GUI toolkit today, I think we’d probably still opt for a similar mutable component model.
When JavaBeans were introduced though, we thought of most objects as mutable, not just UI components.
I mean, why not? - the point of objects was to encapsulate properties and manage the relationships between them.
They were designed to solve problems like updating the width of a component when its bounds are changed, or the total of a shopping cart as items are added.
Objects were the solution to the problem of managing mutable state.
Java was quite radical at the time in having an immutable String
class (although it couldn’t help itself and still plumped for a mutable Date
).
We have a more sophisticated understanding these days, speaking as both your authors and the industry. We appreciate that we can use objects to represent different types of things - values, entities, services, actions, transactions etc. And yet the default pattern for a Java object is still the JavaBean - a mutable object with getters and setters for its properties. Although it may be appropriate for a UI toolkit, this is not a good default pattern. For most things that we want to represent with objects, a value would be better.
Value is a much overloaded term in English.
In computing we say that variables, parameters and fields have values - the primitive or reference that they are bound to.
When we refer to a value in this book we are referring to a specific type of primitive or reference - those with value semantics.
An object has value semantics if only its value is significant in its interactions, not its identity.
Java primitives all have value semantics: every 7
is equal to every other 7
.
The objects that are the targets of references may or may not have value semantics though; in particular, mutable objects do not.
In later chapters we’ll look at finer distinctions, but for now, let’s just define a value to be an immutable piece of data, and a value type to be a type that defines the behaviour of an immutable piece of data.
So 7
is a value, and the boxed Integer
is a value type (because boxed types are immutable). banana
is a value (because String
s are immutable), a URI
is a value (because URI
s are immutable), but java.util.Date
is not a value type (because we can call setYear
etc on the date).
An instance of an immutable DBConnectionInfo
is a value, but an instance of Database
is not a value, even if all its properties are immutable. This is because it is not a piece of data, it is a means of accessing pieces of data. Code to Data discusses the difference between data and other immutable objects.
Are JavaBeans values? In the case of UI components JavaBeans are not values. UI components aren’t just data - two otherwise identical buttons have different identities. In the case of beans used to represent plain data, it will depend on whether they are immutable. It is possible to create immutable beans - just don’t define any setter methods and add the ability to initialise the properties in at least one non-default constructor. This is (serialization aside) technically a bean, but most developers would think of it more as a POJO.
Are POJOs values? The term was coined to refer to classes that don’t have to extend from framework types in order to be useful. They usually represent data and conform to the JavaBeans conventions for accessor methods. Many POJOs will not have a default constructor, but instead define constructors to initialize properties that don’t have sensible defaults. Because of this, immutable POJOs are common, and may have value semantics. Mutable POJOs still seem to be the default though, so much so that many people consider that object-oriented programming in Java is synonymous with mutable objects. Mutable POJOs are not values.
In summary, a bean could technically be a value, but rarely is. POJOs more often have value semantics, especially in the modern Java age. So whilst Beans to Values is snappy, in this chapter we’re really looking at migrating from mutable objects to immutable data, so maybe we should have called it Mutable POJOs to Values. We hope you’ll forgive the clickbait chapter title.
A value is immutable data. Why we should prefer immutable objects to mutable objects, and objects that represent data to other types of object. This is a theme that we will visit time and again in this book. But for now, we prefer immutable objects because they are much easier to reason about:
We can put them into sets or use them as map keys.
We never have to worry about an immutable collection changing as we iterate over its contents.
We can explore different scenarios without having to deep-copy initial states, and simply implement undo and redo.
We can safely share immutable objects between different threads or different processes.
Let’s look at migrating a use of a mutable bean or POJO to a value.
We represent preferences in the Travelator mobile app with a UserPreferences
JavaBean.
public
class
UserPreferences
{
private
String
greeting
;
private
Locale
locale
;
private
Currency
currency
;
public
UserPreferences
()
{
this
(
"Hello"
,
Locale
.
UK
,
Currency
.
getInstance
(
Locale
.
UK
));
}
public
UserPreferences
(
String
greeting
,
Locale
locale
,
Currency
currency
)
{
this
.
greeting
=
greeting
;
this
.
locale
=
locale
;
this
.
currency
=
currency
;
}
public
String
getGreeting
()
{
return
greeting
;
}
public
void
setGreeting
(
String
greeting
)
{
this
.
greeting
=
greeting
;
}
public
Locale
getLocale
()
{
return
locale
;
}
public
void
setLocale
(
Locale
locale
)
{
this
.
locale
=
locale
;
}
public
Currency
getCurrency
()
{
return
currency
;
}
public
void
setCurrency
(
Currency
currency
)
{
this
.
currency
=
currency
;
}
}
The Application
has a preferences
property, which it passes to views that need it.
public
class
Application
{
private
final
UserPreferences
preferences
;
public
Application
(
UserPreferences
preferences
)
{
this
.
preferences
=
preferences
;
}
public
void
showWelcome
()
{
new
WelcomeView
(
preferences
).
show
();
}
public
void
editPreferences
()
{
new
PreferencesView
(
preferences
).
show
();
}
...
}
Finally PreferencesView
updates its preferences
when the user makes changes.
public
class
PreferencesView
extends
View
{
private
final
UserPreferences
preferences
;
private
final
GreetingPicker
greetingPicker
=
new
GreetingPicker
();
private
final
LocalePicker
localePicker
=
new
LocalePicker
();
private
final
CurrencyPicker
currencyPicker
=
new
CurrencyPicker
();
public
PreferencesView
(
UserPreferences
preferences
)
{
this
.
preferences
=
preferences
;
}
public
void
show
()
{
greetingPicker
.
setGreeting
(
preferences
.
getGreeting
());
localePicker
.
setLocale
(
preferences
.
getLocale
());
currencyPicker
.
setCurrency
(
preferences
.
getCurrency
());
super
.
show
();
}
protected
void
onGreetingChange
()
{
preferences
.
setGreeting
(
greetingPicker
.
getGreeting
());
}
protected
void
onLocaleChange
()
{
preferences
.
setLocale
(
localePicker
.
getLocale
());
}
protected
void
onCurrencyChange
()
{
preferences
.
setCurrency
(
currencyPicker
.
getCurrency
());
}
...
}
This design, whilst simple, is fraught with complications typical of mutable data, such as:
If the PreferencesView
and WelcomeView
are both active, the WelcomeView
can get out of sync with the current values.
UserPreferences
equality and hashCode depend on the values of its properties, which may be changed.
So we can’t reliably use it in sets or as keys in maps.
There is nothing to indicate that the WelcomeView
only reads from the preferences.
If it accidentally changed a value that would probably be a defect.
Errors in the PreferencesView
could lead to data corruption, especially if there is coupling between preference properties, or between preferences and other application data.
If reading and writing occur on different threads we have to manage synchronisation at the preference property level.
Before we refactor to using an immutable value, let’s convert Application
and UserPreferences
to Kotlin, which will help us see the nature of our model. Application
is simple:
class
Application
(
private
val
preferences
:
UserPreferences
)
{
fun
showWelcome
()
{
WelcomeView
(
preferences
).
show
()
}
fun
editPreferences
()
{
PreferencesView
(
preferences
).
show
()
}
...
}
UserPreferences
is more complicated.
At the time of writing, IntelliJ yields this:
class
UserPreferences
@JvmOverloads
constructor
(
var
greeting
:
String
=
"Hello"
,
var
locale
:
Locale
=
Locale
.
UK
,
var
currency
:
Currency
=
Currency
.
getInstance
(
Locale
.
UK
)
)
That @JVMOverloads
annotation tells the compiler to generate constructors allowing combinations of greeting
, locale
or currency
to be defaulted. This wasn’t what our original Java did - it had just two constructors.
Arguably the conversion is more flexible, but it is unnecessary complication, so let’s simplify.
class
UserPreferences
(
var
greeting
:
String
,
var
locale
:
Locale
,
var
currency
:
Currency
)
{
constructor
()
:
this
(
greeting
=
"Hello"
,
locale
=
Locale
.
UK
,
currency
=
Currency
.
getInstance
(
Locale
.
UK
)
)
}
At this stage we haven’t changed the functioning of our application, just simplified its expression.
Those var
(as opposed to val
) properties are the sign that we have mutable data.
It’s worth reminding ourselves at this point, that the Kotlin compiler is going to generate a private field, a getter method, and a setter method for each property, so that our Java continues to see the data class as a bean.
Kotlin embraces the beans naming convention.
How now do we make UserPreferences
immutable?
After all, we do want the preferences seen in the app to reflect any changes the user makes.
The answer is to move the mutation.
In common with many of the refactorings in this book, we’re going to move the problematic thing (in this case mutation) up.
Which is to say, towards the entry point, or into the higher-level, more application-specific code (see “Directions in Code”).
Instead of mutating the preferences, we are going to update the reference in the Application
.
The reference we’re going to use will be an updated copy returned by PreferencesView
.
Instead of making all the changes at once, let’s start by converting PreferencesView
to Kotlin and return the existing mutable preferences from a showModal
method:
class
PreferencesView
(
private
val
preferences
:
UserPreferences
)
:
View
()
{
private
val
greetingPicker
=
GreetingPicker
()
private
val
localePicker
=
LocalePicker
()
private
val
currencyPicker
=
CurrencyPicker
()
fun
showModal
():
UserPreferences
{
greetingPicker
.
greeting
=
preferences
.
greeting
localePicker
.
locale
=
preferences
.
locale
currencyPicker
.
currency
=
preferences
.
currency
show
()
return
preferences
}
protected
fun
onGreetingChange
()
{
preferences
.
greeting
=
greetingPicker
.
greeting
}
protected
fun
onLocaleChange
()
{
preferences
.
locale
=
localePicker
.
locale
}
protected
fun
onCurrencyChange
()
{
preferences
.
currency
=
currencyPicker
.
currency
}
...
}
Now make Application.preferences
a mutable property, set from the result of showModal
:
class
Application
(
private
var
preferences
:
UserPreferences
)
{
...
fun
editPreferences
()
{
preferences
=
PreferencesView
(
preferences
).
showModal
()
}
...
}
This gives us the worst of both worlds, a mutable reference to mutable data, but we haven’t finished.
PreferencesView
can now copy rather than mutate its preferences.
class
PreferencesView
(
private
var
preferences
:
UserPreferences
)
:
View
()
{
private
val
greetingPicker
=
GreetingPicker
()
private
val
localePicker
=
LocalePicker
()
private
val
currencyPicker
=
CurrencyPicker
()
fun
showModal
():
UserPreferences
{
greetingPicker
.
greeting
=
preferences
.
greeting
localePicker
.
locale
=
preferences
.
locale
currencyPicker
.
currency
=
preferences
.
currency
show
()
return
preferences
}
protected
fun
onGreetingChange
()
{
preferences
=
UserPreferences
(
greetingPicker
.
greeting
,
preferences
.
locale
,
preferences
.
currency
)
}
protected
fun
onLocaleChange
()
{
preferences
=
UserPreferences
(
preferences
.
greeting
,
localePicker
.
locale
,
preferences
.
currency
)
}
protected
fun
onCurrencyChange
()
{
preferences
=
UserPreferences
(
preferences
.
greeting
,
preferences
.
locale
,
currencyPicker
.
currency
)
}
...
}
Now there are no setters called on preferences - we can make it a proper value. While we’re there we’ll get rid of that irritating default constructor by inlining it into the one place in the code that it was used.
data
class
UserPreferences
(
val
greeting
:
String
,
val
locale
:
Locale
,
val
currency
:
Currency
)
The eagle-eyed reader will notice that we sneakily made UserPreferences
a data class.
We didn’t do that before now, because it was mutable, and while Kotlin allows mutable data classes they are suspect, because the meaning of equality is then much more complicated.
What have we achieved so far?
We’ve replaced two immutable references to a mutable value, with two mutable references to immutable values.
Now we can see at a glance which views can update the preferences, and if we had to manage updates across threads we could do that at the application level.
Having a mutable reference in PreferencesView
is a bit irritating though. We can fix that by not holding a reference at all, but instead passing the preferences into showModal
.
In this scheme there is no point in updating the individual preference properties when their respective pickers update.
class
PreferencesView
:
View
()
{
private
val
greetingPicker
=
GreetingPicker
()
private
val
localePicker
=
LocalePicker
()
private
val
currencyPicker
=
CurrencyPicker
()
fun
showModal
(
preferences
:
UserPreferences
):
UserPreferences
{
greetingPicker
.
greeting
=
preferences
.
greeting
localePicker
.
locale
=
preferences
.
locale
currencyPicker
.
currency
=
preferences
.
currency
show
()
return
UserPreferences
(
greeting
=
greetingPicker
.
greeting
,
locale
=
localePicker
.
locale
,
currency
=
currencyPicker
.
currency
)
}
...
}
class
Application
(
private
var
preferences
:
UserPreferences
)
{
...
fun
editPreferences
()
{
preferences
=
PreferencesView
().
showModal
(
preferences
)
}
...
}
It may seem a bit of a waste to create a new UserPreferences
to return from showModal
even if nothing has changed.
If you’re used to sharing mutable objects it may even seem dangerous.
In the world of values though, two UserPreferences
with the same values are to most intents and purposes the same object, and you would have to be in a very constrained environment to detect the extra allocation.
In this chapter we’ve examined some advantages of immutable values over mutable objects. We’ve seen how to migrate mutation towards our application’s entry points and event handlers by replacing immutable references to mutable objects with mutable references to immutable objects. The end result is that less of our code has to deal with the consequences and complications of mutability.
That said, JavaBeans were designed for use in user-interface frameworks, and UIs are in many ways the last bastion of mutable objects.
If we had more exacting liveness requirements, for example updating a WelcomeView
when the greeting preference changed, we might prefer using shared objects with change events, rather than immutable values.
Converting mutable objects to values and transformations is a repeating motif. Chapter 5 continues the discussion with respect to collections. Chapter 8 looks at how to translate code using accumulating parameters to higher order functions over collections.
1 We’ll gloss over the Serializable
requirement as even Sun never really took this seriously.
54.163.221.133