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

ben*_*enz 6 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 
} 
Run Code Online (Sandbox Code Playgroud)

在无界通配符参数化类型的情况下,我们还限制了我们如何使用数组元素,因为编译器阻止了对无界通配符参数化类型的某些操作.本质上,原始类型和无界通配符参数化类型的数组在语义上与我们用具体通配符参数化类型的数组表达的数组非常不同.由于这个原因,它们不是一个好的解决方法,只有当阵列的优越效率(与集合相比)至关重要时才能接受.

我这里有两个具体问题.

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

有人可以解决这个问题吗?

anu*_*ava 4

首先关于这段代码:

static void addElements(Object[] objArr) { 
  objArr[0] = new Pair<Integer,Integer>(0,0); 
  objArr[1] = new Pair<String,String>("","");    // should fail, but succeeds 
}
Run Code Online (Sandbox Code Playgroud)

在这里,您将类型参数传递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
}
Run Code Online (Sandbox Code Playgroud)

但是,您将得到运行时异常,因为泛型类型是编译时检查和运行时强制转换。

现在你的问题是为什么在泛型中允许原始类型?

这是允许它向后兼容旧版 JVM 的原因之一,也是为了解决接口开发人员可能不知道运行时可以提供的所有类型的情况。您error-1确实需要从原始类型转换为特定类型:

// this should compile
@SuppressWarnings("unchecked")
Pair<Integer, Integer> pair = (Pair<Integer, Integer>) intPairArr[0];  // NO error -1
Run Code Online (Sandbox Code Playgroud)

编辑:

关于通配符困境:

让我们举一个使用无界通配符的非常简单的例子:

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
Run Code Online (Sandbox Code Playgroud)

它将编译并运行并产生预期的输出:

val2: 9, isInt: true
Run Code Online (Sandbox Code Playgroud)

然而这不会编译:

intPair.setSecond((Object) new Integer(10)); // compile error
intPair.setSecond(new Integer(10)); // compile error
Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)

现在,这不仅可以编译,而且可以运行并得到预期的结果:

val2: 9, isInt: true
val2: 10, isInt: true
Run Code Online (Sandbox Code Playgroud)

关于你的第二个问题:我直接引用你链接文章中的段落:

通过使用原始类型数组或无界通配符参数化类型,我们放弃了同质序列附带的静态类型检查。因此,我们必须使用显式强制转换,否则就会面临意外的 ClassCastException 的风险。对于无界通配符参数化类型,我们在如何使用数组元素方面受到额外限制,因为编译器会阻止对无界通配符参数化类型进行某些操作。本质上,原始类型和无界通配符参数化类型的数组在语义上与我们使用具体通配符参数化类型的数组表达的内容非常不同。因此,它们不是一个好的解决方法,只有当数组(与集合相比)的卓越效率至关重要时才可以接受。

作者强调,数组中的无界通配符并不是一个好的解决方法,因为它的限制和卓越的效率仅在arrays vs collections.