Section Computer Science - Noncommerial pages
Java programming - Learning from bad examples
javax.swing.border.Border and its derivates
The types javax.swing.border.Border and BevelBorder show us four things we should not do:
- Giving a fuzzy documentation.
- Implementing a semantically immutable class technically mutable.
- Implementing a value object type that is not unique (also called interned) without implementing its equal and hashCode methods.
- Not marking immutablility by an interface or an annotation.
The type documentation of Border tells us at point three of the usage notes that "Border instances are designed to be shared". What does that mean? What does designed mean? Does it mean the semantic or the implementation? And what does "shared" exactly mean. Here, we assume that shared means having multiple fields referencing the same object. However, types that represent value objects are intended to be shared as well as types that represent entities. Only types that represent mutable objects that are used like value objects are not intended to be shared by multiple objects.
Taking a look at for example BevelBorder we see that "designed to be shared" means a semantically immutable value object type.
This kind of fuzzy documentation is what wastes our time and disturbs understanding. We conclude: Give a clear documentation. Here we should state clearly that Border is semantically immutable.
Implementing an immutable class
BevelBorder is semantically immutable. It has no mutable public fields and no mutator methods. We may denote it weak immutable. Because the fields of BevelBorder are neither final nor private it is technically mutable. There are three ways of a mutation:
- We derive a class and access the protected fields.
- We put out own class in the package and access the protected fields.
- We use reflection to access the protected fields.
However, all these mutations are not a direct and simple misuse of instances of BevelBorder. A misuse can be unintentionally in a derived class or intentionally to attack the program or even to do some tricky programming.
We conclude: implement a semantically immutable object technically immutable if you can. Make all fields at best final or at least private without any mutator methods. If they are private to allow lazy initialization, ensure thread safety. However, there may be exceptional cases with a virtual lazy initialization that can reason protected none final fields.
Note that immutability here does not imply deep immutability.
Implement equals and hashCode for value object types that are not unique
In Java, the == operator compares the identity of two objects. The equals method should define the usual equality realation. In case of a value object type it should define the value equality. In simple cases it is the equality of all fields. However, the usual equality relation of value object types is not their identity. Except the case that the the value objects are unique (also called interned). If we compare to complex number value objects we do not want to know if they are identical. We want to know if they represent the same complex number.
By the contract of the hashCode method, it must return the same value for equal objects. Thus, if we override the equals method we are enforced to implement a suitable hashCode method also.
BevelBorder seems to be thought as a value object type and it is not unique. However, it does not implement a suitable equals method and thus behaves like a unique value object type or like an entity type. That is inconsistent.
Note: In case of an entity type it is usual in Java to compare the identity in the equals method by not overriding the implementation from java.lang.Object.
Mark immutable classes
The last point is to mark immutable types either by a marker interface or by an annotation. At the time Border was implemented there were no annotations in Java. But a marker interface, maybe named "Immutable" should have been used. That makes it clear that we have immutable objects without the necessity to waste our time reading type documentations to find out. Also classes such as java.util.HashMap could maybe use such an interface to ensure that key objects are not mutable. Currently both, a marker interface and an annotation have advantages. A simple solution is for example to define a marker interface such as "Immutable" that has an annotation "Immutable" (from another package of course). Another advantage of marker interfaces and annotations is that tools have the opportunity to evaluate these for example to do static error checking.
We conclude: Use a marker interface or an annotation or at best both to declare a type to be immutable.
Author and Copyright 2014: Raoul Naujoks, Braunschweig, Germany Version 9.9.2014