tags:

views:

459

answers:

5

Here's a Java generic pattern:

public <T> T getResultData(Class<T> resultClass, other_args) { 
   ...
   return resultClass.cast(T-thing);
}

A typical call looks like:

   DoubleBuffer buffer;
   buffer = thing.getResultData(DoubleBuffer.class, args);

I've never been able to figure out how to use this pattern cleanly when the desired return type is, itself, generic. To be 'concrete', what if a function like this wants to return Map<String,String>? Since you can't get a class object for a generic, of course, the only option would be to pass Map.class, and then you need a cast and an @SuppressWarning after all.

One ends up with a call like:

Map<String, String> returnedMap;
returnedMap = thing.getResultData(Map.class, some_other_args);

Now one is back to needing casts and suppressing a warning.

I suppose that one could take something from the java.lang.reflect.Type family instead of the Class, but those aren't especially easy to concoct. That looks like:

class Dummy {
 Map<String, String> field;
}

...

Type typeObject = Dummy.class.getField("field").getGenericType();

Given this, the called function could extract the base type and use that for casting or newInstance-ing, but the dummy field business sure looks ugly.

Note that functions like this are not always calling newInstance. Obviously, if they do, they don't need to call resultClass.cast.

+1  A: 

You can use TypeLiteral if you want to use typesafe container classes. They are used in Guice to provide typesafe bindings. See Guice Source code for more examples and TypeLiteral class.

By the way, ou don't need cast, if you call resultClass.newInstance() for instance.

public <T> T getResultData(Class<T> resultClass, other_args) {       
   ...
   return resultClass.newInstance();
}
Chandra Patni
Except you can't obtain and thus pass Class instance for generic class like `Map<String, String>` so this won't help at all with OP's question.
ChssPly76
Yup. That's exactly how we got here.
bmargulies
They are not supported in the Language but you use TypeLiteral, invented by Neal Gafter. http://gafter.blogspot.com/2006/12/type-literals.html
Chandra Patni
I'd upvote the TypeLiteral reference if you edited it into your actual answer.
bmargulies
Thank you bmargulies.
Chandra Patni
+1  A: 

So if I understood correctly what you really trying to do is (illegal pseudo-syntax):

public <T, S, V> T<S, V> getResultData(Class<T<S, V>> resultClass, other_args) { 
  ...
  return resultClass.cast(T-thing);
}

You can't because you can't further parameterize generic type T. Messing about with java.lang.reflect.* won't help:

  1. There is no such thing as Class<Map<String, String>> and thus no way to dynamically create its instance.
  2. There is no way to actually declare the method in question as returning T<S, V>
ChssPly76
@ChssPly76: As it happens, there is a GenericType object for Map<String, String>. If you have a field of type Map<String, String>, and you call Field.getGenericType, you will get it. However, about the only way to acquire one of those is, indeed, to get it from reflection on a field or return type.
bmargulies
You mean `ParameterizedType`. Yes, there is and yes, you can obtain it from field / method declaration. And no, it's **not** the same as `Class` and thus will **not** help you in any way: you can't cast to it and you can't create an instance of corresponding class using just `ParameterizedType` either.
ChssPly76
Well, actually, this isn't as impossible as it looks. The called function can call getRawType() on a ParameterizedType for newInstance/cast purposes. It's just ugly, but it looks like that's as good as it gets.
bmargulies
+1  A: 

Well,. here's an ugly (partial) solution. You can't generically paramaterize a generic param, so you can't write

public <T<U,V>> T getResultData ...

but you can return a parameterized collection, e.g. for a set

public < T extends Set< U >, U > T getResultData( Class< T > resultClass, Class< U > paramClass ) throws IllegalAccessException, InstantiationException {
     return resultClass.newInstance();
    }

where you could get rid of the U-param if it existed in the class signature.

Steve B.
Interesting, and it will work for two parameters as well (by extending `Map`). But, honestly, choosing between this and `@SuppressWarnings` I'd take the latter every time :-)
ChssPly76
I agree - I did say "ugly" ;)
Steve B.
+4  A: 

You cannot do that in the standard Java API. However, there are utility classes available which can get the Type out of a generic class. In for example Google Gson (which converts JSON to fullworthy Javabeans and vice versa) there's a TypeToken class. You can use it as follows:

List<Person> persons = new Gson().fromJson(json, new TypeToken<List<Person>>() {}.getType());

Such a construct is exactly what you need. You can find here the source sode, you may find it useful as well. It only requires an additional getResultData() method which can accept a Type.

BalusC
A: 

Yeah I don't think this is possible.

Imagine this scenario. You have a file with a serialized object that you want to read it. If you make the function generic, you'll be able to use it without a cast. But lets say you have a Map serialized into a file.

When you deserialize it, there's no way to know what the original generic map type was. As far as java is concerned, it's just a Map

I ran into this with lists, and it was annoying. But what I actually ended up doing was creating a generic "converting collection" class that takes a collection and a "converter" (which can be an anonymous inner type).

Then when you iterate through the converting collection, every item gets cast. And that solved the warning problem. It's a lot of work just to get rid of the warning, but I don't think that was the main reason I came up with that framework. You can also do more powerful things like take a collection of filenames, and then write a converter that loads the data in the file, or does a query, or whatever.

Chad Okere