WildcardType to ParameterizedType
WildcardType to ParameterizedType
I have the bean property of type List<? extends Number>
and the list of candidate implementation classes with their constructors. I recursively seek candidates for constructor parameters.
List<? extends Number>
One of the candidate constructors is ArrayList(Collection<? extends E>)
ArrayList(Collection<? extends E>)
I'm trying to resolve the type parameters and constructor parameters using Guava, but when there's a chain of wildcard types, I get something like: ? extends capture#3-of ? extends capture#2-of ? extends capture#1-of ? extends ...
? extends capture#3-of ? extends capture#2-of ? extends capture#1-of ? extends ...
public static <T extends Object> void main(String args) throws Exception
TypeToken<?> propTt = new TypeToken<List<? extends Number>>()
;
TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);
TypeToken<?> constructorResult;
Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);
// java.util.ArrayList<? extends java.lang.Number>
constructorResult = candidate.constructor(cons).getReturnType();
System.out.println(constructorResult);
// java.util.Collection<? extends E>
Type param = cons.getGenericParameterTypes()[0];
System.out.println(param);
// java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
TypeToken<?> resolvedParam = constructorResult.resolveType(param);
System.out.println(resolvedParam);
This happens although we can't use wildcards as type parameters for new
. The following is illegal:
new
List<?> x = new ArrayList<?>();
List<? extends Number> y = new ArrayList<? extends Number>();
Instead we write:
List<?> x = new ArrayList<Object>();
List<? extends Number> y = new ArrayList<Number>();
or automate the latter by using the diamond operator.
The desired type of resolvedParam
is java.util.Collection<? extends java.lang.Number>
.
resolvedParam
java.util.Collection<? extends java.lang.Number>
com.google.common.reflect.Types
has the newParameterizedType()
method where I could pass the former wildcards manually resolved to parameterized types or classes, but this method is package-private. I'm uncertain my workaround:
com.google.common.reflect.Types
newParameterizedType()
package com.google.common.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.checkerframework.checker.nullness.qual.Nullable;
@SuppressWarnings("serial")
public class TestWildcard
public static <T extends Object> void main(String args) throws Exception
TypeToken<?> propTt = new TypeToken<List<? extends Number>>()
;
TypeToken<?> candidate = propTt.getSubtype(ArrayList.class);
TypeToken<?> constructorResult;
Constructor<?> cons = ArrayList.class.getConstructor(Collection.class);
// new java.util.ArrayList<java.lang.Number>();
constructorResult = resolveConstructorResult(candidate);
System.out.println(constructorResult);
// java.util.Collection<? extends E>
Type param = cons.getGenericParameterTypes()[0];
System.out.println(param);
// java.util.Collection<? extends capture#1-of ? extends class java.lang.Number>
TypeToken<?> resolvedParam = constructorResult.resolveType(param);
System.out.println(resolvedParam);
static TypeToken<?> resolveConstructorResult(TypeToken<?> candidate)
Type ct = candidate.getType();
if (ct instanceof ParameterizedType)
ParameterizedType pt = (ParameterizedType) ct;
Class<?> ptClazz = ((Class<?>) pt.getRawType());
TypeVariable<?> tvars = ptClazz.getTypeParameters();
Type targs = pt.getActualTypeArguments();
boolean doIt = false;
for (int i = 0; i < targs.length; i++)
if (targs[i] instanceof WildcardType)
WildcardType wt = (WildcardType) targs[i];
TypeToken<?> ubound1 = TypeToken.of(wt.getUpperBounds()[0]);
TypeToken<?> ubound2 = TypeToken.of(tvars[i].getBounds()[0]);
if (ubound1.isSubtypeOf(ubound2))
doIt = true;
targs[i] = ubound1.getType();
else if (ubound2.isSubtypeOf(ubound1))
doIt = true;
targs[i] = ubound2.getType();
if (doIt)
pt = GuavaReflectAccessHelper.newParameterizedTypeWithOwner(pt.getOwnerType(), ptClazz, targs);
candidate = TypeToken.of(pt);
return candidate;
private static class GuavaReflectAccessHelper
public static ParameterizedType newParameterizedTypeWithOwner(@Nullable Type ownerType, Class<?> rawType,
Type... arguments)
return Types.newParameterizedTypeWithOwner(ownerType, rawType, arguments);
2 Answers
2
Converting wildcard type to ParameterizedType does not sound good here, but you can just implement that interface on your own.
Or use some other library that does support more dynamic creation:
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/reflect/TypeUtils.html
And probably many other libraries.
In guava you can only do something like that:
static <K, V> TypeToken<Map<K, V>> mapToken(TypeToken<K> keyToken, TypeToken<V> valueToken)
return new TypeToken<Map<K, V>>()
.where(new TypeParameter<K>() , keyToken)
.where(new TypeParameter<V>() , valueToken);
...
TypeToken<Map<String, BigInteger>> mapToken = mapToken(
TypeToken.of(String.class),
TypeToken.of(BigInteger.class));
TypeToken<Map<Integer, Queue<String>>> complexToken = mapToken(
TypeToken.of(Integer.class),
new TypeToken<Queue<String>>() );
It might be enough for you.
List<? extends SomeClass & SomeInterface>
You are right here, but if you are normalizing your generics to ParameterizedType type you might get type like this:
<T extends SomeClass & SomeInterface> List<T>
(as method return type, or T from class and as field) But yep, I forgot that they only added support for upper/lower bounds reflection API but not in language itself, but you still should think about case I mentioned above.– GotoFinal
Sep 9 '18 at 8:49
<T extends SomeClass & SomeInterface> List<T>
How to rewrite
mapToken()
to accept an arbitrary raw class and an array of actual type arguments? There's no TypeParameter.of(Type)
and TypeToken.of(Class)
returns the raw type token, not a parameterized type needed for where()
– basin
Sep 11 '18 at 17:37
mapToken()
TypeParameter.of(Type)
TypeToken.of(Class)
where()
TypeToken.of(Object.class).getSubtype(Map.class)
?– basin
Sep 11 '18 at 17:50
TypeToken.of(Object.class).getSubtype(Map.class)
I don't think it is possible with this library, at least I don't see any way, you can use some other one
– GotoFinal
Sep 13 '18 at 9:36
A custom newParameterizedType
can be implemented with TypeResolver.where()
(not to be confused with TypeToken.where()
). The related part in my resolveConstructorResult()
in the question can be changed like this:
newParameterizedType
TypeResolver.where()
TypeToken.where()
resolveConstructorResult()
if (doIt)
pt = (ParameterizedType)newParameterizedType(ptClazz, targs);
candidate = TypeToken.of(pt);
}
return candidate;
}
public static Type newParameterizedType(Class<?> cls, Type... args)
TypeResolver resolver = new TypeResolver();
TypeVariable<?> tvars = cls.getTypeParameters();
for (int i = 0; i < args.length; i++)
resolver = resolver.where(tvars[i], args[i]);
return resolver.resolveType(dumbToGenericType(cls).getType());
@SuppressWarnings("unchecked")
public static <T> TypeToken<? extends T> dumbToGenericType(Class<T> cls)
return (TypeToken<T>)TypeToken.of(Object.class).getSubtype(cls);
Thanks for contributing an answer to Stack Overflow!
But avoid …
To learn more, see our tips on writing great answers.
Required, but never shown
Required, but never shown
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
All good, but
List<? extends SomeClass & SomeInterface>
can't exist. Wildcards always have one upper bound.– basin
Sep 9 '18 at 8:37