Sunday, October 22, 2017

Java Advanced Generics Examples Part 2 - Reflection


When a generic type information is required at runtime, it has to be passed in:


public class GenericClass<T> {

     private final Class<T> type;

     public GenericClass(Class<T> type) {
          this.type = type;
     }

     public Class<T> getMyType() {
         return this.type;
     }
}

Java Generics is not reified. The type information is erased from Java runtime, however declared parameter types are available from Java reflection. The parameter type information of a field, a return type and types of parameters of methods are available at runtime.

e.g. The following parameter information is available:
  1. the field parameters of its generic declaration by using: field.getGenericType()
  2. the parameter declared in the return generic type by using: method.getGenericReturnType()
  3. the parameter declared against all the method parameters by using: method.getGenericParameterTypes
Here are some examples the type information is retrieved, many methods used have been added to Java reflection framework for dealing generics.



public class GenericReflection {
       public static class B {
             private A<String, Integer> a;

             public A<String, Integer> get(final List<String> ls, final List<Integer> li) {
                    a = new A<String, Integer>(ls.get(0), li.get(0));
                    return a;
             }
       }

       public static class A<U, V> {
             private final U u;
             private final V v;

             public A(final U u, V v) {
                    this.u = u;
                    this.v = v;
             }

             @A1
             public U getU() {
                    return u;
             }

             public V getV() {
                    return v;
             }
       }

       public static class IndexedType {
             public int getIndex() {
                    return index;
             }

             public Class<?> getType() {
                    return type;
             }

             private final int index;
             private final Class<?> type;

             public IndexedType(final int index, final Class<?> type) {
                    this.index = index;
                    this.type = type;
             }

             @Override
             public int hashCode() {
                    final int prime = 31;
                    int result = 1;
                    result = prime * result + index;
                    result = prime * result + ((type == null) ? 0 : type.hashCode());
                    return result;
             }

             @Override
             public boolean equals(Object obj) {
                    if (this == obj)
                           return true;
                    if (obj == null)
                           return false;
                    if (getClass() != obj.getClass())
                           return false;
                    IndexedType other = (IndexedType) obj;
                    if (index != other.index)
                           return false;
                    if (type == null) {
                           if (other.type != null)
                                 return false;
                    } else if (!type.equals(other.type))
                           return false;
                    return true;
             }

             @Override
             public String toString() {
                    return "IndexedType [index=" + index + ", type=" + type + "]";
             }
       }

       @Test
       public void test() throws NoSuchMethodException, SecurityException, NoSuchFieldException {
             System.out.println("===test===");
             final B b = new B();
             Map<IndexedType, List<Class<?>>> parameterizedReturnTypeMap = getParameterizedReturnTypes(b, "get", List.class, List.class);
             System.out.println("returnParameterTypes:" + parameterizedReturnTypeMap);
             Map<IndexedType, List<Class<?>>> parameterizedParameterTypeMap = getParameterizedParameterMap(b, "get", List.class,
                           List.class);
             System.out.println("parameterParametersMap:" + parameterizedParameterTypeMap);
             Map<IndexedType, List<Class<?>>> parameterizedFieldArgumentTypeMap = getParameterizedFieldArgumentTypeMap(b, "a");
             System.out.println("fieldArgTypeList:" + parameterizedFieldArgumentTypeMap);
       }

       public static Map<IndexedType, List<Class<?>>> getParameterizedReturnTypes(Object obj, String methodName, Class<?>... parameterTypes)
                    throws NoSuchMethodException, SecurityException {
             final Method method = obj.getClass().getMethod(methodName, parameterTypes);
             final Type genericReturnType = method.getGenericReturnType();
             final Type[] genericTypes = new Type[]{genericReturnType};
             final Map<IndexedType, List<Class<?>>> parameterizedTypeMap = getParameterizedTypeMap(genericTypes);
             return parameterizedTypeMap;
       }

