mikeyin.org | what's it going to take?

TAG | code

Check out this Java 5 code:

  public static <T> List<T> extractSomeStuff(Collection collection) {
    List<T> extractedStuff = new ArrayList<T>(collection.size());
    for (Object element : collection) {
      extractedStuff.add((T) element);
    }
    return extractedStuff;
  }

  public static void someMethodThatIsRunning() {
    List integers = new ArrayList();
    integers.add(1); integers.add(2); integers.add(500);

    List longs = extractSomeStuff(integers);

    long notAnInteger = longs.get(0);
  }

Will there be a ClassCastException thrown? Yes.
Do you know what line it will be on?

Possibly not where you expect. Java Generics are designed to be able to easily compile down to older Java versions. In essence, I could write a program in Java 5 and make it target a Java 1.4 JVM so I can run on older machines that haven’t upgraded their JVM. The way this works is by a process called type erasure which happens at compile time. Any uses of Generics are checked to make sure the types are in agreement, and after they are validated, all type information is erased.

At any point where you actually need to the typed object, it will be replaced with a cast at runtime, so for List, when T get(int index) is called, the result gets cast to T.

The result is that the ClassCastException happens at long notAnInteger = longs.get(0); and not at extractedStuff.add((T) element); because that type information is erased.

The moral of the story is don’t cast objects with Generic types unless you really know what you’re doing. 90% of the time, you’re probably presenting a user with a method contract that you won’t be able to fulfill, and the user will only find out several lines later in their code when they realize that their collection is filled with objects they didn’t expect.

· · ·

Theme Design by devolux.nh2.me