Wednesday, July 22, 2009

Checked collections

The generics mechanism provides compile-time type checking. So generic collections are mostly type-safe. I did not say they will "always" be safe because it is possible to add wrong types into generic collections using unchecked casts. For instance mixing generic and non-generic code results in unchecked casts. The compiler will issue warnings when there are unchecked casts but the compilation does not fail. Also at runtime, when a wrong type is added to the parameterized collection no ClassCastException is thrown because the runtime has no knowledge of type parameters due to type erasure. So now, the wrong type is happily added to the collection. The problem of wrong type in the collection does not get discovered until much later. And when the problem is discovered, the runtime exception makes it quite impossible to discover the source of the problem i.e. where exactly the unsafe type got added. Here is a simple example followed by the runtime exception that the program throws when it runs.

import java.util.ArrayList;
import java.util.Collection;

public class UnsafeCollectionOperation {

public static void unsafeAdd(Collection c) {
c.add(new Integer(1));
}

public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("One");
c.add("Two");
unsafeAdd(c);
for (String s: c) {
System.out.println(s);
}

}

}




Here is what happens at runtime:
One
Two

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer
at UnsafeCollectionOperation.main(UnsafeCollectionOperation.java:17)


As you see from the exception above, the runtime exception does not offer any information on where exactly the unsafe type was added. My code is so small, so we can easily deduce where we went wrong, but when we deal with a lot of code, this task becomes very cumbersome. One way to deal with this problem is to use checked collection(see java.util.Collections). Checked collections are dynamically type safe views of collections. They perform a runtime type check every time an element is inserted into the collection. Any attempt to insert a wrong type will immediately result in ClassCastException. So we know exactly where we went wrong. Here is an example:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;

public class CheckedCollection {

public static void safeAdd(Collection c) {
c.add("three");
}

public static void unsafeAdd(Collection c) {
c.add(new Integer(1));
}

public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
c.add("One");
c.add("Two");
Collection<String> checkedCollection =
Collections.checkedCollection(c, String.class);
safeAdd(checkedCollection);
unsafeAdd(checkedCollection);
for (String s: checkedCollection) {
System.out.println(s);
}

}

}



Running the program produces:
Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.Integer element into collection with element type class java.lang.String
at java.util.Collections$CheckedCollection.typeCheck(Collections.java:2206)
at java.util.Collections$CheckedCollection.add(Collections.java:2247)
at CheckedCollection.unSafeAdd(CheckedCollection.java:14)
at CheckedCollection.main(CheckedCollection.java:23)


From the exception above we know exactly where we went wrong.
Couple of note worthy points are:
- When a checked collection view is created using an existing collection, it will NOT discover incorrectly typed elements, if they already exist in the collection prior to creating the view.
- After creating the checked collection view, if any access happens through the original collection and not through the checked collection, it cannot ensure type safety.
- There is a performance overhead of a runtime type check every time an element is inserted into the collection.

No comments:

Post a Comment