Skip to content

Rough Book

random musings

Menu
  • About Me
  • Contact
  • Projects
    • bAdkOde
    • CherryBlossom
    • FXCalendar
    • Sulekha
Menu

Don’t use class literals as type-tokens

Posted on August 30, 2015July 17, 2020 by vivin

Generics were added to the Java language within J2SE 5.0, and there was much rejoicing. It was finally possible to deal with containers in a type-safe manner. Prior to the availability of generics, Java developers had to do things like this:

List people = new ArrayList();
people.add(new Person("Donkey Kong"));
people.add(new Person("Guybrush Threepwood"));

Person pirate = (Person) people.get(1);

This kind of code is very fragile since it is not easy to keep track what is inside a container. If at runtime, the object you retrieve is not of the type that you're expecting, you can get a ClassCastException. It is also remarkably easy to pollute a container by shoving objects of different types inside there, which makes it even more difficult to keep track of the types of the objects inside. Workarounds included littering code with instanceof checks, or creating a wrapper class (for example a class called PeopleList that would delegate to an internal List instance) around the container so that you could have control over the types of objects being inserted.

When generics finally arrived, people were ecstatic because now you could do things like this:

List<Person> people = new ArrayList<Person>();
people.add(new Person("Donkey Kong"));
people.add(new Person("Guybrush Threepwood"));

Person pirate = people.get(1); //It just works!

This meant no-more ugly workarounds, which means that things are awesome! Right?

Unfortunately not. When Sun introduced generics to Java, they had to do so in a manner that maintained backwards compatibility. Specifically, they had to make sure that they maintained binary compatibility with existing Java code, and also had to make sure that existing libraries could work with generic-types as well. Sun's solution to this was to subject generics to type erasure. What this means is that at runtime, generic-parameter information is lost and not retained (except in special cases). Instead, the Java compiler generates bytecode that performs explicit casts, essentially making it seem (at the bytecode level) as if you hadn't used generics at all. In most cases this was fine, since people still got the compile-time type-safety they desired. However, type-erasure also caused other problems; people soon discovered that the following was impossible:

public class Foo<T> {
    private T data;
    public Foo() {
        data = new T(); //doesn't compile!
    }
}

This is mainly due to the fact that people weren't (and some still aren't) aware that generics in Java are type-erased and furthermore, in other cases generics "just work". This is due to no fault of their own because Java is often advertised as supporting generic programming, without any mention of how they are implemented. In my opinion, this is a perfect example of abstraction-leakage since the details of how generics are actually implemented in Java leaks out, requiring programmers to know those details to understand why some usages of generics aren't valid.

So how do you write code that can find out what T is at runtime? The recommended solution was to use class-literals as runtime type-tokens. One of the changes made with the introduction of literals, was to make class instances generic as well. Where previously the language just had Class, it now has Class<T>, which lets you do things like this:

public class Foo<T> {
    private T data;
    private Foo(Class<T> type) {
        data = type.newInstance(); //exception handling omitted for brevity
    }
}

But this is a limited solution that is also semantically incorrect. This becomes evident when you try to do something like this:

Foo<Set<String>> fooSetString = new Foo(Set<String>.class); // halp

This code doesn't compile, and so the next thing to try might be to just pass in Set.class which, while it is not exactly what you want, should hopefully be good enough. But no dice:

error: incompatible types: Class<Set> cannot be converted to Class<Set<String>>
    Foo<Set<String>> fooStr = new Foo<Set<String>>(Set.class);

WTF? We're back to square one! The compiler is actually suggesting that we send in Class<Set<String>> which we can't even do (using a class-literal, anyway)! I think this is another great example of abstraction leakage, which leads to this strange dichotomy between compile-time and runtime semantics. It even looks like what the compiler is suggesting you do, is impossible! The non-obvious "solution", is to perform some extremely-ugly typecasts:

//my eyes!!!
Foo<Set<String>> fooSetString = new Foo((Class<Set<String>>) (Class<?>) Set.class);