       public static Map<IndexedType, List<Class<?>>> getParameterizedParameterMap(Object obj, String methodName,
                    Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException {
             Method method = obj.getClass().getMethod(methodName, parameterTypes);
             Type[] genericTypes = method.getGenericParameterTypes();

             final Map<IndexedType, List<Class<?>>> parameterizedTypeMap = getParameterizedTypeMap(genericTypes);
             return parameterizedTypeMap;
       }

       public static Map<IndexedType, List<Class<?>>> getParameterizedFieldArgumentTypeMap(Object obj, String fieldName) throws NoSuchFieldException, SecurityException {
             final Field field = obj.getClass().getDeclaredField(fieldName);
             final Type genericFieldType = field.getGenericType();
             final Type[] genericTypes = new Type[]{genericFieldType};
             final Map<IndexedType, List<Class<?>>> parameterizedTypeMap = getParameterizedTypeMap(genericTypes);
             return parameterizedTypeMap;
       }

       private static Map<IndexedType, List<Class<?>>> getParameterizedTypeMap(Type[] genericTypes) {
             final Map<IndexedType, List<Class<?>>> parameterizedTypeMap = new HashMap<>();
             int index = 0;
             for (Type genericType : genericTypes) {
                    if (genericType instanceof ParameterizedType) {
                           ParameterizedType aParameterizedType = (ParameterizedType) genericType;
                           final List<Class<?>> actualTypeArgumentClassList = new ArrayList<>();
                           Class<?> aParameterizedTypeClass = (Class<?>) aParameterizedType.getRawType();
                           parameterizedTypeMap.put(new IndexedType(index++, aParameterizedTypeClass), actualTypeArgumentClassList);
                           Type[] actualTypeArguments = aParameterizedType.getActualTypeArguments();
                           for (Type typeArgument : actualTypeArguments) {
                                 Class<?> actualTypeArgumentClass = (Class<?>) typeArgument;
                                  actualTypeArgumentClassList.add(actualTypeArgumentClass);
                           }
                    }
             }
             return parameterizedTypeMap;
       }

       @Test
       public void testTypeSafeCache() {
             System.out.println("===testTypeSafeCache===");
             final TypeSafeCache cache = new TypeSafeCache();
             cache.put(Integer.class, 1);
             cache.put(Long.class, 1L);
             cache.put(Double.class, 1.0);
             System.out.println(cache.get(Integer.class));
             System.out.println(cache.get(Long.class));
             System.out.println(cache.get(Double.class));
       }

       @Test
       public void testTypeSafeAnnotationType() throws NoSuchMethodException, SecurityException {
             System.out.println("===testTypeSafeAnnotationType===");
             final A<String, Integer> a = new A<>("A", 1);
             Method method = a.getClass().getMethod("getU");
             System.out.println("method" + method);
             Annotation a1 = method.getAnnotation(A1.class);
             System.out.println("annotationType:" + a1.annotationType());
             System.out.println("annotationType().getCanonicalName():" + a1.annotationType().getCanonicalName());
             System.out.println(
                           "annotationType().getClass().getCanonicalName():" + a1.annotationType().getClass().getCanonicalName());
             System.out.println(".annotationType().getTypeName():" + a1.annotationType().getTypeName());
             System.out.println(".annotationType().getGenericSuperclass():" + a1.annotationType().getGenericSuperclass());
             Annotation result = getAnnotation(method, a1.annotationType().getTypeName());
             System.out.println("result:" + result);
       }
}


===testTypeSafeCache===
1
1
1.0
===test===
returnParameterTypes:{IndexedType [index=0, type=class com.americanexpress.ssae.sample.generic.GenericReflection$A]=[class java.lang.String, class java.lang.Integer]}
parameterParametersMap:{IndexedType [index=0, type=interface java.util.List]=[class java.lang.String], IndexedType [index=1, type=interface java.util.List]=[class java.lang.Integer]}

===testTypeSafeAnnotationType===
method:public java.lang.Object com.americanexpress.ssae.sample.generic.GenericReflection$A.getU()
annotationType:interface com.americanexpress.ssae.sample.generic.GenericReflection$A1
annotationType().getCanonicalName():com.americanexpress.ssae.sample.generic.GenericReflection.A1
annotationType().getClass().getCanonicalName():java.lang.Class
.annotationType().getTypeName():com.americanexpress.ssae.sample.generic.GenericReflection$A1
.annotationType().getGenericSuperclass():null
annotationType:interface com.americanexpress.ssae.sample.generic.GenericReflection$A1
result:@com.americanexpress.ssae.sample.generic.GenericReflection$A1()




Java Advanced Generics Examples Part 1 - Wildcards

Java Generics are invariant. Wildcards is a mechanism to make expression of covariance available in order to expand the usages of the generics.

