我有一个基本上处理配置类型的转换的方法,但是在指定泛型类型(例如List
)时,它成为如何处理特定类型的问题。在理想的世界中,诸如使用类型证人之类的东西:
List<String> someVal = MyConfig.SOME_VAL.<List<String>>.as(List.class);'
(完整的as
代码):
/**
* Attempts to return the {@link Config} value as a casted type. If the
* value cannot be casted it will attempt to return the default value. If
* the default value is inappropriate for the class, the method will
* throw a {@link ClassCastException}.
*
* @since 0.1.0
* @version 0.1.0
*
* @param <T> The type of the casting class
* @param c The class type to cast to
* @return A casted value, or {@code null} if unable to cast. If the passed
* class parameter is of a primitive type or autoboxed primitive,
* then a casted value of -1 is returned, or {@code false} for
* booleans. If the passed class parameter is for {@link String},
* then {@link Object#toString()} is called on the value instead
*/
default public <T> T as(Class<T> c) {
Validate.notNull(c, "Cannot cast to null");
Validate.isTrue(Primitives.unwrap(c) != void.class, "Cannot cast to a void type");
Object o = this.get();
if (o == null) {
T back = Reflections.defaultPrimitiveValue(c);
if (back != null) { //catch for non-primitive classes
return back;
}
}
if (c == String.class) {
return (T) String.valueOf(o);
}
if (c.isInstance(o)) {
return c.cast(o);
}
if (c.isInstance(this.getDefault())) {
return c.cast(this.getDefault());
}
throw new ClassCastException("Unable to cast config value");
}
所以基本上这给我留下了两个部分的问题:为什么不能在类上使用证人作为泛型(例如List(raw)
- &gt; List<String>
),我怎么能如果支持检索具有泛型边界的类而不进行无关的转换?第一点特别令我感到困惑,因为这是完全合法的:
List<String> test = new ArrayList<>();
test = MyConfig.FRIENDLY_MOBS.as(test.getClass());
尽管它返回了原始类型列表
答案 0 :(得分:1)
该行真的很邪恶(类型擦除/原始类型),因为没有检查Collection类型是否真的包含字符串。
test = MyConfig.FRIENDLY_MOBS.as(test.getClass());
我认为最简单的解决方案是编写一个as方法,它接受集合类型和元素类的类对象。请参阅以下示例(在静态范围内,因此您必须对其进行调整):
static List<String> words = Arrays.asList("Hello", "Bonjour", "engine");
static public <E, Coll extends Collection<E>> Coll as(Class<? extends Collection> collClass, Class<E> elemClass) {
if (collClass.isInstance(words)) {
Collection coll = collClass.cast(words);
for (Object o : coll)
if (!elemClass.isInstance(o))
throw new ClassCastException("BAM");
return (Coll)coll;
}
return null;
}
现在发现以下行为:
final List<String> list = as(List.class, String.class); // works
final List<String> list = as(List.class, Integer.class); // compile error: type check
final List<Integer> list = as(List.class, String.class); // compile error: type check
final List<Integer> list = as(List.class, Integer.class); // ClassCastException
至于其他尝试:iirc Jackson有一些神奇的TypeToken内容,允许捕获List&lt; String&gt;等类型。它以某种方式滥用了Enum&lt; T&gt;我想......
答案 1 :(得分:1)
您对类型见证的想法确实是要走的路,但是您需要一个更好的类型见证,它不仅捕获原始类型(此处为List
),还捕获其通用参数。这在Java中并不容易,因为在大多数地方,由于类型擦除,通用参数在运行时不可用。 Java的反射API使用作为泛型类型的运行时表示的Type
子接口的接口,但这些接口不适合您的目的,因为它们不提供任何编译时类型信息。
但是,通过一招可以实现您想要的效果。
诀窍是基于以下事实:如果一个类(例如:MyClass
)继承自泛型类型(例如:List<String>
),则没有类型擦除。您可以在运行时检索MyClass
从List<String>
继承的信息(使用方法Class.getGenericSuperclass()
)。
对我们想要传递的实际类型进行子类化将是非常不灵活的(例如,这对最终类不起作用)。因此,我们创建了一个特殊的类(通常称为TypeToken
),我们可以从中继承。 TypeToken
类有一个泛型参数,在子类中我们指定要作为此参数传递的类型。当然,为你想要传递的每个不同的值创建一个特殊的类通常会非常麻烦,但幸运的是我们可以使用匿名类来简化这一过程。
总而言之,解决方案可能如下所示。
我们的类型令牌的定义:
public abstract class TypeToken<T> {}
方法as
的定义:
public <T> T as(TypeToken<T> typeToken) {
Type targetType = typeToken.getClass().getGenericSuperclass();
// use targetType here, e.g.
if (targetType instanceof ParameterizedType) { ... }
并称之为:
List<Integer> list = MyConfig.SOME_VAL.as(new TypeToken<List<String>>() {});
注意声明匿名子类的{}
并避免类型擦除。
更好的方法是使用现有的类作为类型标记,例如伟大的TypeToken
库的类Guava(如果你还不知道这个库,还要看看还有什么它提供并考虑使用它!)。此类还提供了其他辅助方法,使得在as
方法中使用令牌变得更加容易(直接使用Type
实例可能很困难)。番石榴维基有more information on its TypeToken
class。
如果您担心创建过多的类,您当然可以轻松地为TypeToken<List<String>>
等常见情况提供一些默认实例。番石榴TypeToken
也有一个of(Class<T>)
方法可以用于非泛型类型,因此子类将仅限于实际需要的情况。
其他项目也使用此技巧,例如Guice与班级TypeLiteral
(explanation),Gson(TypeToken
)和杰克逊(TypeReference
)。因此,我不会过分担心子类的数量,因为它们不会使源代码混乱。