我正在阅读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
}
在无界通配符参数化类型的情况下,我们是 另外限制我们如何使用数组元素,因为 编译器阻止对无界通配符执行某些操作 参数化类型。本质上,原始类型和无界的数组 通配符参数化类型在语义上与什么不同 我们将用一个参数化的具体通配符数组来表达 类型。出于这个原因,他们不是一个好的解决方法而且只是 阵列的优越效率(与之相比)是可以接受的 收藏品)是至关重要的。
我在这里有两个具体问题。
有人可以解决这个问题吗?
答案 0 :(得分:3)
首先关于这段代码:
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
它将编译&amp;运行并产生这个预期的输出:
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方法并返回一个未知类型的对象,我们可以将其赋值给Object类型的引用变量。
所以你是对的,它确实限制了它的使用,如上面的小例子所示,其中值可以在构造期间分配,但不是在你尝试调用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
的上下文中。
答案 1 :(得分:0)
当您需要灵活的泛型类型(例如接受Collection
时)但实际上并不关心类型是什么时,无界的通配符基本上是语法糖(你只是打印它的toString
)。您可以使用通用方法执行相同的操作,但它会变得笨拙并且不会为您购买任何东西。一个无界的通配符可以让你省去它。
关于数组的解释只是在提醒你,在大多数情况下,最好使用集合类型而不是数组,除非你真的负担不起集合的额外成本。在您的示例中,您找到了原因:当您将数组作为Object[]
传入时,您告诉编译器将任何对象添加到该数组是完全可以的,因为Pair<Integer,Integer>
是Object
,编译器允许您这样做。根据您的其他代码(您还没有完全触发异常),您现在可能在intPairArr
中存在堆污染。