我正在通过AngelikaLangerParametrizedTypeWorkAround阅读。我理解这里的许多概念,我理解什么是无界的外卡参数化类型。虽然引用了其中的内容,但它指出:
static void test() {
Pair<?,?>[] intPairArr = new Pair<?,?>[10] ;
addElements(intPairArr);
Pair<Integer,Integer> pair = intPairArr[1]; // error -1
Integer i = pair.getFirst();
pair.setSecond(i);
}
static void addElements(Object[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("",""); // should fail, but succeeds
}
对于无界通配符参数化类型,我们在如何使用数组元素方面受到额外限制,因为编译器阻止对无界通配符参数化类型的某些操作。本质上,原始类型和无界通配符参数化类型的数组在语义上与我们使用具体的通配符参数化类型的数组表示的非常不同。由于这个原因,它们不是一个很好的解决方法,只有在数组的优越效率(与集合相比)是最重要的时候才能被接受。
我这里有两个具体的问题。
有人能在这个问题上详细阐述吗?
发布于 2013-08-19 09:22:38
首先,关于这个代码:
static void addElements(Object[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("",""); // should fail, but succeeds
}
在这里,您将Object[]
类型的参数传递给addElements
方法。因此编译器将允许您添加任何属于Object
的内容。甚至这些代码也会编译:
static void addElements(Object[] objArr) {
objArr[0] = new Pair<Integer,Integer>(0,0);
objArr[1] = new Pair<String,String>("","");
objArr[2] = new Date(); // won't be a compilation error here
}
但是,您将获得运行时异常,因为一般类型是编译时检查和运行时转换。
现在你的问题是,为什么甚至允许在泛型中使用原始类型?
允许它向后兼容旧JVM的原因之一,也适用于接口开发人员可能不知道在运行时可以提供的所有类型的情况。您的error-1
确实需要从原始类型转换为特定类型:
// this should compile
@SuppressWarnings("unchecked")
Pair<Integer, Integer> pair = (Pair<Integer, Integer>) intPairArr[0]; // NO error -1
编辑:
关于通配符困境的:
让我们以一个使用无界通配符的简单例子为例:
Pair<?, ?> intPair = new Pair<Integer, Integer>(4, 9);
Object val2 = intPair.getSecond();
System.out.printf("val2: %d, isInt: %s%n", val2, (val2 instanceof Integer));
intPair.setFirst( null ); // assigning null will be allowed
它将编译和运行并产生预期的输出:
val2: 9, isInt: true
然而,这将不会编译:
intPair.setSecond((Object) new Integer(10)); // compile error
intPair.setSecond(new Integer(10)); // compile error
在unbounded wildcard
参数化类型(如Pair<?,?>
)中,字段的类型和方法的返回类型将是unknown
,即这两个字段都是?
类型。setter方法将采用?
类型的参数,getter方法将返回一个?
。
在这种情况下,编译器将不允许向字段分配任何内容,也不允许将任何内容传递给setter方法。原因是编译器无法确保我们试图作为set方法的参数传递的对象是预期类型,因为预期类型是未知的。
相反,可以调用getter方法并返回未知类型的对象,我们可以将该对象分配给类型对象的引用变量。
因此,您是正确的,在某种程度上,它确实限制了它的使用,正如上面所示,在构造期间可以分配值,但当您尝试调用setter方法时就不能这样做。
但是,您可以通过使用通配符和低绑定类型的来提高代码的有用性,如下所示:
Pair<? super Object, ? super Object> intPair = new Pair<Object, Object>(4, 9);
Object val2 = intPair.getSecond();
System.out.printf("val2: %s, isInt: %s%n", val2, (val2 instanceof Integer));
intPair.setSecond(10);
val2 = intPair.getSecond();
System.out.printf("val2: %s, isInt: %s%n", val2, (val2 instanceof Integer));
现在,这不仅编译,而且运行预期的结果:
val2: 9, isInt: true
val2: 10, isInt: true
关于你的第二个问题:我直接引用你的链接文章中的段落:
通过使用原始类型数组或无界通配符参数化类型,我们提供了静态类型检查,以确定同质序列。因此,我们必须使用显式强制转换,否则就可能出现意外的ClassCastException。在无界通配符参数化类型的情况下,我们在如何使用数组元素方面受到额外的限制,因为编译器阻止对无界通配符参数化类型的某些操作。本质上,原始类型和无界通配符参数化类型的数组在语义上与我们使用具体的通配符参数化类型的数组表示的非常不同。由于这个原因,它们不是一个很好的解决方法,只有在数组的优越效率(与集合相比)是最重要的时候才能被接受。
作者强调,数组中的无界通配符并不是一个很好的解决方法,因为它的限制和优越的效率仅在arrays vs collections
的上下文中。
发布于 2013-08-19 09:24:33
当您需要灵活的泛型类型(例如接受Collection
时)时,无界通配符基本上是语法糖,但实际上并不关心类型是什么(您只是打印它的toString
)。你可以用一个通用的方法做同样的事情,但是它变得笨拙,不会给你买任何东西。一个无限制的通配符可以让你省去它。
关于数组的解释只是提醒您,在大多数情况下,最好使用集合类型而不是数组,除非您真的无法负担集合的额外成本。在您的示例中,您了解了原因:当您以Object[]
的形式传入数组时,您告诉编译器,向该数组添加任何对象是完全可以的,而且由于Pair<Integer,Integer>
是一个Object
,所以编译器允许您这样做。根据您的其他代码(尚未完全触发异常),您现在可能在intPairArr
中出现堆污染。
https://stackoverflow.com/questions/18310023
复制相似问题