Generic return type from generic method

Consider the definition of a general method as follows:

public static <T> T getParameter(final Context context, final ContextParameter contextParameter, final Class<T> clazz) { return (T) context.get(contextParameter.name()); } 

Works well to get an object of any type until you try a generic object like List <String>. The only way to make it work:

  final List<String> fileNames = (List<String>) ContextUtils.getParameter(context, ContextParameter.EOF_FILE_NAMES_TO_ZIP, List.class); 

But this requires additional casting, excluding the purpose of using the method. Is there a way to provide a List<String>.class or something similar to a method? (I know that erasing a type prevents the existence of a List<String> , but there may be a way to do this).

Note. With it, I mean without warning about the type of security. I know that the code will work as it is, but it is not type safe.

+8
java generics type-erasure
source share
2 answers

You cannot do this in a safe way without changing the downstream code ( context.get() ), because the generic type is erased at runtime. In fact, the clazz argument clazz not currently used at all: you can simply delete this argument and the method will execute identically, since the cast to (T) is currently no-op due to erasure. Your current code is not type safe even for non-class classes.

So, if you do not care about security and just want to avoid being cast into the client code, you can simply delete the last argument and turn off warnings for the method once.

If you want a security type, you can use super-type tokens to preserve general type information: Guava TypeToken works and is easily accessible (IMO every Java project should use Guava already).

However, your code will require changes in the opposite direction, since you need to capture the type information when adding objects and check it when exiting. You haven't shared the code for your Context class, so it's hard to say if this is possible, but in any case you need something like this:

 static class TypedParam { final TypeToken<?> type; final Object object; private TypedParam(TypeToken<?> type, Object object) { this.type = type; this.object = object; } } public static class Context { Map<String, TypedParam> params = new HashMap<>(); TypedParam get(String name) { return params.get(name); } } public static <T> T getParameter(final Context context, final String name, final TypeToken<T> type) { TypedParam param = context.get(name); if (type.isAssignableFrom(param.type)) { @SuppressWarnings("unchecked") T ret = (T)param.object; return ret; } else { throw new ClassCastException(param.type + " cannot be assigned to " + type); } } 

Basically, you know the general type of all objects in your parameter map and check with a "well-known throw" at runtime. I did not include Context.put() above, but you would need to write down the type information when adding the parameter.

You still have @SuppressWarnings("unchecked") , but here it is type safe because you maintain type information (in the guts of many generic classes you will find reliable throws so they cannot always be avoided even in the right code).

+7
source share

As you said, there is nothing like List<String>.class . Therefore, if you just want to read the parameters without castings, you can use something like this:

 public static <T> T getParameter(final Map<String, Object> context, final String name) { return (T) context.get(name); } 

I assume that your context returns Object , and you need to suppress type safety checks for this method anyway.

+1
source share

All Articles