Thursday, June 23, 2011

Type Safety: why would you need a Collections.checkedSet in JDK1.4?

In one of the first posts on this blog I have discussed the problem of type checking (or lack of it) in some of Java collections even when using generics. Today I want to show you a similar issue that can break the type safety in your code. It’s a problem that can cause a really nasty bugs in your application – the ones that manifest themselves many thousand lines of code after the faulty method introduce it.
Imagine that you work with generics, but you use a third party code. What if some part of that code was written in Java 1.4 or earlier, therefore not using generics. Let’s go one step further: what if (for some reason) this code does not respect the typing of your collections? You would expect to get an error, right? Well, you’ll get one… but do you know when? Check the following code:

public static void main(String[] args) {
    // create a type-safe set of Integers and add some values to it
    Set<Integer> setOfInts = new HashSet<Integer>();
    setOfInts.add(new Integer(7));
    setOfInts.add(new Integer(13));

    // pass your set to a non-generic code
    Set uncheckedSet = setOfInts;
    // non-generic code does not respect your typing
    // and adds a String to your set of integers
    uncheckedSet.add(new String("Illegal"));

    // back in your code - do some set manipulations:
    System.out.println(setOfInts);
    setOfInts.remove(new Integer(7));

    // at the end iterate trough your set
    for (Integer integer : setOfInts) {
        System.out.println(integer);
    }
}

The code above is a simplified version of the described scenario. In lines 8 – 11 the ‘third party’ code is executed and adds a String into a set of Integers. So back to the question: in a given example when will error occur? One might assume in line 11 as the bad element is inserted… wrong! Maybe when we access the collection and print all of its elements?… wrong! We can even do some manipulations on it (line 15) and still be fine! The error will show his ugly head finally in the loop in line 18 when we will enter it for the second time. This will happen so late because in line 18 for the first time we access the inserted String and cast it to Integer – at this point ClassCastException occurs.
In the example above the distance between the faulty insertion and the place where exception is thrown is only seven lines, but you can imagine its being 7000. Your code can be working fine for 7000 hours and after that throw an exception that will basically give you no hint what’s wrong. If you do not use third party code you should not feel safe because of that – I am sure that if your project has more than 10000 lines there is a part of it so old and dusty that it was written without the usage of generics…
What can be done? Well, we are in luck as there is a quite simple way of protecting yourself from that: in java.util.Collections you will find a way to wrap your Set, Map or List into an forwarding class that will check in runtime the typing of the inserted objects. In the case of our code snippet we only need to add one line:

public static void main(String[] args) {
    // create a type-safe set of Integers and add some values to it
    Set<Integer> setOfInts = new HashSe<Integer>();
    setOfInts = Collections.checkedSet(setOfInts, Integer.class);
    setOfInts.add(new Integer(7));
    setOfInts.add(new Integer(13));

    // pass your set to a non-generic code
    Set uncheckedSet = setOfInts;
    // non-generic code does not respect your typing
    // and adds a String to your set of integers
    uncheckedSet.add(new String("Illegal"));

    // back in your code - do some set manipulations:
    System.out.println(setOfInts);
    setOfInts.remove(new Integer(7));

    // at the end iterate trough your set
    for (Integer integer : setOfInts) {
        System.out.println(integer);
    }
}

As you see the only difference between this and previous snippet is additional line (#4) in which we wrap our set of integers into a CheckedSet. See that due to Generic type erasure in Java the Collections.checked* methods require a class object besides a collection to wrap. Now when we run the new code the error will manifest itself just at the moment where an error is commited: in line 12 when we try to insert a string. Exactly what we needed!

To summarize: if in your project you use either third party code or legacy code that you suspect of not using generics consider wrapping your collections with Collection.checked* methods!


Note :
Users with jdk 6 or higher will get ClassCastException in any case, if they add some wrong datatype.

No comments:

Post a Comment

Chitika