简体   繁体   中英

Java generic function: how to return Generic type

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 .

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 .

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, eg 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.

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

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();
}

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>

If you don't really need the exact generic type of the map and your code does not rely on that type but you're simply annoyed by the warnings you could write:

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

then you can use any of the map methods that are not type-parametrized like containsKey, clear, iterator and so on or iterate over the entrySet or pass returnedMap to any generic method and everything will be type safe.

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

Object myKey = ...;

Object someValue = returnedMap.get(myKey);

for (Iterator<? extends Map.Entry<?,?>> it = returnedMap.entrySet().iterator(); it.hasNext(); )
  if (it.next().equals(myKey))
    it.remove();

for (Map.Entry<?,?> e : returnedMap.entrySet()) {
  if (e.getKey().equals(myKey))
    e.setValue(null); // if the map supports null values
}

doSomething(returnedMap);

...

<K,V> void doSomething(Map<K,V> map) { ... }

there is actually many things that you can do with a Map in a type-safe manner without knowing its key or value type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM