发生这种情况是因为 Java 类型擦除。要回答这个问题,我需要解释无界通配符、有界通配符和类型擦除。如果您熟悉它,请随意跳过任何部分。
这篇文章的内容是从 java 文档中组装而成的。
1. 无界通配符
无界通配符类型使用通配符 ( ?
) 指定,例如List<?>
。这称为未知类型列表。在两种情况下,无界通配符是一种有用的方法:
2. 有界通配符
考虑一个可以绘制矩形和圆形等形状的简单绘图应用程序。要在程序中表示这些形状,您可以定义一个类层次结构,如下所示:
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class Circle extends Shape {
private int x, y, radius;
public void draw(Canvas c) {
...
}
}
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw(Canvas c) {
...
}
}
这些类可以在画布上绘制:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
任何绘图通常都会包含许多形状。假设它们被表示为一个列表,那么在 Canvas 中有一个方法来绘制它们会很方便:
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
现在,类型规则说drawAll()
只能在完全为 Shape 的列表上调用:例如,它不能在 a 上调用List<Circle>
。这是不幸的,因为该方法所做的只是从列表中读取形状,所以它也可以在List<Circle>
. 我们真正想要的是该方法接受任何形状的列表:
public void drawAll(List<? extends Shape> shapes) {
...
}
这里有一个很小但非常重要的区别:我们将类型替换List<Shape>
为List<? extends Shape>
. 现在drawAll()
将接受 的任何子类的列表Shape
,因此我们现在可以根据需要在 a 上调用它List<Circle>
。
List<? extends Shape>
是有界通配符的一个示例。代表未知类型,但是,在这种?
情况下,我们知道这种未知类型实际上是 Shape 的子类型。(注意:它可以是 Shape 本身,也可以是某个子类;它不需要从字面上扩展 Shape。)我们说 Shape 是通配符的上限。
类似地,作为有界通配符的语法? super T
表示未知类型,它是 T 的超类型。例如,ArrayedHeap280 包括ArrayedHeap280<Integer>
、ArrayedHeap280<Number>
和ArrayedHeap280<Object>
。正如您在Integer 类的 java 文档中看到的那样,Integer 是 Number 的子类,而 Number 又是 Object 的子类。
类整数
* java.lang.Object * java.lang.Number * java.lang.Integer
3.类型擦除和ClassCastException
泛型被引入 Java 语言以在编译时提供更严格的类型检查并支持泛型编程。为了实现泛型,Java 编译器将类型擦除应用于:
- 如果类型参数是无界的,则将泛型类型中的所有类型参数替换为其边界或 Object。因此,生成的字节码只包含普通的类、接口和方法。
- 必要时插入类型转换以保持类型安全。
- 生成桥方法以保留扩展泛型类型中的多态性。
在类型擦除过程中,Java 编译器擦除所有类型参数,如果类型参数是有界的,则将每个类型参数替换为其第一个边界,如果类型参数是无界的,则将其替换为 Object。
考虑以下表示单链表中节点的泛型类:
public class Node<T> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) }
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
```
>Because the type parameter T is unbounded, the Java compiler replaces it with Object:
```java
public class Node {
private Object data;
private Node next;
public Node(Object data, Node next) {
this.data = data;
this.next = next;
}
public Object getData() { return data; }
// ...
}
在以下示例中,通用 Node 类使用有界类型参数:
public class Node<T extends Comparable<T>> {
private T data;
private Node<T> next;
public Node(T data, Node<T> next) {
this.data = data;
this.next = next;
}
public T getData() { return data; }
// ...
}
Java 编译器将有界类型参数 T 替换为第一个绑定类 Comparable:
public class Node {
private Comparable data;
private Node next;
public Node(Comparable data, Node next) {
this.data = data;
this.next = next;
}
public Comparable getData() { return data; }
// ...
}
```
> Sometimes type erasure causes a situation that you may not have anticipated. The following example shows how this can occur.
>
> Given the following two classes:
```java
public class Node<T> {
public T data;
public Node(T data) { this.data = data; }
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node<Integer> {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
类型擦除后,Node
andMyNode
类变为:
public class Node {
public Object data;
public Node(Object data) { this.data = data; }
public void setData(Object data) {
System.out.println("Node.setData");
this.data = data;
}
}
public class MyNode extends Node {
public MyNode(Integer data) { super(data); }
public void setData(Integer data) {
System.out.println("MyNode.setData");
super.setData(data);
}
}
考虑以下代码:
MyNode mn = new MyNode(5);
Node n = mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = mn.data; // Causes a ClassCastException to be thrown.
类型擦除后,此代码变为:
MyNode mn = new MyNode(5);
Node n = (MyNode)mn; // A raw type - compiler throws an unchecked warning
n.setData("Hello");
Integer x = (String)mn.data; // Causes a ClassCastException to be thrown.
以下是代码执行时发生的情况:
n.setData("Hello");
导致方法setData(Object)
在 class 的对象上执行MyNode
。(MyNode
继承setData(Object)
自的类Node
。)
- 在的体内
setData(Object)
,
这
data
引用的对象的字段n
分配给 a String
。
data
通过 引用的同一对象的字段mn
可以被访问,并且应该是一个Integer
(因为mn
是 aMyNode
这是一个Node<Integer>
.尝试将 a 分配String
给 anInteger
会导致ClassCastException
来自 Java 编译器在分配时插入的强制转换。