无界通配符参数化类型数组的实际用法是什么?

时间:2013-08-19 09:03:26

标签: java generics collections

我正在阅读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 
} 
  

在无界通配符参数化类型的情况下,我们是   另外限制我们如何使用数组元素,因为   编译器阻止对无界通配符执行某些操作   参数化类型。本质上,原始类型和无界的数组   通配符参数化类型在语义上与什么不同   我们将用一个参数化的具体通配符数组来表达   类型。出于这个原因,他们不是一个好的解决方法而且只是   阵列的优越效率(与之相比)是可以接受的   收藏品)是至关重要的。

我在这里有两个具体问题。

  1. 无界外卡参数化类型的实际用法是什么?从示例中可以清楚地看出,您可以向数组添加元素,但在检索时会发出编译错误吗?
  2. 这篇文章的意思是什么,当它指出,这些外卡参数化只有在数组的卓越效率至关重要时才可以接受?
  3. 有人可以解决这个问题吗?

2 个答案:

答案 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中存在堆污染。