Home of The JavaSpecialists' Newsletter

251Collections.checkedCollection()

Posted: 2017-11-30Category: LanguageJava Version: 8Dr. Heinz M. Kabutz
 

Abstract: Since Java 5, we have been able to create collections that would check at runtime that the objects added were of the correct type. But very few Java programmers know about it. The benefit we get in debugging ease is huge, as exceptions are thrown earlier.

 

Welcome to the 251st edition of The Java(tm) Specialists' Newsletter. Exactly 17 years ago today, with fear and trepidation, I sent out the first edition of my newsletter to 80 friends and colleagues that I had gleaned from my inbox. I expected mocking and laughter down the corridors. There were some who made fun of my pretentious style and my title "Java Specialist", but the majority liked it and encouraged me to carry on. I had been tinkering with Java for three years already at the time and had made a few interesting discoveries. Over the years I supplemented the newsletter with Extreme Java corporate training and, of course, the hottest Java unconference in the world (Crete in July FTW). It has been too much fun!

Even more fun, I have been steadily honing my skills as a television personality. Living on Crete has some disadvantages. To get anywhere requires at least two flights. Often three. I travel so much that by March my Senator Status Star Alliance Gold Card is safe. Fortunately a new craze has hit this world, that of self-study training. And so I have launched Java Specialists Learning, where you can take all of my courses from the comfort of your sofa.

Birthday and Anniversary Special: Since the 30th November 2017 is the 17th anniversary of our newsletter and my birthday is coming up on the 4th December, we are giving away a 30% discount on our new Data Structures for Java 9 Course (Late 2017 Edition). Whether you are a seasoned Java programmer or you just want to get ready for your next job interview, this course will help you. Besides detailed lectures, the course has over 130 questions that will help you discover what you missed.

Collections.checkedCollection()

Dinosaurs roamed the earth. Fred Flintstone wrote Applets with JBuilder. A shaft of lightning split the dark sky and with a flash <> appeared on his amber screen. Fred scratched his head. "What on flat earth was List<String>?"

It was the advent of generics.

Programmers do not like change. Or rather, we do not like change that will break something that was working perfectly well before. Java 1.4 introduced the assert keyword. It broke a bunch of our classes. Java 1.5 added enum to the relatively short list of reserved words, a popular name for Enumeration instances. And that was the last time a new keyword was added to the Java Programming Language. Sun Microsystems' engineers knew their audience. They also knew that for their generics to be accepted, old code should preferably still compile without any changes necessary.

Generics were designed so we could ignore them if we wanted to. The javac compiler would issue a faint sigh, but would still compile everything as before. How did they do it? Type erasure was the magic ingredient. When they compiled the class ArrayList<E>, the generic type parameter was erased and replaced with Object. Even the E[] was erased to Object[]. In Java 6, they changed the element array in ArrayList to the more honest Object[].

By not distinguishing at runtime between ArrayList<String> and ArrayList<Integer>, we allowed Java programmers to still shoot themselves in the foot like so:

import java.util.*;

public class FootShootJava5 {
  public static void main(String... args) {
    List<String> names = new ArrayList<>();
    Collections.addAll(names, "John", "Anton", "Heinz");
    List huh = names;
    List<Integer> numbers = huh;
    numbers.add(42);
  }
}
  

Sure, javac would emit a warning, but at runtime everything would appear to work. It was only when we retrieved elements from ArrayList<String> that a cast to String was inserted into the client code and then a ClassCastException would jump in our faces. This is an example of an exception that is thrown late. A while after the incorrect object has been inserted into the ArrayList<String>, we discover that it wasn't a String after all, thus if we add the following we see the problem:

import java.util.*;
import static java.util.stream.Collectors.*;

public class FootShootJava8 {
  public static void main(String... args) {
    List<String> names = new ArrayList<>();
    Collections.addAll(names, "John", "Anton", "Heinz");
    List huh = names;
    List<Integer> numbers = huh;
    numbers.add(42);
    System.out.println(names.stream().collect(joining("+")));
  }
}
  

Results in a rather grumpy:

ClassCastException: Integer cannot be cast to CharSequence
    at ReduceOps$3ReducingSink.accept()
    at ArrayList$ArrayListSpliterator.forEachRemaining()
    at AbstractPipeline.copyInto()
    at AbstractPipeline.wrapAndCopyInto()
    at ReduceOps$ReduceOp.evaluateSequential()
    at AbstractPipeline.evaluate()
    at ReferencePipeline.collect()
    at FootShootJava8.main
  

Since the exception is thrown late, it results in wasted programmer effort searching for where the wrong type could have been inserted into the list.

And yet there has always been a better way, even in Java 5. We can wrap our List object with a checkedList. This way, every time we add an element, it is checked that it is of the correct type. The ClassCastException thus happens during the add(42), rather than much later. For example:

import java.util.*;
import static java.util.stream.Collectors.*;

public class FootShootWithSafetyCatch {
  public static void main(String... args) {
    List<String> names = Collections.checkedList(
        new ArrayList<>(), String.class);
    Collections.addAll(names, "John", "Anton", "Heinz");
    List huh = names;
    List<Integer> numbers = huh;
    numbers.add(42);
    System.out.println(names.stream().collect(joining("+")));
  }
}
  

We would still get a ClassCastException, but at the place where the damage was done:

ClassCastException: Attempt to insert class Integer element into
        collection with element type class String
    at java.util.Collections$CheckedCollection.typeCheck()
    at java.util.Collections$CheckedCollection.add()
    at FootShootWithSafetyCatch.main
  

The checked collection would also discover objects that are added via reflection and throw a ClassCastException. It could not safeguard against "deep reflection", but then not much can.

You might wonder why I am writing about a method that was added in Java 5? The reason is that hardly anybody I speak to has heard of Collections.checkedCollection() and its derivatives. It is useful to make your collections just a bit more robust against accidental or deliberate tomfoolery.

It can also be a quick and easy way to debug any ClassCastException you might discover in your system. Wrap the collection in a checked exception and the guilty party will quickly come to the fore.

Oh one last thing, completely unrelated to Java, but definitely related to our profession. Today also marks one year since I started my running streak, running at least one mile a day, in snow, rain, lightning and 48C heat. It's been fun and a great way to think about all sorts of things. I've had far more energy, have slept better and have produced more this year than in many previous years. If you're a couch potato, I can only recommend you try Streak Running and join me in the list of people who've run for at least one year, every day. No excuses.

Kind regards from Crete

Heinz

 

Related Articles

Browse the Newsletter Archive

About the Author

demo

Java Champion, author of the Javaspecialists Newsletter, conference speaking regular... About Heinz

Java Training

We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.

Java Consulting

Nobody ever wants to call a Java performance consultant, but with first-hand experience repairing and improving commercial Java applications - JavaSpecialists are a good place to start...

Threading Emergency?

If your system is down, we will review it for 15 minutes and give you our findings for just 1 € without any obligation.