  1. Wildcards could be used to expand the code usage to certain type hierarchy instead of limiting to one type, the super and extends keywords  provide help here.
  2. With wildcards, sometimes same object will be labeled as different captures. Help method could be used to synchronize the type in another method.
  3. Explicit type parameter is in this format: Class.<Type>method(). It could be used to declare a limit to a wild card.

The following code shows examples of:
  1. wildcard with super and extends
  2. wildcard with help method
  3. explicit type parameter
  4. example of multiple extends


public class GenericUsages {
       // this list is a destination or consumer: super
       public static void addNumbers(final List<? super Integer> list) {
             for (int i = 1; i <= 10; i++) {
                    list.add(i);
             }
       }
       
       // this list is a source or producer: extends
       public static int sumOfList(final List<? extends Integer> list) {
             int s = 0;
             for (Number n : list)
                    s += n.doubleValue();
             return s;
       }

       @Test
       public void testSimpleSuperAndExtend() {
             System.out.println("===testSimpleSuperAndExtend===");
             final List<Integer> intList = new ArrayList<>();
             addNumbers(intList);
             final int result = sumOfList(intList);
             System.out.println(result);
       }

       public static void increment(Map<?, String> in) {
             incrementInternal(in);
       }

       // A help method to avoid compiling errors
       private static <T> void incrementInternal(final Map<T, String> in) {
             Set<T> keySet = in.keySet();
             for (T key : keySet) {
                    in.put(key, in.get(key) + ":)");
             }
       }

       @Test
       public void testHelpMethod() {
             System.out.println("===testHelper===");
             Map<Number, String> map = new HashMap<>();
             map.put(1, "Integer 1");
             map.put(2.0, "Double 2.0");
             map.put(3L, "Long 3");
             increment(map);
             System.out.println(map);

             Map<String, String> map2 = new HashMap<>();
             map2.put("k1", "v1");
             map2.put("k2", "v2");
             map2.put("k3", "v3");
             increment(map2);
             System.out.println(map2);

       }

       public static <E> Set<E> union(final Set<? extends E> s1, final Set<? extends E> s2) {
             final Set<E> result = new HashSet<>(s1);
             result.addAll(s2);
             return result;
       }

       @Test
       public void testExplicitTypeParameter() {
             System.out.println("===testExplicitTypeParameter===");
             Set<Integer> integers = new HashSet<>();
             integers.add(1);
             integers.add(2);
             Set<Double> doubles = new HashSet<>();
             doubles.add(1.0);
             doubles.add(2.0);
             Set<Number> result = GenericUsages.<Number>union(integers, doubles);
             System.out.println(result);
             Set<String> strings = new HashSet<>();
             strings.add("1s");
             strings.add("2s");
             Set<?> result2 = GenericUsages.union(integers, strings);
//           Compiling errors:
//           Set<Number> result2 = GenericUsages.union(integers, strings);
//           Set<?> result3 = GenericUsages.<Number>union(integers, strings);
             System.out.println(result2);
       }

       interface Label<T> {
             String getLabel();
       }

       public static <S extends Label<? super S>, U extends Label<? super U>> String m1(final S s, final U u) {
             final String result = s.getLabel().toString() + u.getLabel().toString();
             return result;
       }

       public static class Base implements Label<Base> {
             private String label;

             public Base(final String label) {
                    this.label = label;
             }

             @Override
             public String getLabel() {
                    return this.label;
             }
       }

       public static class S1 extends Base {
             public S1() {
                    super("S1");
             }
       }

       public static class U1 extends Base {
             public U1() {
                    super("U1");
             }
       }

       @Test
       public void testComplicatedSuperAndExtend() {
             System.out.println("===testComplicatedSuperAndExtend===");
             final S1 s1 = new S1();
             final U1 u1 = new U1();
             String result = m1(s1, u1);
             System.out.println(result);
       }

       public static <X extends A & B & C> C m2(final X x) {
             return x;
       }

       public static class A {
       }

       interface B {
       }

       interface C {
       }

       public static class D extends A implements B, C {
       }

       @Test
       public void testMultipleSuperTypes() {
             System.out.println("===testMultipleSuperTypes===");
             final D d = new D();
             System.out.println(m2(d).toString());
       }
}