使用 Java 泛型进行实验

问题描述 投票:0回答:2

我在玩一点java泛型,我遇到了这段代码,我很困惑为什么会这样。

我将第二个参数

K
作为
Integer
传递,并且在泛型方法中我将
float
转换为我的
K
类型,并且在
main()
中我将其接收为
Integer
,

在我的代码检查器中,我看到

Float
数字完全位于我的列表中(在转换为
Integer
后没有被截断),它是
Integer
类型,但是当我尝试选择元素将其保存在
Integer
中时它给出的变量
ClassCastException
.

有人可以解释一下泛型出了什么问题吗,这样它并不能让我们免于抛出异常。

注意:当我从签名中删除第二个参数

K
时,我就遇到了这种情况,因此不会有任何定义类型的
K
,在这种情况下,我认为Java会使其成为
Object
,然后我们可能会被强制转换例外,但为什么在这种情况下,当我也传递
K
类型时。

import java.util.ArrayList;
import java.util.List;

public class IntegerPrinter {

    Integer item;
    
    public void  print() {
        System.out.println(item);
    }
    
    public <T,K> List<K>  anyPrint(List<T> num,K lo) {
        List<K> mylist = new ArrayList<>();
        mylist.add( (K) new Float(2.99f));
        return mylist;
    }

    public  IntegerPrinter(Integer item) {
        this.item = item;
    }
}
import java.util.ArrayList;
import java.util.List;

public class GenericsInAction {

    public static void main(String[] args) {
        IntegerPrinter oldPrinter = new IntegerPrinter(188);
        oldPrinter.print();
        List<Integer> dates = oldPrinter.anyPrint(new ArrayList<Integer>(),7);
        Integer x = dates.get(0);
        
        
    }
}
java generics casting
2个回答
1
投票

我将代码压缩为基本部分,并对其进行了轻微修改以突出显示重要的行为:

class Ideone {
  public static void main(String[] args) {
    List<Integer> dates = new IntegerPrinter().anyPrint(7);
    System.out.println(dates.get(0)); // succeeds
    Integer x = dates.get(0);         // Line 8, throws
  }
}

class IntegerPrinter {
  public <K> List<K> anyPrint(K lo) {
    List<K> mylist = new ArrayList<>();
    mylist.add((K) Float.valueOf(2.99f));
    return mylist;
  }
}

执行时,该程序将产生以下输出:

2.99
Exception in thread "main" java.lang.ClassCastException: class java.lang.Float cannot be cast to class java.lang.Integer (java.lang.Float and java.lang.Integer are in module java.base of loader 'bootstrap')
    at Ideone.main(Main.java:8)

Ideone.com
演示

现在,让我们逐步浏览代码并尝试了解发生了什么。

这一行:

mylist.add((K) new Float(2.99f));

基本上告诉编译器“不关心类型,我们(作为程序员)保证它是一个

K
,将其视为
K
”。

然后,如果我们深入挖掘,我们会发现

ArrayList
使用
Object[]
作为支持数据结构
。所以这里有一个问题,背衬
Object[] elementData
可以存储所有东西。

当我们开始检索元素时,事情变得很奇怪。 JLS 对于这些情况下的类型断言有些模糊(我认为它们包含在§5.1.5§5.1.6.3下,但我不完全确定)。它基本上是说“编译器必须断言类型,但仅在必要时”。

因此,如果我们从

List<Integer>
中检索一个元素,那么它显然不是
Integer
,而是传递给可以处理
Object
的方法,则不需要类型断言。这里的情况正是如此:

System.out.println(dates.get(0));

System.out
中最接近的签名匹配是
println(Object)
方法。这就是 JLS 中的情况,§5.1.5:扩大转换,它永远不会抛出。

另一方面,如果我们现在尝试检索

Integer
并尝试将其存储在
Integer
中:

Integer x = dates.get(0);

现在,类型检查已经到位。事实上,如果我们检查程序的输出,我们会看到发生了

System.out.println(...)
,但是对
int
变量的赋值是触发
ClassCastException
的语句。这是 JLS,§5.1.6.3 中描述的情况:运行时的缩小转换(来自
ArrayList
elementData(int)
方法
)。


脚注

泛型无疑是 JLS 中最复杂和令人困惑的部分之一,即使不是最复杂和令人困惑的部分。我尽最大努力在相关部分引用 JLS,这可能会被错误引用。我也知道这个问题以前被问过,但我找不到重复的问题。


0
投票
ArrayList

是泛型类型,其类型擦除为

java.lang.Object
,因此这就是列表中存储的内容。您可以将类型擦除视为运行时类型,即“真实”类型,而不是编译器知道的编译时类型。程序运行时,
ArrayList
中可以存储任何类型。
恰好 

anyPrint

中 K 的类型擦除也是

java.lang.Object
,因为类型 K 没有界限。该方法对于所有用途都编译一次,并且它必须能够接受任何类型K. 因此,当编译
anyPrint
的代码时,
mylist.add( (K) new Float(2.99f));
行中对 K 的强制转换将被忽略,因为 K 的类型擦除是
java.lang.Object
。投射到
java.lang.Object
是无用且毫无意义的。它编译为
mylist.add(new Float(2.99f));
并且代码将
java.lang.Float
类型的对象插入到
java.lang.Object
类型的列表中。
此外,Java 中对对象类型的强制转换只是确保对象具有正确的类型,它不会更改对象的值,就像对基本类型的强制转换一样。所以你没有理由相信 2.99f 的值会改变。

GenericsInAction

是单独编译的。

K 的参数化类型是 

java.lang.Integer

main
方法中的
GenericsInAction
,因为你传入的
7
会通过自动装箱转换为
java.lang.Integer
,以兼容
 的类型擦除java.lang.Object
anyPrint
。因此,当编译该
main
方法时,编译器会在调用
dates.get
之后插入一个运行时检查(即检查转换),该检查确保对
dates.get(0);
日期的调用返回类型为
 的对象java.lang.Integer
,因为 K 的类型必须是
java.lang.Integer
内部
main
由于您将 

java.lang.Float

插入到列表中,因此运行时检查失败并抛出

ClassCastException
    

© www.soinside.com 2019 - 2024. All rights reserved.