为类类型指定泛型

时间:2015-03-08 19:41:55

标签: java generics

我有一个基本上处理配置类型的转换的方法,但是在指定泛型类型(例如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());

尽管它返回了原始类型列表

2 个答案:

答案 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>),则没有类型擦除。您可以在运行时检索MyClassList<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与班级TypeLiteralexplanation),Gson(TypeToken)和杰克逊(TypeReference)。因此,我不会过分担心子类的数量,因为它们不会使源代码混乱。