Chapter 3. Beans to Values

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.

What is a Value?

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 Strings are immutable), a URI is a value (because URIs 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.

Why Should We Prefer Values?

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.

Migrating Beans to Values

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.

Conclusions

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
54.163.221.133