views:

879

answers:

10

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?
+5  A: 

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

Hank Gay
what is reification anyway?
ante.sabo
Sure, but they would still have problems. `List<String>` is not a `List<Object>`.
Tom Hawtin - tackline
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.
Ran Biron
The problem with arrays is their covariance, nothing to do with reification.
Recurse
+1  A: 

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.

Bozho
A: 

If Java generics were improved I really would like to be able to create a kind of typedef so that I can abstract from the implementation detail that is contained in List<Integer> while I would like to say I'm using a list of temperatures all over my code. (The generic notation gets ugly fast imho.)

Another improvement would be the possibility to have the compiler allow List<Integer> to be used as List<Object> for use in generic implementations taking advantage of the inheritance hierarchy for generics containers too.

Edit

What I am saying above is not that I want the types List<A> and List<B> to be interchangeable but I would like to be able to abstract from the fact that A and B are in a container, maybe an example helps.

Given the definitions:

class Shape { 
    void draw() {
         // do something
    }
}

class Circle extends Shape { 
}

class Rectangle extends Shape {
}

class Canvas {
    void draw(List<Shape> shapes) {
         // draw shapes in list
    } 
}

I would like to be able to:

Cancas canvas = new Canvas();
List<Circle> circles = new List<Circle>();
List<Rectangle> rectangles = new List<Rectangle>();

// set up and create shapes

canvas.draw(circles);
canvas.draw(rectangles);

I.e. Canvas::draw can implement functionality based on the Shape interface (polymorphism) analogous to a method draw(Shape shape) that can be called with a Circle or Rectangle object nowadays.

Of course it would be an error if the list argument would be used to add objects of the wrong type, but with reified generics that should result in an exception.

rsp
A mutable list List<Integer> is not a List<Object>. This will never be correct. Consider convariant Java arrays: `Object[] x = new String[]{"1"}; x[0] = 1;` -> java.lang.ArrayStoreException
Thomas Jung
Read-only context (using the `const` keyword?) would have many uses with regards to polymorphism. And like the arraystore exception, reified generics can catch errors equivalent to your array example.
rsp
+17  A: 

From the few times I came across this "need", it often 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().

BalusC
This works (for example) in C#? How do you know that T has a default constructor?
Thomas Jung
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.
BalusC
Well, there are workarounds for that. But they're ugly.
Bozho
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.
Thomas Jung
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.
oxbow_lakes
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.
BalusC
@BalusC - Super type tokens (new Type<List<String>>(){}) are a workaround to transport generic type information and there are Scala's Manifest.
Thomas Jung
@Thomas: this workaround require that you know the type during build/compile. In some cases you just don't know.
BalusC
@BalusC - That's the same for reified types. The type information has to be there at compile time.
Thomas Jung
Back to the starting point: new T() does not compile in C#, right?
Thomas Jung
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.
Martin Harris
Would using t.clone() be of any use here?
OscarRyz
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.
PSpeed
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".
Ran Biron
+1  A: 

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.

James B
Which, in a nutshell, is balusC's point
James B
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 class
oxbow_lakes
+9  A: 

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

gustafc
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.
oxbow_lakes
gustafc: I don't follow your second point at all.
Tom Hawtin - tackline
@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.
gustafc
... 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.
gustafc
@gustafc: if you think that C++ templates give you complete type safety, read this: http://www.kdgregory.com/index.php?page=java.generics.cpp
kdgregory
@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.
gustafc
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).
Ran Biron
+19  A: 

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) { ... }
RHSeeger
Yes - that's a really good point; this comes up a lot
oxbow_lakes
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.
Tom Hawtin - tackline
@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.
BalusC
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.
Fabian Steeg
@Fabian: Which means you've got to have separate code, and prevent the sort of advantages you get from `<algorithm>` in C++.
David Thornley
I am confused, this is essentially the same as my second point, yet I got 2 downvotes on it? Anybody care to enlighten me?
rsp
@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.
RHSeeger
@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.
rsp
+2  A: 

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

public <T> static void DoStuff() {
    T[] myArray = new T[42]; // No can do
}
Turnor
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.
Ran Biron
Type safety. I can put whatever I want in Object[], but only Strings in String[].
Turnor
+3  A: 
sateesh
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?
oxbow_lakes
Couldn't agree more.
Ran Biron
@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.
sateesh
It could act as a substitute for multiple inheritance of exception types.
meriton
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.
meriton
+2  A: 

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.

kvb
This essentially boils down to being able to do `new T[]` where T is of primitive type!
oxbow_lakes