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

13

104

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?
Unripe answered 18/12, 2009 at 11:54 Comment(5)
Would this mean that you could get the class of a generic type at runtime? (if so, I have an example!)Lorusso
I think most of the desire with reifiable generics are from people who use generics primarily with collections, and want those collections to behave more like arrays.Extraction
The more interesting question (to me): what would it take to implement C++ style generics in Java? It certainly seems do-able in the runtime, but would break all existing classloaders (because findClass() would have to ignore parameterization, but defineClass() couldn't). And as we know, The Powers That Be hold backwards compatibility paramount.Extraction
Actually, Java does provide reified generics in a very restricted way. I provide more details in this SO thread: #880355Boutwell
The JavaOne Keynote indicates that Java 9 will support reification.Trow
E
81

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:

Eupheemia answered 18/12, 2009 at 12:5 Comment(23)
This works (for example) in C#? How do you know that T has a default constructor?Newsworthy
Yes, it do. And this is just a basic example (let assume that we work with javabeans). The whole point is that with Java you cannot get the class during runtime by T.class or T.getClass(), so that you could access all its fields, constructors and methods. It makes construction also impossible.Eupheemia
This would use reflection APIs: T.class.newInstance(). Otherwise you would have to add the type information for T. It has to be a class with such and such constructor.Newsworthy
This seems really weak to me as the "big problem", particularly as this is only likely to be useful in conjunction with some very brittle reflection around constructors/parameters etc.Unripe
No, there are no workarounds for this particular issue. Those workarounds only work if the parameterized type is not a generic type. E.g. you can extract java.lang.String from List<String> field, but not from List<T> class.Eupheemia
@Eupheemia - Super type tokens (new Type<List<String>>(){}) are a workaround to transport generic type information and there are Scala's Manifest.Newsworthy
@Thomas: this workaround require that you know the type during build/compile. In some cases you just don't know.Eupheemia
@Eupheemia - That's the same for reified types. The type information has to be there at compile time.Newsworthy
Back to the starting point: new T() does not compile in C#, right?Newsworthy
It does compile in C# provided that you declare the type as: public class Foo<T> where T : new(). Which will limit the valid types of T to those that contain a parameterless constructor.Surrey
Would using t.clone() be of any use here?Dithyrambic
What would you be cloning? ...For me this comes up when say initializing a wrapper around something like Number. Anything the user passes as the parameterized type has a default value (new Integer(), new Double(), etc.) but to have the wrapper initialized with it then I have to let them provide it on the constructor. Ugly.Litmus
Always when I choose the "workaround" solution I ended up providing the "delegator" solution. Calling the default constructor just doesn't scale to "special needs".Gilgai
Don't underestimate how badly java templates are hamstrung. It's quite a bit worse than described. One can't even ask general questions about type. For instance it's a small stretch of the imagination that one might like to detect whether variables are of type, say Float. Reasonable tricks that you could play to help you out are also off limits, e.g. you can't tell java that a given generic type is required to extend more than one class or interface. Throw in other limitations such as that static member variables can't be of generic types and try to do something practical and general...Exemplificative
@Colin You actually can tell java that a given generic type is required to extend more than one class or interface. See the "Multiple Bounds" section of docs.oracle.com/javase/tutorial/java/generics/bounded.html. (Of course, you can't tell it that the object will be one of a specific set that do not share methods that could be abstracted into an interface, which could be somewhat implemented in C++ using template specialization.)Buskin
@Buskin Thank you, good point. I stand corrected. btw Is there a way to tell java that a generic type used in class A is the same as the generic type in class B? (without using inner classes)Exemplificative
@Colin Yes, if you supply the class objects of the types in question when instantiating the classes. #52082Buskin
@Buskin As far as I can tell the compiler can't determine that template variable E in class A is of the same type as the template variable T in class B, so the compiler won't allow passing E and T between classes A and B.Exemplificative
@Colin Which is why I suggested using class objects (e.g. Class<E> and Class<T>). Not a perfect solution, especially because that wouldn't work if E and/or T were generics themselves due to type erasure and you'd have to be handling a lot of stuff as Objects, but you don't have to worry about the actual type of a class object when doing a comparison as a class's class object will be the same object for all instances (they're singletons, basically).Buskin
@Buskin I am surprised the compiler will accept that solution. If that works then in some sense you may as well use Object pointers as type safety is out the window. Anyway, interesting.Exemplificative
@Colin That's how things were done before generics. Objects obtained from a Java collection would be of type Object and you'd have to cast them to the desired type, but you'd have to be careful because you could also add any type of object to a collection without needing to cast. That is, the old style of collections (also called "raw" collections) are roughly equivalent to a generic version with Object as its type parameter.Buskin
Note that with this method, you should be able to use the cast method of the class objects to cast the objects to their desired types at runtime and call methods of them (but be careful about supplying those cast objects to methods expecting specific types for parameters, as that is determined at compile-time rather than runtime, so you may get unexpected behavior with overloaded methods).Buskin
Very professional communication indeed. And good day to you, too.Misleading
G
102

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) { ... }
Gabbro answered 18/12, 2009 at 12:35 Comment(11)
There is absolutely no need for reification to be able to do that. Method selection is done at compile time when the compile-time type information is available.Schlock
@Tom: this doesn't even compile because of type erasure. Both get compiled as public void my_method(List input) {}. I have however never came across this need, simply because they would not have the same name. If they have the same name, I'd question if public <T extends Object> void my_method(List<T> input) {} isn't a better idea.Eupheemia
Hm, I would tend to avoid overloading with identical number of parameters altogether, and prefer something like myStringsMethod(List<String> input) and myIntegersMethod(List<Integer> input) even if overloading for such a case was possible in Java.Lengthways
@Fabian: Which means you've got to have separate code, and prevent the sort of advantages you get from <algorithm> in C++.Ibadan
I am confused, this is essentially the same as my second point, yet I got 2 downvotes on it? Anybody care to enlighten me?Oxbow
@rsp: I didn't downvote you but, even with you saying your point was the same as mine, I still can't read it that way. What you seemed to say was that you want to be able to use List<Integer> as List<Object>. What I'm saying is that I want to be able to dynamic dispatch based on the generic type. For example, if I wanted to have debug routines that printed out the contents of the List, I could have one for generic Objects, and then one for each more specific types that I know I want to display differently... but have the name the same for all of them.Gabbro
@RHSeeger, thanks for your answer. I see we did mean different things, your point is about getting Java generics to be more like C++ template behaviour while mine is about using inheritance rules between container contents.Oxbow
@Eupheemia Old comment, but isn't T extends Object redundant since everything extends object and List<T> is the same as List<?>?Bevatron
@Bevatron the first part of your statement is correct, but the latter is not. List<T> means "List holding objects of the class represented by generic parameter T", while List<?> means "List holding objects of an unknown type". docs.oracle.com/javase/tutorial/extra/generics/wildcards.htmlBuskin
@Buskin I meant to say that due to type erasure, the symbol T does not really mean anything within the method. Is there a case where accepting a List<T> to a method (where T can be anything) is better than accepting a List<?>?Bevatron
@Bevatron When the T is defined by the enclosing type or the method itself is a generic. You use ? in non-generic methods/methods where the compiler will not be able to determine the type from the compile-time context.Buskin
E
81

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:

Eupheemia answered 18/12, 2009 at 12:5 Comment(23)
This works (for example) in C#? How do you know that T has a default constructor?Newsworthy
Yes, it do. And this is just a basic example (let assume that we work with javabeans). The whole point is that with Java you cannot get the class during runtime by T.class or T.getClass(), so that you could access all its fields, constructors and methods. It makes construction also impossible.Eupheemia
This would use reflection APIs: T.class.newInstance(). Otherwise you would have to add the type information for T. It has to be a class with such and such constructor.Newsworthy
This seems really weak to me as the "big problem", particularly as this is only likely to be useful in conjunction with some very brittle reflection around constructors/parameters etc.Unripe
No, there are no workarounds for this particular issue. Those workarounds only work if the parameterized type is not a generic type. E.g. you can extract java.lang.String from List<String> field, but not from List<T> class.Eupheemia
@Eupheemia - Super type tokens (new Type<List<String>>(){}) are a workaround to transport generic type information and there are Scala's Manifest.Newsworthy
@Thomas: this workaround require that you know the type during build/compile. In some cases you just don't know.Eupheemia
@Eupheemia - That's the same for reified types. The type information has to be there at compile time.Newsworthy
Back to the starting point: new T() does not compile in C#, right?Newsworthy
It does compile in C# provided that you declare the type as: public class Foo<T> where T : new(). Which will limit the valid types of T to those that contain a parameterless constructor.Surrey
Would using t.clone() be of any use here?Dithyrambic
What would you be cloning? ...For me this comes up when say initializing a wrapper around something like Number. Anything the user passes as the parameterized type has a default value (new Integer(), new Double(), etc.) but to have the wrapper initialized with it then I have to let them provide it on the constructor. Ugly.Litmus
Always when I choose the "workaround" solution I ended up providing the "delegator" solution. Calling the default constructor just doesn't scale to "special needs".Gilgai
Don't underestimate how badly java templates are hamstrung. It's quite a bit worse than described. One can't even ask general questions about type. For instance it's a small stretch of the imagination that one might like to detect whether variables are of type, say Float. Reasonable tricks that you could play to help you out are also off limits, e.g. you can't tell java that a given generic type is required to extend more than one class or interface. Throw in other limitations such as that static member variables can't be of generic types and try to do something practical and general...Exemplificative
@Colin You actually can tell java that a given generic type is required to extend more than one class or interface. See the "Multiple Bounds" section of docs.oracle.com/javase/tutorial/java/generics/bounded.html. (Of course, you can't tell it that the object will be one of a specific set that do not share methods that could be abstracted into an interface, which could be somewhat implemented in C++ using template specialization.)Buskin
@Buskin Thank you, good point. I stand corrected. btw Is there a way to tell java that a generic type used in class A is the same as the generic type in class B? (without using inner classes)Exemplificative
@Colin Yes, if you supply the class objects of the types in question when instantiating the classes. #52082Buskin
@Buskin As far as I can tell the compiler can't determine that template variable E in class A is of the same type as the template variable T in class B, so the compiler won't allow passing E and T between classes A and B.Exemplificative
@Colin Which is why I suggested using class objects (e.g. Class<E> and Class<T>). Not a perfect solution, especially because that wouldn't work if E and/or T were generics themselves due to type erasure and you'd have to be handling a lot of stuff as Objects, but you don't have to worry about the actual type of a class object when doing a comparison as a class's class object will be the same object for all instances (they're singletons, basically).Buskin
@Buskin I am surprised the compiler will accept that solution. If that works then in some sense you may as well use Object pointers as type safety is out the window. Anyway, interesting.Exemplificative
@Colin That's how things were done before generics. Objects obtained from a Java collection would be of type Object and you'd have to cast them to the desired type, but you'd have to be careful because you could also add any type of object to a collection without needing to cast. That is, the old style of collections (also called "raw" collections) are roughly equivalent to a generic version with Object as its type parameter.Buskin
Note that with this method, you should be able to use the cast method of the class objects to cast the objects to their desired types at runtime and call methods of them (but be careful about supplying those cast objects to methods expecting specific types for parameters, as that is determined at compile-time rather than runtime, so you may get unexpected behavior with overloaded methods).Buskin
Very professional communication indeed. And good day to you, too.Misleading
O
35

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).

Opportuna answered 18/12, 2009 at 12:33 Comment(7)
Yes - I have a way around this in that I create a ParametrizedList which copies the data in the source collection checking types. It'sa bit like Collections.checkedList but can be seeded with a collection to start with.Unripe
@tackline - well, a few abstractions would leak less. If you need access to type metadata in your implementation, the external interface will tell on you because clients need to send you a class object.Opportuna
... meaning that with reified generics, you could add stuff like T.class.getAnnotation(MyAnnotation.class) (where T is a generic type) without changing the external interface.Opportuna
@gustafc: if you think that C++ templates give you complete type safety, read this: kdgregory.com/index.php?page=java.generics.cppExtraction
@kdgregory: I never said that C++ was 100% type safe - just that erasure damages type safety. As you say yourself, "C++, it turns out, has its own form of type erasure, known as the C-style pointer cast." But Java only does dynamic casts (not reinterpreting), so reification would plug this whole in the type system.Opportuna
Been there - gone all the way from "lets put a thing", to "lets put a list of things" to "oh noes, I need more than one type of thing". Always end up building a wrapper around the session to handle type casts (a la "spring recommended" style - don't mix cache/factory code with your real code).Gilgai
I'm a bit late to the party, but does it really improve type safety? Your program will crash sooner, but it still crashes at runtime.Salot
L
27

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

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
Laevorotation answered 18/12, 2009 at 14:39 Comment(4)
what's wrong with Object? An array of objects is array of references anyway. It's not like the object data is sitting on the stack - it's all in the heap.Gilgai
Type safety. I can put whatever I want in Object[], but only Strings in String[].Laevorotation
Ran: Without being sarcastic: You might like using a scripting language instead of Java, then you have the flexibility of untyped variables anywhere!Synchro
Arrays are covariant (and hence not typesafe) in both languages. String[] strings = new String[1]; Object[] objects = strings; objects[0] = new Object(); Compiles fine in both languages. Runs notsofine.Lydon
M
18

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.
Mcreynolds answered 26/3, 2014 at 16:55 Comment(2)
Mostly I find it hard with serialization cases. You often would like to be able to sniff out the class types of generic things getting serialized but you are stopped short because of type erasure. It makes it hard to do something like this deserialize(thingy, List<Integer>.class)Ribaldry
I think this is the best answer. Especially parts describing what issues are really fundametally due to type erasure and what are just Java language design problems. The first thing I tell people starting erasure-whining is that Scala and Haskell are working by type erasure too.Lizzielizzy
R
15

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.

Ribaldry answered 6/8, 2014 at 23:7 Comment(1)
This. A thousand times this. Every time I try to write deserialization code in Java I spend the day lamenting that I'm not working in something elseZofiazoha
B
11

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.

Bolt answered 18/12, 2009 at 18:18 Comment(4)
It's a valid point but I'm not quite sure why an exception class so parametrized would be useful. Could you modify your answer to contain a brief example of when this might be useful?Unripe
@oxbow_lakes:Sorry My knowledge of Java Generics is quite limited and I am making an attempt to improve upon it. So now I am not able to think of any example where parametrized exception could be useful. Will try to think about it. Thx.Bolt
It could act as a substitute for multiple inheritance of exception types.Obtect
The performance improvment is that currently, a type parameter must inherit from Object, requiring boxing of primitive types, which imposes an execution and memory overhead.Obtect
R
8

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

Recombination answered 18/12, 2009 at 12:0 Comment(3)
Sure, but they would still have problems. List<String> is not a List<Object>.Schlock
Agree - but only for primitives (Integer, Long, etc). For "regular" Object, this is the same. Since primitives can't be a parameterized type (a far more serious issue, at least IMHO), I don't see this as a real pain.Gilgai
The problem with arrays is their covariance, nothing to do with reification.Constrain
L
5

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.

Lorusso answered 18/12, 2009 at 12:9 Comment(1)
But passing in the class seems safer all round to me. In any case, reflectively creating instances from database queries is extremely brittle to changes such as refactoring anyway (in both your code and the database). I guess I was looking for things whereby it is just not possible to provide the classUnripe
I
5

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.

Inebriety answered 18/12, 2009 at 20:51 Comment(1)
This essentially boils down to being able to do new T[] where T is of primitive type!Unripe
Y
3

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.

Y answered 18/12, 2009 at 12:2 Comment(1)
This is certainly what generics are in Java. In C# and other languages they're a powerful toolZofiazoha
A
1

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!!
Antinucleon answered 19/8, 2017 at 6:26 Comment(0)
F
0

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.

Fishbolt answered 16/10, 2011 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.