How’d this String get into my List<Integer>?!
I was having a discussion with some co-workers at the No Fluff Just Stuff conference this weekend about Java and incorrect types getting put into generic collection classes. Generics were introduced in Java 5 in 2004 to add a compile time type safety to allow a type or method to perform operations on objects of various uniform or related types. The discussion we had focused on the fact that generics do not accomplish what the language specification says they will accomplish by allowing objects of a different type into a generic object.
Let’s specifically talk about generic collections in Java 5. I can declare and initialize a list like so:
List<Integer> myInts = new ArrayList<Integer>();
This declaration says that only Integers or subclasses of Integers is allowed into my list. I can add an Integer to the list using the add method.
myInts.add(1);
This code compiles and runs smoothly. What happens if I try to add a String to my new list of Integers?
myInts.add(“Hello, world!”);
I compile with javac and get:
WrongTypePut.java:25: cannot find symbol
symbol : method add(java.lang.String)
location: interface java.util.List<java.lang.Integer>
myInts.add(“Hello, world!”);
^
1 error
That’s interesting. I’m told that the method add(String) doesn’t exist in the type List<Integer>. This is a good thing. If I were working on a large program for a production product or service I definitely wouldn’t want Strings getting into my Lists of Integer. How would I know what object to expect back out of the List at run time? The answer is I wouldn’t. I’d iterate over the list and expect an Integer and want to call numberOfLeadingZeros() on it but I wouldn’t even make it that far. I’d get a ClassCastException during the assignment in the enhanced for loop.
So far it looks like generics solve the type safety problem and won’t allow objects of one type into our list of a different type. So why did we have this discussion about generics failure to provide type safety? Compatability and consistency. Java 5 was a big step forward in terms of new langauge features, but who was going to go back and retrofit all this new type safety into production systems built up over the last X years just to migrate to Java 5? Upgrading all Java programs that use the Collections API to use Generics would have been too big a burden to bear so Generics was left as an optional but preferred feature. This allows new development to use Generics or other Java 5 features without the need to retrofit old code. New projects using Java 5 and Java 6 should take advantage of the type safety offered by Generic but sometimes (in)consistency in declarations can lead to sloppy mixed use of generic and non-generic code. Let’s see what happens when we use what I’ll call “mixed mode” Collections.
In the previous example I declared a List of Integer. Because of language version compatability nothing prevents me from assigning my List of Integer to a List.
List<Integer> myInts = new ArrayList<Integer>();
…
List yourInts = myInts; // perfectly legal in Java 5 and Java 6
The uninformed developer might think, “this is great! Now instead of being limited to holding only Integers I can add any type I want to this list!” And then he does it:
yourInts.add(“Hello, world!”); // still perfectly legal Java
No problems so far. The program will continue to execute for as long as I avoid reading that String from the List. But now I come to a point in the program exectution where I iterate over the entire list:
for (Integer i : myInts) {
System.out.println(i);
}
First of all the compiler will not let you even use yourInts in the enhanced for loop because it can’t cast an element from Object (which is what yourInts contains) to Integer (which is what the for loop is expecting). But let’s say we’re in a different part of the program, far far away from the method where myInts was assigned to yourInts and we can’t immediately see the problem. This is what happens when the program runs:
Exception in thread “main” java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at net.anthonychaves.sandbox.generics.WrongTypePut.runDemo(WrongTypePut.java:29)
at net.anthonychaves.sandbox.generics.WrongTypePut.main(WrongTypePut.java:40)
It’s our old friend from pre-Java 5 days, ClassCastException! This runtime exception was completely avoidable if only I had been consistent in my use of Generics throughout the program. Because I failed to declare yourInts as List<Integer> my extremely important financial transactions didn’t get processed in time and I lost a ton of money. Not really, but hopefully you get the point.
There are a couple of things we can take away from this example. The first is that if you are using Java 5 or Java 6 and your problem domain is condusive to using Generics then you should absolutely take advantage of the built in type safety they offer, if only for the Collections API. If it’s not then you have other problems on your hands. The second is that you need to consistently apply Java Generics in your program. One slip-up and you’re back to the stone age of type unsafe Collections and you will drag everyone else on your team down with you. The third and most important thing is that Generics absolutely trivialize an entire class of bugs as long as you use them.
I realize this example was specific to the Java Collections API and Generics are far more powerful than I present them here. The Java Language Specification has whole chapters dedicated to the topic, there are plenty of examples in both a beginner and advanced trail in the Java Tutorial and there are countless more articles on the Internet at large detailing best practices that I don’t have time or resources to get into here. Besides they all do a far better job than I could do of explaining the complete capabilities of Generics.
If you have any comments or corrections please leave them below.





Did you have to ignore compile warnings to see this problem?
Hi Ricky,
Compiling that program with javac gets this output:
javac net\anthonychaves\sandbox\generics\WrongTypePut.java
Note: net\anthonychaves\sandbox\generics\WrongTypePut.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
Obviously a careful developer would then recompile with -Xlint:unchecked and find out what the problem is and fix it. A few concerns are careless developers and using 3rd party libraries to which you don’t have access to the source code. A 3rd party library not using Generics can do what was shown in the example and worse and all the warnings in the world won’t help you if you depend on that library for major functionality.
Thanks for reading and thanks even more for commenting!
Anthony
I’m not concerned with developers who ignore warnings, as I don’t currently work with any of them. If I did, I’d be concerned with not working with them, or changing their habits, rather than criticising the compiler for giving warnings rather than errors.
A good optional typing implementation, whenever we see one, will give warnings, not errors.