Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there no way to distinguish different generic instantiations runtime in Java?

Tags:

java

generics

I come from a C# world and I've just learned about erasure in Java, which put me a bit off. Is there really no way to distinguish SomeGenericInstance<String> from SomeGenericInstance<Integer> runtime in Java?

I'm asking because I've implemented a super simple pub-sub framework and I wanted to have a generic class GenericMessage<T>. It's essential not to send GenericMessage<String> to listeners of GenericMessage<Integer>. I tried implementing it by having a List of key-value pairs where the key is the Class object representing the type of the message. But this code line yields true which is a problem...:

new GenericMessage<Integer>().getClass.equals(new GenericMessage<String>().getClass())

like image 608
Nilzor Avatar asked Mar 27 '13 13:03

Nilzor


3 Answers

As far as I am aware, sorry, it is simply impossible.

like image 145
Bet Lamed Avatar answered Oct 13 '22 20:10

Bet Lamed


You can access it using next trick:

public class Example<T> {

    Class<T> genericType;

    public Example(Class<T> genericType) {
        this.genericType= genericType;
    }

    public static void main(String args[]) {
        Example<Integer> ex1 = new Example<>(Integer.class);
        Example<String> ex2 = new Example<>(String.class);
        System.out.println(ex1.genericType);
        System.out.println(ex2.genericType);
    }
}

Output:

class java.lang.Integer

class java.lang.String

like image 2
bsiamionau Avatar answered Oct 13 '22 21:10

bsiamionau


You can do it using Java Reflection. Don't know if it's always a good idea, but it's surely possible. Here's an example:

public class Test{

    private List<String> list;

    public static void main(String[] args) throws Exception{
        Field field = Test.class.getDeclaredField("list");
        Field f = field.getGenericType().getClass().getDeclaredField("actualTypeArguments");
        f.setAccessible(true);
        Type[] genericTypes = (Type[]) f.get(field.getGenericType());
        System.out.println(genericTypes[0]);
    }

}

Or you can cast directly to ParameterizedType, if it seems any better to you:

public class Test{

    private List<String> list;

    public static void main(String[] args) throws Exception{
        Field field = Test.class.getDeclaredField("list");
        ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();
        Type[] actualTypes = parameterizedType.getActualTypeArguments();
        System.out.println(actualTypes[0]);
    }

}

Both examples print: class java.lang.String

Now just to leave a more complete answer, the same can be done for a Map. As you can see the getActualTypeArguments() method returns a Type[] and for a Map, the key type would be index 0, and the value type would be index 1. Example:

public class Test{

    private Map<String, Integer> map;

    public static void main(String[] args) throws Exception{
        Field mapField = Test.class.getDeclaredField("map");
        ParameterizedType mapParameterizedType = (ParameterizedType) mapField.getGenericType();
        Type[] actualMapTypes = mapParameterizedType.getActualTypeArguments();
        System.out.println(actualMapTypes[0]);
        System.out.println(actualMapTypes[1]);
    }

}

Prints:

class java.lang.String
class java.lang.Integer
like image 2
Rodrigo Sasaki Avatar answered Oct 13 '22 19:10

Rodrigo Sasaki