Value Objects
Goals
- Understand and recognize value objects.
- Create value object classes that behave as expected according to best practices.
Concepts
- Abstract Window Toolkit (AWT)
- autoboxing
- boxing
- consistent
- to delegate
- design pattern
- factory design pattern
- factory method
- fluent method
- hash code
- immutable
- JavaFX
- reflexive
- string interning
- Swing
- symmetric
- transitive
- unboxing
- value object
- wrapper class
Library
java.awt.Color
java.awt.Insets
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Integer.valueOf(int)
java.lang.Long
java.lang.Number
java.lang.Number.longValue()
java.lang.Object.equals(Object)
java.lang.Object.hashCode()
java.lang.Short
java.lang.String
java.math.BigDecimal
java.math.BigInteger
java.lang.Objects.hash(Object...)
Preview
Lesson
When object-oriented programming first came out, developers began creating complex hierarchies of types. Each class had many public methods that allowed instances not only to do things
but also to change their internal information. Changing the state of an object was a simple as as calling one of its methods. But because these objects could be changed, a module with a reference to an object in one part of a system might, simply by calling an object's method's method, make changes that would come as a surprise to other parts of the system.
For some classes representing business objects
, such as a bank account, it is natural to allow access from multiple sources; it is in part the responsibility of the bank account class to coordinate this access. But what about a simple value such as five dollars
, or the ninth of September
? It would come as a surprise to someone depositing money of the amount of cash in their hand suddenly changed from five dollars
to eight rupees
, but this is exactly what could happen if the money value
class allowed callers to change the money and currency represented.
Thus it has recently become clear the benefits of creating value objects to represent simple values (even if they contain multiple instance variables) that are interchangeable if their represented values are interchangeable. Take for instance a class to represent a calendar date
. The class might contain a year, month, and day of the month. Once created, no one would be able to change the date it represented. You could create various date instances, and if they contained the same values (year, month, day) they would be considered an equivalent date (even if they were not the same instance).
Here are examples of small bundles of values that might be best represented by value objects. What internal values might each of these value objects hold?
- an amount of money with its currency type
- a date on a calendar
- a range of values
- a geometrical point
- a single primitive value represented by an object (see Primitive Wrappers, below)
Famous Value Objects
Before, discussing best practices of making your own value objects, it is instructive to see what value objects already exist for your use.
Primitive Wrappers
You've learned about and grown to love Java's primitive types: byte
, short
, int
, long
, float
, double
, boolean
, and char
. You know that these types are the only ones that do not reference objects, and thus cannot be null
. But what if you have some method that works for any object, and you'd like to pass in a value from one of these primitive types? Wouldn't it be possible to create a class that did nothing else but hold a single primitive value?
Indeed Java has created such wrapper classes for all of the primitive types, so called because they conceptually wrap around
the primitive value. You've already seen one of these classes, java.lang.Double
, when you used the constant Double.POSITIVE_INFINITY
, but the class Double
class does more than just provide constants and utility methods; it can be instantiated to represent a single double
value. (More on how to create an instance of Double
can be found below under Static Factory Methods.) Here are the primitive types and corresponding wrapper classes provided by Java.
Primitive Type | Wrapper Class |
---|---|
byte | java.lang.Byte |
short | java.lang.Short |
int | java.lang.Integer |
long | java.lang.Long |
float | java.lang.Float |
double | java.lang.Double |
boolean | java.lang.Boolean |
char | java.lang.Character |
Numbers
One benefit of having a class wrap a primitive type is that classes can have inheritance hierarchies. Java has provided a java.lang.Number
class which serves as a base class for all the wrapper classes representing primitive types that are numbers.
All java.lang.Number
instances, regardless of subtype, will have a longValue()
method for returning their value as a primitive long
.
Boxing
Over the years Java has offered various conveniences to developers, many of which were no more than syntactic sugar
, adding no real capabilities but making it easier to accomplish something already possible. One such feature, which on the surface seems very useful, is autoboxing. Creating a wrapper class instance (such as Integer
) to represent a primitive value (such as 5
) is called boxing. Converting that object back to a primitive value (such as Integer.intValue()
is called unboxing. For a while now Java has had the ability to make this conversion automatically, without the developer explicitly requesting it.
Consider the case of a method that takes an Integer
as a parameter. If you attempt to invoke that method using the value 5
, Java will convert that value to an instance of Integer
without being asked! Likewise, if the method in question uses that Integer
instance in an expression, Java will convert the reference back to a primitive value on the fly.
Big Numbers
Along with the primitive wrapper classes Java provides two classes for representing extremely large numbers, as you might have noted from the Numbers
class diagram above. Integral values that are larger than Long
supports can be stored in java.math.BigInteger
, while non-integral values larger than Double
supports can be stored in java.math.BigDecimal
—and with more precision as well.
Strings
A value object you've already worked with is java.lang.String
. Although you haven't used the new
keyword to create them, Java creates value objects for the string literals you create.
Worst Practices
When Java was first released, many of its complex data types stored data in a publicly modifiable class that was little more than a structure in the C programming language. A good example is the java.awt.Insets
class from the Java AWT, which provided public bottom
, left
, right
, and top
fields.
In practice this had limitations:
- Any part of the application with access to an instance of the class could modify it.
- There was no way to create constant
Insets
for reuse, as its value might be modified, as one never knew whether theInsets
instance had the same values as when it was created.
Best Practices
Now that we know what not to do, let's take a look at some of the things we should do when making a class to represent a simple value. We'll be following the Java documentation's description of value-based classes, which generally follows accepted best practices.
Immutable Classes
One of the fundamental principles that has become evident in object-oriented programming is that making a class immutable, or unable to be changed (that is, the values it holds in its instance variables), makes a class and the program that uses it less prone to errors. If a program creates an instance of a class and then later makes some decision with the assumption that the object's value has not changed, the program will likely produce errors if in the meantime some other module has modified the object. Immutability prevents this from happening.
Immutability is especially relevant for value objects. You wouldn't want to make a calculation with the number 5
, only to find out that half-way through the algorithm that the value 5
now was the value 10
without your knowledge. Value objects represent simple values; they simply may have more parts. Thus immutability is important to them as well.
Final Classes
Many value objects are defined in classes marked final
, which prevents others from creating subclasses. If the classes weren't final
, others could possibly extend the class and change the behavior. There are other ways to prevent others to override behavior such as marking methods as final
, but for most simple value objects it's probably easiest and less confusing just to mark the classes themselves as final
.
Static Factory Methods
Another common trait of value objects is that they provide special constant methods the sole purpose of which is to create instances of the class! We call these methods factory methods because they function like a factory
that produces objects on demand. You already used static factory methods in the lesson on generics.
You might think that such a method would be unnecessary and even redundant—after all, isn't that what a constructor is for? But it turns out that, for value classes, having a separate static method for object creation provides much more control than a constructor. Here are some of the benefits of a static factory method:
- The factory method can configure the object, resting assured that the object is fully constructed.
- The object can choose whether it actually creates a new instance or returns an existing instance.
How does this relate to immutability?
The primitive wrapper classes provide excellent examples of static factory methods named valueOf(…)
. The java.lang.Integer.valueOf(int)
method as one example keeps a cache of pre-constructed Integer
instances. If the caller requests a wrapper instance matching one that is already in the cache, the existing instance can be returned rather than creating a new one.
If a caller were to invoke new Integer(5)
multiple times, multiple instances of Integer
would be created, even though they all hold the same primitive value. Yet if a caller invokes Integer.valueOf(5)
multiple times, each time the same instance of Integer
would be returned, with no need to create a new one. This not only saves time, it also reduces memory usage.
Fluent Value Manipulation Methods
Because they are immutable, the values stored by value objects can't be modified
as such. But it is typical that one might want to manipulate that value to produce some other value (commonly an other instance of the same value class). The java.lang.BigInteger
class, as it may contain values too large to be manipulated via a primitive value, has special methods for adding and multiplying (among other operations) the current BigInteger
instance with another.
As the result is itself a BigInteger
, additional methods can be chained
to further manipulate the value, producing a sequence of fluent methods
that resembles English. Thus arithmetic with BigDecimal
can performed like this:
Equality
Value objects are simple, immutable objects representing a single value. Because they are immutable, they can thus be interchangeable! For this to function correctly, instances that hold equivalent objects must be recognized as equal—after all, calculations would not work if somehow 5
was not equal to 5
. But because different instances could have been created to represent the same values, you cannot rely on ==
to compare them. Instead, you must override the Object.equals(Object)
so that they can be compared using fooBar1.equals(fooBar2)
.
The equals(…)
method is fundamentally important, and works like this: it takes a single argument, which may be null
, and returns true
or false
depending on whether the argument is equal
to the object on which the method is being invoked. What constitutes equal?
What constitutes as equal depends on the semantics of your class, but there are a few common-sense guideposts:
- If the argument is
null
, theequals(…)
method must always returnfalse
, becausenull
is not equal to any reference. How do we know that the other reference is notnull
as well? - If the argument is the same instance (i.e.
fooBar1 == fooBar2
), theequals(…)
method must always returntrue
, because an object is always equal to itself. - The argument must be of a compatible type. For value objects, this usually means that it is the same class.
- Finally, some value or values within the objects must be equal.
- If the object is a value object, all the value(s) stored inside the value object should be the same.
- If the object represents some business object that has a unique identifier, the identifiers should be the same.
Let's look how equals might be implemented on a Point
value object, which stores two-dimensional geometric coordinates.
Hash Code
A hash code in Java is an arbitrary number assigned to an object to speed up comparison. Every class that overrides the equals(…)
method must also override the Object.hashCode()
method—even those classes that do not represent value objects. The hashCode()
method is used throughout Java as a way to make comparing objects more efficient.
As you learned in the lesson on hash tables, a hash code allows you group objects into buckets
based upon their hash codes. When it's time to retrieve the object, the hash table needs merely to look in the bucket with the same number as the hash code. Each item in the bucket will still need to be compared via equals(…)
, but the search will be narrowed down considerably.
There are some common-sense implications of this:
- Two objects that return
true
forequals(…)
must return the same value forhashCode()
. Otherwise, they wouldn't be placed in the samebucket
. Thus the hash code should be based upon the same internal properties being compared forequals()
. This means that the default implementation ofhashCode()
, which returns some internal memory address, won't work for a class with a customeguals()
method, which is whyhashCode()
must be overridden ifequals()
is overridden. - Two objects that return
false
forequals()
are not required to return differenthashCode()
s. In fact, it would be permissible (though not advisable) to have all objects simply return the same number, such as5
. Although the hash table would still work, this would result in all of the object instances being placed in the samebucket
, which wouldn't cut down the search at all; it would still be required that each object be compared usingequals()
, which is no different than searching an unordered list. Thus two objects that returnfalse
forequals()
should return different hash codes as much as possible. - Because the
hashCode()
represents the internal values being compared byequals()
for later lookup, the same hash code should be used as long as the the internal equality values don't change. For immutable objects such as value classes, this means that thehashCode()
value returned should never change.
Hash Codes for Single Values
A good hash code must be the same for equal objects, and should be different for non-equal objects if possible. So how does one determine a good hash code? It all depends on what the underlying values are relating to equality.
If there is only one value that determine equality, the answer is simple: return the hash code of that object! For example, imagine a value object representing the postal code of some country (referred to as a zip code
in the United States). This class would simply wrap a string value, bringing the benefit of type safety while providing added semantics. As String
s themselves override hashCode()
and follow its contract, and as PostalCode
's equality is based upon this single string, PostalCode.hashCode()
can simply return the hash code of the internal string value. We say that PostalCode.hashCode()
delegates to String.hashCode()
.
Hash Codes for Multiple Values
If your value object (or any object) considers equality based upon more than one value, you must find a way to return a single hash code value that somehow represents those underlying values while still following the contract of Object.hashCode()
. If you search the web you will find various formulas which purport to combine multiple values into a single, good hash code. But nowadays there is no need to roll your own
hash code algorithm; Java provides the java.lang.Objects.hash(Object...)
method to calculate a hash code based upon as many values as you provide. Let's make a Point.hashCode()
method to go along with our Point.equals(…)
method above:
Review
Summary
For simple values stored in classes, follow best practices in defining classes for these value objects
:
- immutable class
- final class
- static factory method(s)
- fluent value manipulations method(s)
- Implement
Object.equals(Object)
:- It must return
false
fornull
. - It must be reflexive: an object must be equal to itself.
- It must be symmetric: if
fooBar1.equals(fooBar2)
, thenfooBar2.equals(fooBar1)
must returntrue
. - It must be transitive: if
fooBar1.equals(fooBar2)
andfooBar2.equals(fooBar3)
, thenfooBar3.equals(fooBar1)
must returntrue
. - It must be consistent: calling
equals(…)
multiple times on the same object with the same argument must return the same result.
- It must return
- Implement
Object.hashCode()
:- Calling
hashCode()
multiple times on an object must produce the same hash code if the information related to equality hasn't changed. - If two objects are equal according to the
equals(…)
method, theirhashCode()
methods must return the same value. - If two objects are not equal according to the
equals(…)
method, theirhashCode()
methods must return different values as far as possible to aid in efficiency.
- Calling
Gotchas
- Don't make a public constant of an object unless it is immutable; otherwise, someone could change e.g. your value for
PI
to something that isn't3.14159…
. - While the primitive wrapper classes are very useful, they become dangerous when combined with autoboxing and unboxing. If you use a reference to a wrapper class in your algorithm, make sure it isn't
null
. - Some
String
literal values may have been interned and may returntrue
for==
. But do not rely on this behavior; useequals()
instead of==
to compareString
instances.
In the Real World
- Immutability is immensely useful in reducing error. Try to make your classes immutable as much as possible, even if they don't represent traditional
value objects
. - If you have many values to store, primitive wrapper classes make take up too much room. It may be better to store the values as primitives and convert them to their wrapper equivalents as needed.
BigDecimal
has become a popular class for storing money values, as it is not subject to the approximations and rounding errors of IEEE floating point values.- You should indicate in the contract documentation what constitutes
equal
for your class. - Don't try to calculate a hash code manually; use one of the built-in hash code methods.
Think About It
- What constitutes
equal
for my class? Does my class represent a simple value or values? Or does it instead represent abusiness object
identified by some unique identifier?
Self Evaluation
- Why is immutability so important for value objects?
- What is autoboxing/unboxing and how can it be dangerous?
- Why might one make all constructors of a class
private
? What might be provided for the developer to use instead? - Why is it so important to override
equals(…)
?
Task
Some of the books shown in the Booker application have a range of years for the first published
date. Create a value object class that represents a range of years, and integrate that value object into your periodical hierarchy at the appropriate place(s).
See Also
- Value-based Classes (Java™ Platform, Standard Edition 8 API Specification)
- ValueObject (Martin Fowler)
- VALJOs - Value Java Objects (Stephen Colebourne's blog)
- The Numbers Classes (Oracle - The Java™ Tutorials)
- Autoboxing and Unboxing (Oracle - The Java™ Tutorials)
- hashcode() and equals() method in java (Java Tutorial for Beginners Blog)
- Java HashCode (Muhammad Khojaye's Blog)
References
- Value object (Wikipedia)
- Values in Object Systems (Dirk Riehle)
- Value Object (Martin Fowler Catalog of Patterns of Enterprise Application Architecture)
- Value Object (Cunningham & Cunningham Wiki)
- String interning (Wikipedia)
Acknowledgments
- Some symbols are from Font Awesome by Dave Gandy.