The Final (Constants) Story - Final Collections
(Page 4 of 7 )
Periodically, while programming, you may want to make constant sets and store them in final variables for public use. This desire can lead to all sorts of problems. Consider the code in Example 2-11.
Example 2-11. A collection in a final static member
package oreilly.hcj.finalstory;
public class FinalCollections {
public static class Rainbow {
public final static Set VALID_COLORS;
static {
VALID_COLORS = new HashSet();
VALID_COLORS.add(Color.red);
VALID_COLORS.add(Color.orange);
VALID_COLORS.add(Color.yellow);
VALID_COLORS.add(Color.green);
VALID_COLORS.add(Color.blue);
VALID_COLORS.add(Color.decode("#4B0082")); // indigo
VALID_COLORS.add(Color.decode("#8A2BE2")); // violet
}
}
}
The goal of this code is to declare a class with aSetoffinalandstatic Colors representing the colors of the rainbow. You want to be able to use thisSetwithout concerning yourself with the possibility of accidentally changing it. The problem is that theSetisn’tfinalat all! Break it with Example 2-12.
Example 2-12. A defect caused by a nonimmutable set
package oreilly.hcj.finalstory;
public final static void someMethod() {
Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler
System.out.println(colors);
}
The reference to theSet is final, but theSet itself is mutable. In short, your constant variable isn’t very constant. The point is thatfinalis not the same as immutable.
You can firm up this code in the same way you locked down returned collections from a bean in Chapter 1:
package oreilly.hcj.finalstory;
public static class RainbowBetter {
public final static Set VALID_COLORS;
static {
Set temp = new HashSet();
temp.add(Color.red);
temp.add(Color.orange);
temp.add(Color.yellow);
temp.add(Color.green);
temp.add(Color.blue);
temp.add(Color.decode("#4B0082")); // indigo
temp.add(Color.decode("#8A2BE2")); // violet
VALID_COLORS = Collections.unmodifiableSet(temp);
}
}
}
This version of the class is much better. YourSet ofColors cannot be modified because you have turned it into an immutable object. The reference to theSetisfinal, and the contents of the collection are locked down.
In thestatic{}initializer, note how you have to use a temporary set to store the colors. This is because you can set afinalvariable only once, even in the initializer. If you try to set it more than once or change the variable in the initializer, your compiler will give an error message stating that you cannot change thefinalvariable. Remember that deferredfinals are a one-shot deal. Once set (no pun intended), they can’t be changed.
Now that you have a strategy to lock down your Set, let’s revisit the old logic bug that we discussed in Example 2-12:
package oreilly.hcj.finalstory;
public final static void someMethod() {
Set colors = RainbowBetter.VALID_COLORS;
colors.add(Color.black); // <= exception here
System.out.println(colors);
}
Now that you have theSetlocked down, this code results in an exception. Specifically, the method will throw anUnsupportedOperationExceptionwhenever the user tries to use any write methods onVALID_COLORS, as it is now immutable. In this case, you haven’t been able to trade a logic bug for a compiler bug, but you have been able to trade a logic bug for an exception. Although this trade isn’t as good, it’s still definitely worthwhile. Always use thejava.util.Collectionsclass to get unmodifiable collections and maps when creatingfinalcollections and maps.
As far as unmodifiable sets go, the performance hit is negligible. As it turns out, the JDK implements unmodifiable collections in a performance-conscious way. If you look into the JDK source, you will see the static nested classesUnmodifiableSet andUnmodifableCollection. The code in Example 2-13* is pasted directly from the JDK source. All I did was change the spacing to conform to O’Reilly standards and remove the Javadoc for brevity’s sake.
Example 2-13. Implementation of unmodifiable collections
package oreilly.hcj.finalstory;
public static Collection unmodifiableCollection(Collection c) {
return new UnmodifiableCollection(c);
}
static class UnmodifiableCollection implements Collection, Serializable {
// use serialVersionUID from JDK 1.2.2 for interoperability
private static final long serialVersionUID = 1820017752578914078L;
Collection c;
UnmodifiableCollection(Collection c) {
if (c==null)
throw new NullPointerException();
this.c = c;
}
public int size() {return c.size();}
public boolean isEmpty() {return c.isEmpty();}
public boolean contains(Object o) {return c.contains(o);}
public Object[] toArray() {return c.toArray();}
public Object[] toArray(Object[] a) {return c.toArray(a);}
public String toString() {return c.toString();}
public Iterator iterator() {
return new Iterator() {
Iterator i = c.iterator();
public boolean hasNext() {return i.hasNext();}
public Object next() {return i.next();}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public boolean add(Object o){
throw new UnsupportedOperationException();
}
public boolean remove(Object o) {
throw new UnsupportedOperationException();
}
public boolean containsAll(Collection coll) {
return c.containsAll(coll);
}
public boolean addAll(Collection coll) {
throw new UnsupportedOperationException();
}
public boolean removeAll(Collection coll) {
throw new UnsupportedOperationException();
}
public boolean retainAll(Collection coll) {
throw new UnsupportedOperationException();
}
public void clear() {
throw new UnsupportedOperationException();
}
}
public static Set unmodifiableSet(Set s) {
return new UnmodifiableSet(s);
}
* From JDK source Java.util.Collections.
When you callCollections.unmodifiableSet(), the class creates a new instance of this static nested class and sets the source collection as the delegate object. As you can see in the example code from the JDK, the classUnmodifiableSetimplementsjava.util.Setand inherits fromUnmodifiableCollection, which in turn implementsjava.util.Collection. Together, they form a delegate structure. Any read call to theUnmodifiableCollectionis forwarded to the delegate collection. However, if the user tries to access a write operation, the class throws an instance ofUnsupportedOperationException. Therefore, the additional overhead of theUnmodifiableSet is only a single method call.
This delegate structure also plugs another big hole: if theUnmodifiableSetclass inherited fromHashSet, then the user could just cast the instances back toHashSetto gain access to write methods. The delegate structure in the JDK quite elegantly blocks this, ensuring that anUnmodifiableSettruly is unmodifiable, even when placed in the hands of a clever and sneaky programmer.
All of the other collection classes work similarly toUnmodifiableSet. You should use these heavily in your code. Regrettably, there is no similar way to lock downfinalarray objects, so be careful when using them.
Next: Instance-Scoped Variables >>
More Java Articles
More By O'Reilly Media
|
This article is excerpted from chapter two of Hardcore Java, written by Robert Simmons Jr. (O'Reilly, 2004; ISBN: 0596005687). Check it out at your favorite bookstore. Buy this book now.
|
|