Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extract class from Java generic class to satisfy compiler?

Tags:

java

generics

I am fully aware this question was asked many times, but I cannot find an answer to it. :/

I have one parametrized class:

public class MessageType<T> {
    private final Class<T> clazz;

    public MessageType(final Class<T> clazz) {
        this.clazz = clazz;
    }

    public Class<T> getClazz() {
        return clazz;
    }
}

And several static objects of this class:

static final MessageType<String> TYPE_A = new MessageType<>(String.class);
static final MessageType<Double> TYPE_B = new MessageType<>(Double.class);
static final MessageType<List<String>> PROBLEM_TYPE = new MessageType(List.class);

The problem is that I have to omit diamond operator and stick to unchecked cast of MessageType to MessageType<List<String>> in the last line.

I would like to write something like

static final MessageType<List<String>> PROBLEM_TYPE = new MessageType<>(List<String>.class);

but List<String>.class cannot be computed in runtime as, you know, type erasure. :D

➥ Is there any way to comply with the compiler, and avoid unchecked casts? (It has already costed me an hour due to my negligence and lack of attention)

like image 204
Xobotun Avatar asked Sep 11 '19 18:09

Xobotun


People also ask

Can we inherit generic class in Java?

Generics also provide type safety (ensuring that an operation is being performed on the right type of data before executing that operation). Hierarchical classifications are allowed by Inheritance. Superclass is a class that is inherited. The subclass is a class that does inherit.

How do I get a class instance of generic type T?

The short answer is, that there is no way to find out the runtime type of generic type parameters in Java. A solution to this is to pass the Class of the type parameter into the constructor of the generic type, e.g.

Do generics prevent type cast errors?

Implementing generics into your code can greatly improve its overall quality by preventing unprecedented runtime errors involving data types and typecasting.


2 Answers

You can avoid unnecessary casts! See below:

public static class MessageType<T> {
    static MessageType<List<String>> STRINGS_LIST_TYPE1 = new MessageType<>(List.class);
    static MessageType<List<String>> STRINGS_LIST_TYPE2 = new MessageType<>();
    static MessageType<Double> DOUBLE_TYPE = new MessageType<>(Double.class);
    static MessageType<String> STRING_TYPE = new MessageType<>(String.class);

    List<T> messages = new ArrayList<>();
    Class<T> clazz;

    @SuppressWarnings({"rawtypes","unchecked"})
    MessageType(Class<? extends T> clazz) { this.clazz = (Class) clazz; }

    MessageType() { this.clazz = null; }

    void addMessage(T message) { messages.add(message); }
    T getMessage(int i) { return messages.get(i); }
    Class<T> getClazz() { return this.clazz; }

    public static void main(String[] args) {
        // MessageType<List<String>> STRINGS_LIST_TYPE1 = new MessageType<>(List.class);
        STRINGS_LIST_TYPE1.addMessage(new ArrayList<String>()); // no compile issues
        STRINGS_LIST_TYPE1.addMessage(new ArrayList<Integer>()); // compile error "not applicable  for the arguments (ArrayList<Integer>)"
        List<String> message1 = STRINGS_LIST_TYPE1.getMessage(0); // no warnings!
        Class<List<String>> clazz1 = STRINGS_LIST_TYPE1.getClazz(); // no warnings!

        // MessageType<List<String>> STRINGS_LIST_TYPE2 = new MessageType<>()
        STRINGS_LIST_TYPE2.addMessage(new ArrayList<String>()); // no compile issues
        STRINGS_LIST_TYPE2.addMessage(new ArrayList<Integer>()); // compile error "not applicable  for the arguments (ArrayList<Integer>)"
        List<String> message2 = STRINGS_LIST_TYPE2.getMessage(0); // no warnings!
        Class<List<String>> clazz2 = STRINGS_LIST_TYPE2.getClazz(); // no warnings! but returns null
    }
}
like image 122
xtratic - Reinstate Monica Avatar answered Nov 01 '22 04:11

xtratic - Reinstate Monica


You can't avoid the unchecked cast here; List<String> is not a Class<?> but a ParameterizedType (both extend/implement Type but have no further common parent). You can store and use the type information of List<String> (with an ParameterizedType). But there will always be some unchecked cast to get back to List<String>.

There are some options for workarounds. Most are tailored to the actual usecase.

You can move the unchecked cast into MessageType:

public <T> static MessageType<List<T>> createListMessage(Class<T> innerType){
    return (MessageType<List<T>>) (Object) new MessageType(List.class);
}

If you need Class to create a new Instance. Provide a Supplier<T>.

   private final Supplier<T> supplier;
   public MessageType(Supplier<T> supplier){
       this.supplier = supplier;
   }
   public T newType(){
      return supplier.get();
   }

   MessageType<String> STRINGS = new MessageType(String::new);
   MessageType<List<String>> STRING_LIST = new MessageType(ArrayList::new);

If you need it for serialization a Supertype-Token is an option.

like image 23
k5_ Avatar answered Nov 01 '22 05:11

k5_