Why should I care that Java doesn't have reified generics?

12.6k views Asked by At

This came up as a question I asked in an interview recently as something the candidate wished to see added to the Java language. It's commonly-identified as a pain that Java doesn't have reified generics but, when pushed, the candidate couldn't actually tell me the sort of things that he could have achieved were they there.

Obviously because raw types are allowable in Java (and unsafe checks), it is possible to subvert generics and end up with a List<Integer> that (for example) actually contains Strings. This clearly could be rendered impossible were type information reified; but there must be more than this!

Could people post examples of things that they would really want to do, were reified generics available? I mean, obviously you could get the type of a List at runtime - but what would you do with it?

public <T> void foo(List<T> l) {
   if (l.getGenericType() == Integer.class) {
       //yeah baby! err, what now?

EDIT: A quick update to this as the answers seem mainly to be concerned about the need to pass in a Class as a parameter (for example EnumSet.noneOf(TimeUnit.class)). I was looking more for something along the lines of where this just isn't possible. For example:

List<?> l1 = api.gimmeAList();
List<?> l2 = api.gimmeAnotherList();

if (l1.getGenericType().isAssignableFrom(l2.getGenericType())) {
    l1.addAll(l2); //why on earth would I be doing this anyway?
13

There are 13 answers

23
BalusC On BEST ANSWER

From the few times that I came across this "need", it ultimately boils down to this construct:

public class Foo<T> {

    private T t;

    public Foo() {
        this.t = new T(); // Help?
    }

}

This does work in C# assuming that T has a default constructor. You can even get the runtime type by typeof(T) and get the constructors by Type.GetConstructor().

The common Java solution would be to pass the Class<T> as argument.

public class Foo<T> {

    private T t;

    public Foo(Class<T> cls) throws Exception {
        this.t = cls.newInstance();
    }

}

(it does not necessarily need to be passed as constructor argument, as a method argument is also fine, the above is just an example, also the try-catch is omitted for brevity)

For all other generic type constructs, the actual type can easily be resolved with a bit help of reflection. The below Q&A illustrate the use cases and possibilities:

0
Adam On

Here's one that's caught me today: without reification, if you write a method that accepts a varargs list of generic items ... callers can THINK they're typesafe, but accidentally pass in any-old crud, and blow up your method.

Seems unlikely that would happen? ... Sure, until ... you use Class as your datatype. At this point, your caller will happily send you lots of Class objects, but a simple typo will send you Class objects that don't adhere to T, and disaster strikes.

(NB: I may have made a mistake here, but googling around "generics varargs", the above appears to be just what you'd expect. The thing that makes this a practical problem is the use of Class, I think - callers seem to be less careful :( )


For instance, I'm using a paradigm that uses Class objects as a key in maps (it's more complex than a simple map - but conceptually that's what's going on).

e.g. this works great in Java Generics (trivial example) :

public <T extends Component> Set<UUID> getEntitiesPossessingComponent( Class<T> componentType)
    {
        // find the entities that are mapped (somehow) from that class. Very type-safe
    }

e.g. without reification in Java Generics, this one accepts ANY "Class" object. And it's only a tiny extension of the previous code :

public <T extends Component> Set<UUID> getEntitiesPossessingComponents( Class<T>... componentType )
    {
        // find the entities that are mapped (somehow) to ALL of those classes
    }

The above methods have to be written out thousands of times in an individual project - so the possibility for human error becomes high. Debugging mistakes is proving "not fun". I'm currently trying to find an alternative, but don't hold much hope.

4
sateesh On

My exposure to Java Geneircs is quite limited, and apart from the points other answers have already mentioned there is a scenario explained in the book Java Generics and Collections, by Maurice Naftalin and Philip Walder, where the reified generics are useful.

Since the types are not reifiable, it is not possible to have Parameterized exceptions.

For example the declaration of below form is not valid.

class ParametericException<T> extends Exception // compile error

This is because the catch clause checks whether the thrown exception matches a given type. This check is same as the check performed by instance test and since the type is not reifiable the above form of statement is invalid.

If the above code was valid then exception handling in the below manner would have been possible:

try {
     throw new ParametericException<Integer>(42);
} catch (ParametericException<Integer> e) { // compile error
  ...
}

The book also mentions that if Java generics are defined similar to the way C++ templates are defined (expansion) it may lead to more efficient implementation as this offers more opportunities for optimization. But doesn't offer any explanation more than this, so any explanation (pointers) from the knowledgeable folks would be helpful.

1
kvb On

One nice thing would be avoiding boxing for primitive (value) types. This is somewhat related to the array complaint that others have raised, and in cases where memory use is constrained it could actually make a significant difference.

There are also several types of problems when writing a framework where being able to reflect over the parameterized type is important. Of course this can be worked around by passing a class object around at runtime, but this obscures the API and places an additional burden on the user of the framework.

3
Hank Gay On

Arrays would probably play much nicer with generics if they were reified.

1
Bozho On

It's not that you will achieve anything extraordinary. It will just be simpler to understand. Type erasure seems like a hard time for beginners, and it ultimately requires one's understanding on the way the compiler works.

My opinion is, that generics are simply an extra that saves a lot of redundant casting.

1
James B On

I have a wrapper that presents a jdbc resultset as an iterator, (it means I can unit test database-originated operations a lot easier through dependency injection).

The API looks like Iterator<T> where T is some type that can be constructed using only strings in the constructor. The Iterator then looks at the strings being returned from the sql query and then tries to match it to a constructor of type T.

In the current way that generics are implemented, I have to also pass in the class of the objects that I will be creating from my resultset. If I understand correctly, if generics were reified, I could just call T.getClass() get its constructors, and then not have to cast the result of Class.newInstance(), which would be far neater.

Basically, I think it makes writing APIs (as opposed to just writing an application) easier, because you can infer a lot more from objects, and thereby less configuration will be necessary...I didn't appreciate the implications of annotations until I saw them being used in things like spring or xstream instead of reams of config.

7
gustafc On

Type safety comes to mind. Downcasting to a parametrized type will always be unsafe without reified generics:

List<String> myFriends = new ArrayList();
myFriends.add("Alice");
getSession().put("friends", myFriends);
// later, elsewhere
List<Friend> myFriends = (List<Friend>) getSession().get("friends");
myFriends.add(new Friend("Bob")); // works like a charm!
// and so...
List<String> myFriends = (List<String>) getSession().get("friends");
for (String friend : myFriends) print(friend); // ClassCastException, wtf!? 

Also, abstractions would leak less - at least the ones which may be interested in runtime information about their type parameters. Today, if you need any kind of runtime information about the type of one of the generic parameters you have to pass its Class along as well. That way, your external interface depends on your implementation (whether you use RTTI about your parameters or not).

11
RHSeeger On

The thing that most commonly bites me is the inability to take advantage of multiple dispatch across multiple generic types. The following isn't possible and there are many cases where it would be the best solution:

public void my_method(List<String> input) { ... }
public void my_method(List<Integer> input) { ... }
1
Cogman On

Serialization would be more straightforward with reification. What we would want is

deserialize(thingy, List<Integer>.class);

What we have to do is

deserialize(thing, new TypeReference<List<Integer>>(){});

looks ugly and works funkily.

There are also cases where it would be really helpful to say something like

public <T> void doThings(List<T> thingy) {
    if (T instanceof Q)
      doCrazyness();
  }

These things don't bite often, but they do bite when they happen.

2
user2684301 On

This is an old question, there are a ton of answers, but I think that the existing answers are off the mark.

"reified" just means real and usually just means the opposite of type erasure.

The big problem related to Java Generics:

  • This horrible boxing requirement and disconnect between primitives and reference types. This isn't directly related to reification or type erasure. C#/Scala fix this.
  • No self types. JavaFX 8 had to remove "builders" for this reason. Absolutely nothing to do with type erasure. Scala fixes this, not sure about C#.
  • No declaration side type variance. C# 4.0/Scala have this. Absolutely nothing to do with type erasure.
  • Can't overload void method(List<A> l) and method(List<B> l). This is due to type erasure but is extremely petty.
  • No support for runtime type reflection. This is the heart of type erasure. If you like super advanced compilers that verify and prove as much of your program logic at compile time, you should use reflection as little as possible and this type of type erasure shouldn't bother you. If you like more patchy, scripty, dynamic type programming and don't care so much about a compiler proving as much of your logic correct as possible, then you want better reflection and fixing type erasure is important.
0
ExCodeCowboy On

Something that all the answers here have missed that is constantly a headache for me is since the types are erased, you cannot inherit a generic interface twice. This can be a problem when you want to make fine grained interfaces.

    public interface Service<KEY,VALUE> {
           VALUE get(KEY key);
    }

    public class PersonService implements Service<Long, Person>,
        Service<String, Person> //Can not do!!
4
Turnor On

You'd be able to create generic arrays in your code.

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}