Home > other >  How to avoid using a raw type as a generic parameter when Class<T> is needed
How to avoid using a raw type as a generic parameter when Class<T> is needed

Time:11-24

I have an abstract class that looks like this:

abstract class Handler<T> {

    Handler(Class<T> clazz) {
        // ...
    }

    abstract void handle(T object);

}

I'm trying to extent it, where T is a type with a wildcard generic parameter (for the sake of the example, say List<?>). What I want to be able to do is something like:

class MyHandler extends Handler<List<?>> {
    MyHandler() {
        super(List.class); 
     // ^ Compiler error: The constructor Handler<List<?>>(Class<List>) is undefined
     // super(List<?>.class); is also a compiler error
    }

    void handle(List<?> object) {
        // ...
    }
}

As far as I can tell the above is totally safe, so I'm not sure why the compiler doesn't allow it. My current solution involves the use of raw types, unsafe casting and suppression of the warnings and seems like it can't be solution the language intends me to use:

class MyHandler extends Handler<List> { // Warning: List is a raw type
    MyHandler() {
        super(List.class); 
    }

    void handle(List objectRaw) { // Warning: List is a raw type
        List<?> object = (List<?>) objectRaw;
        // ...
    }
}

This needs to be a singleton so I can't add generic parameters to MyHandler. How do I avoid all these bad practices (raw types and the cast)? Theres no reason this should be unsafe and I'm having a hard time believing there's no way to do this in Java.

CodePudding user response:

You can solve the problem by casting the class object to type Class<List<?>> in the MyHandler constructor:

MyHandler() {
    super((Class<List<?>>) (Object) List.class); 
}

The problem

The problem is that there is no simple way in Java to express a class literal of type Class<List<?>>. List.class has type Class<List>.

I think there are situations where it would not be safe to the second type to the first one, but in your code there is probable no problem at all.

If, for example, your code uses clazz to check the runtime type of objects using isAssignableFrom you might get it trouble.

Type token

If you use some type-token implementation instead of a java.lang.Class, for example the one in Guava, you will be able to express a literal of the exact right type:

abstract class Handler<T> {
    Handler(TypeToken<T> clazz) {
        // ...
    }

    abstract void handle(T object);
}

class MyHandler extends Handler<List<?>> {
    MyHandler() {
        super(new TypeToken<List<?>>() {}); 
    }

    void handle(List<?> object) {
        // ...
    }
}

CodePudding user response:

You don't need to pass a class object to the Handler constructor.

abstract class Handler<T> {
    final Class<T> clazz;

    @SuppressWarnings("unchecked")
    Handler(T... dummy) {
        if (dummy == null || dummy.length > 0)
            throw new IllegalArgumentException("Do not specify the 'dummy' argument");
        clazz = (Class<T>) dummy.getClass().componentType();
        System.out.println("Handler clazz="   clazz);
    }

    abstract void handle(T object);

}

class MyHandler extends Handler<List<Integer>> {
    void handle(List<Integer> object) {
        // ...
    }
}

and

MyHandler mh = new MyHandler();

output

Handler clazz=interface java.util.List

You can also implement anonymous inner classes.

Handler<String> h = new Handler<>() {
    @Override
    void handle(String object) {
        // TODO Auto-generated method stub
    }
};

output

Handler clazz=class java.lang.String
  • Related