Why does this happen? This is because a Class instance is a poor substitute for what you actually want to communicate: the type. Due to type-erasure, at runtime there is no difference between, say Set<String>.class and Set<Integer>.class; they are both represented by Set.class. This is just as well, since there is only one Set class anyway. What you actually want to do is communicate the type. There are ways to do this; a popular way is to create an ad-hoc, concrete-implementation of an interface with a generic type (i.e, an anonymous class), which causes the generic type-information to be persisted. This is how the Guava library does it, and it makes it very easy to deal with issues like this.

Even still, this kind of approach is a band-aid at best. I think at some point Java should implement fully-reified generics. I understand backwards compatibility, but we're talking about compatibility with code that was written more than a decade ago. Large organizations are already pretty slow to shift to newer versions anyway, so at some point Oracle should just put a hard stop at some version and say that in future versions, there will be fully-reified generics. I don't think preserving backwards-compatibility is a good argument when it causes way too much pain to write code with good type-safety (Disclaimer: I am aware Java's type-system has a bunch of other issues, but just reifying generics will give us so much!).

1 thought on “Don’t use class literals as type-tokens”

  1. vi5in says:
    July 18, 2016 at 10:20 pm

    Don’t use class literals as type-tokens https://t.co/Jua7cRA0iC

    Reply

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Meta

  • Log in
  • Entries feed
  • Comments feed
  • WordPress.org

Archives

  • February 2023
  • April 2020
  • February 2020
  • January 2020
  • December 2019
  • November 2019
  • September 2019
  • August 2019
  • July 2019
  • June 2019
  • May 2019
  • March 2019
  • February 2019
  • January 2019
  • December 2018
  • November 2018
  • September 2018
  • August 2018
  • July 2018
  • June 2018
  • May 2018
  • April 2018
  • March 2018
  • February 2018
  • January 2018
  • December 2017
  • November 2017
  • October 2017
  • June 2017
  • March 2017
  • November 2016
  • August 2016
  • July 2016
  • June 2016
  • February 2016
  • August 2015
  • July 2014
  • June 2014
  • March 2014
  • December 2013
  • November 2013
  • September 2013
  • July 2013
  • June 2013
  • March 2013
  • February 2013
  • January 2013
  • October 2012
  • July 2012
  • June 2012
  • January 2012
  • December 2011
  • November 2011
  • October 2011
  • September 2011
  • July 2011
  • June 2011
  • May 2011
  • February 2011
  • January 2011
  • December 2010
  • November 2010
  • October 2010
  • September 2010
  • July 2010
  • June 2010
  • May 2010
  • April 2010
  • March 2010
  • January 2010
  • December 2009
  • November 2009
  • October 2009
  • September 2009
  • August 2009
  • July 2009
  • May 2009
  • April 2009
  • March 2009
  • February 2009
  • January 2009
  • December 2008
  • November 2008
  • October 2008
  • August 2008
  • March 2008
  • February 2008
  • November 2007
  • July 2007
  • June 2007
  • May 2007
  • March 2007
  • December 2006
  • October 2006
  • September 2006
  • August 2006
  • June 2006
  • April 2006
  • March 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • September 2005
  • August 2005
  • July 2005
  • June 2005
  • May 2005
  • April 2005
  • February 2005
  • October 2004
  • September 2004
  • August 2004
  • July 2004
  • June 2004
  • May 2004
  • April 2004
  • March 2004
  • February 2004
  • January 2004
  • December 2003
  • November 2003
  • October 2003
  • September 2003
  • July 2003
  • June 2003
  • May 2003
  • March 2003
  • February 2003
  • January 2003
  • December 2002
  • November 2002
  • October 2002
  • September 2002
  • August 2002
  • July 2002
  • June 2002
  • May 2002
  • April 2002
  • February 2002
  • September 2001
  • August 2001
  • April 2001
  • March 2001
  • February 2001
  • January 2001
  • December 2000
  • November 2000
  • October 2000
  • August 2000
  • July 2000
  • June 2000
  • May 2000
  • March 2000
  • January 2000
  • December 1999
  • November 1999
  • October 1999
  • September 1999
©2023 Rough Book | Built using WordPress and Responsive Blogily theme by Superb
All original content on these pages is fingerprinted and certified by Digiprove