Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic return type from a generic method

Consider a generic method definition like this:

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

Works perfectly to get any type of object until you try a generic object like List<String>. The only way I could make it work was like this:

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

But that requires an extra cast, removing the purpose of having the method. Is there a way to be able to provide List<String>.class or something similar to the method? (I know that type erasure prevents List<String> to exist but maybe there is a way to do it).

Note: With making it work I mean without having a type safety warning. I'm aware that the code would work as it is but it is not type safe.

like image 637
Juru Avatar asked Feb 17 '15 09:02

Juru


People also ask

How does a generic method differ from a generic type?

From the point of view of reflection, the difference between a generic type and an ordinary type is that a generic type has associated with it a set of type parameters (if it is a generic type definition) or type arguments (if it is a constructed type). A generic method differs from an ordinary method in the same way.

How does a generic method differ from a generic type in Java?

Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type, but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods are allowed, as well as generic class constructors.

What is generic class and generic method in Java?

Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively. Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.

Can you give an example of a generic method?

For example, classes like HashSet, ArrayList, HashMap, etc., use generics very well. There are some fundamental differences between the two approaches to generic types.


1 Answers

You can't do this in a type safe way without modifying the downstream code (context.get()) because the generic type is erased at run time. In fact, currently the clazz argument isn't being used at all: you could simply delete that argument and the method would perform identically since the cast to (T) is currently a no-op due to erasure. Your current code is not type safe even for non-genric classes.

So if you don't care about the safety, and just want to avoid having casts in the client code, you can just delete the last argument, and a suppress warnings once on the method.

If you want type safety, you can use super type tokens to keep the generic type information: Guava's TypeToken works and is easily available (IMO every Java project should use Guava already).

It will require downstream changes to your code, however - since you need to capture the type information when objects are added and check it when they come out. You didn't share the code for your Context class, so it's hard to say if that's possible, but in any case you'd want 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 generic type of all the objects in your parameter map, and check with a "generic aware cast" at run time. I didn't include Context.put() above, but you'd need to capture the type info when the parameter was added.

You still have a @SuppressWarnings("unchecked"), but here it's provably type-safe since you are maintaining the type information (in the guts of many generic classes you'll find provably safe casts, so they can't always be avoided even in correct code).

like image 88
BeeOnRope Avatar answered Oct 01 '22 22:10

BeeOnRope