Java泛型中的捕获是否可以在类型声明中统一?

时间:2012-02-08 21:27:27

标签: java generics type-erasure

考虑以下Java函数:

public void foo(Class<? extends Exception> cl, List<? extends Exception> ls) throws Exception {
    ls.add(cl.newInstance());
}

这不起作用,因为clls的类型捕获不统一,实际上可以引用不同的类型。如果编译了这个函数,我可以把它称为foo(NullPointerException.class, new List<SecurityException>()),这本来是非法的。

显然,我们可以通过统一类型捕获来解决这个问题,例如:

public <T extends Exception> void foo(Class<T> cl, List<T> ls) throws Exception {
    ls.add(cl.newInstance());
}

现在此功能按预期工作。因此,在我的问题上:有没有办法在单一类型声明中统一类型捕获?

例如,我经常发现自己想要一个将类映射到自己实例的地图。我目前可以想到的唯一方法是使用一个附带的函数来执行未经检查的强制转换:

private Map<Class<? extends Foo>, ? extends Foo> map = ...;
@SuppressWarnings("unchecked")
private <T extends Foo> T getFoo(Class<T> cl) {
    return((T)map.get(cl));
}

但显然更好的是不必禁止警告并让编译器理解地图类型声明中的两种类型捕获应该是相同的,然后只需将地图公开。例如,如果我有一个类似于此的声明:

<T extends Foo> Map<Class<T>, T> map = ...;

显然,这不是有效的语法,但我的问题归结为:是否有任何有效的语法可以让我做一些这样的事情?

6 个答案:

答案 0 :(得分:3)

您正在寻找Generics of a Higher Kind。 Java没有它们,也可能永远不会。我意识到这是一个Java问题,但我会指出Scala有更高的种类。如果你想看看在类型系统中拥有这种能力意味着什么,你可能想要玩它。

答案 1 :(得分:2)

并非没有压制一些警告。

你真的不想通过在类型系统中使Map“特殊”来做到这一点 - 那些Function类型,或者你有什么?描述像“这个类仅对应于这个类的实例”这样的复杂关系,即使在高级类型的系统中也是非常困难的 - 我甚至不确定Agda能做到这一点,而Agda拥有最强大的类型系统我知道。

当然,您可以将其包装在内部不安全的外部安全API中,参见番石榴的ClassToInstanceMap。但是你无法在编译时做出那种保证。

答案 2 :(得分:1)

怎么样?
public static <T extends Exception> void foo(Class<T> cl, List<? super T> ls) throws Exception {
    ls.add(cl.newInstance());
}

?在Effective Java(Producer Extends Consumer Super

中也可以找到好处

答案 3 :(得分:0)

不,“统一”的唯一方法是使用类型变量,并且类型变量在引入它的整个声明中始终代表相同的类型。无法为地图中的每个条目引入代表不同类型的类型变量,因为您无法将类型变量添加到Map.Entry

我描述了在another answer中有意义地限制地图条目类型的可能性。

当然,如果您不必实现Map接口,只想提供putget方法,则可以使用方法范围的类型变量来统一。但是只要你想要一个addAll()方法,你就会超越Java类型系统的表现力。

答案 4 :(得分:0)

我能想到的第一件事就是扩展Map接口的实现,并将其声明为:

public class ClassMap<T extends Foo> extends HashMap<Class<T>, T>

虽然你似乎不想实施别的东西来做到这一点。

答案 5 :(得分:0)

恕我直言,你的问题的原因是对泛型和使用外卡的理解。

你提出的第一个例子是应该的。

public void foo(Class<? extends Exception> cl, List<? extends Exception> ls) throws Exception;

相同
public void foo(Class<Integer> c1, List<String> ls) throws Exception

我要提出的是,在这两种情况下,参数都没有任何共同之处。

如果我们想强制它,两个参数使用相同的泛型参数,我们需要在泛型参数的部分中声明它。

public <T extends Exception> void foo(Class<T> cl, List<T> ls) throws Exception

好的,现在我们确保两个参数都必须使用扩展Exception

的类型

这个解决方案有一些限制,所以我们可以通过引入一个外卡? super T来改进,通常代表T或超类。

public <T extends Exception> void foo(Class<T> cl, List<? super T> ls) throws Exception

回到原来的问题“是否有任何有效的语法可以让我做一些这样的事情?”

要在字段和方法中使用相同的泛型参数,他的范围应该是class。

public class Foo<T extends Exception> { 

   private Map<Class<T>,T> map;

   public void T getFoo(Class<T> clazz) {
     return map.get(clazz);
   }

}

但是如果你不想在类定义中使用泛型类型,则无法保证泛型类型的方法与字段的泛型类型相同。

所以对你来说最终的解决方案是,如果你创建了一个封装的地图,并且正确的使用方法来设置和检索对象,那么压制警告也不是坏事。因为你以其他方式确保了类型的安全性。