Java 使用不同于从泛型集合定义的对象

Java 使用不同于从泛型集合定义的对象,java,generics,type-inference,Java,Generics,Type Inference,昨天我在回答问题的时候发现了一些很奇怪的事情 我可以将集合定义为O类型,即使它的实际类型是T。 这可能是因为我使用的是原始类型 然而,最令人惊讶的是,我能够从这个集合中消费,它是一个列表,尽管它显然是一个列表 下面是相关的代码 publicstaticvoidmain(字符串[]args){ String value=“带有numb3r5的字符串”; 函数fn1=列表::of; 函数fn2=x->x.get(0); 函数fn3=x->List.of(x.length()); InputConve

昨天我在回答问题的时候发现了一些很奇怪的事情

我可以将集合定义为
O
类型,即使它的实际类型是
T
。 这可能是因为我使用的是原始类型

然而,最令人惊讶的是,我能够从这个集合中消费,它是一个
列表
,尽管它显然是一个
列表

下面是相关的代码

publicstaticvoidmain(字符串[]args){
String value=“带有numb3r5的字符串”;
函数fn1=列表::of;
函数fn2=x->x.get(0);
函数fn3=x->List.of(x.length());
InputConverter converter=新的InputConverter(值);
List ints=converter.convertBy(fn1、fn2、fn3);
System.out.println(“ints=“+ints”);
System.out.println(“ints.get(0)=”+ints.get(0));
System.out.println(“ints.get(0.getClass()=”+ints.get(0.getClass());
}
公共静态类输入转换器{
私人终审法院;
公共输入转换器(src){
this.src=src;
}
@SuppressWarnings({“unchecked”,“rawtypes”})
公共R转换器(功能…功能){
Function functionsChain=Function.identity();
for(函数:函数){
functionsChain=functionsChain.然后(函数);
}
返回(R)函数调用(src);
}
}
结果如下

ints = [21]
ints.get(0) = 21
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.io.FileInputStream (java.lang.Integer and java.io.FileInputStream are in module java.base of loader 'bootstrap')
    at Scratch.main(scratch_8.java:18)
为什么可以从这个集合中消费

我注意到以下内容:在消费时不要抛出异常

System.out.println(“String.valueOf(ints.get(0))=”+String.valueOf(ints.get(0));
System.out.println(“((Object)int.get(0)).toString()=”+((Object)int.get(0)).toString());
然而,以下情况确实如此

System.out.println(“ints.get(0.toString()=”+ints.get(0.toString());

泛型不是java类二进制表示的一部分。泛型是通过casting实现的

“在幕后,编译器替换板条箱中对T的所有引用 换句话说,在代码编译之后,泛型 实际上只是对象类型。”摘自:珍妮·博亚斯基。“OCP Oracle认证专业Java SE 11开发人员完整研究 指南”。苹果书

这意味着编译器将确保convertBy返回列表,但不能确认所有类型。但编译器将替换:
ints.get(0).toString()
((FileInputStream)ints.get(0)).toString())
配合使用,以备需要

类似的例子是,具有错误泛型参数的强制转换列表将产生相同的异常

    List list = List.of("text");
    List<Integer> numbers = (List<Integer>) list;
    System.out.println(numbers.get(0).longValue());
List List=List.of(“文本”);
列表编号=(列表)列表;
System.out.println(numbers.get(0.longValue());
结论: 如果我们想使用类型安全代码,我们必须确保不使用原始类型。

输入转换器#convertBy使用原始类型
函数
声明作为输入,其返回类型使用泛型类型推断
,因此它将解析为指定的类型,在您的情况下,它将是
列表

也就是说,您可以将转换后的类型,即,
InputConverter\convertBy
的结果分配给您想要的任何类型:
List
List
List
。。。只要它在运行时与转换类型(最后一个函数结果)的结果兼容,您仍然不会得到任何错误,直到在运行时,JVM将对集合擦除类型执行隐式强制转换(在您的情况下为
FileInputStream
),并且由于实际类型为
Integer
而失败

下面的语句不会失败,因为您将集合元素向上强制转换为
对象
超类型,并从
java.lang.Object
类型调用
#toString
方法(您在访问任何成员/方法之前强制转换变量):

下面的陈述与前面的陈述几乎相似:

System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
给定
对象#值
方法实现如下:

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}
另一方面,下面的语句将失败,因为
ints.get(0)
将在访问其
#toString
成员之前强制转换(checkcast)到已擦除的运行时类型,即
FileInputStream

System.out.println("ints.get(0).toString() = " + ints.get(0).toString());
你可以这样想:

System.out.println("ints.get(0).toString() = " + ((FileInputStream) ints.get(0)).toString());

你的问题到底是什么?@Turing85我刚刚编辑了它,在最后添加了一个问题。“为什么可以从这个集合中消费*”,因为
+
是为
(字符串、对象)
对定义的(即列表元素的确切类型无关紧要)。但是如果我们在假设is是
FileInputStream
的情况下调用list元素上的某个方法,那么它的类型必须通过类型转换来确定。好的,但是当我尝试
ints.get(0).toString()
时,我得到了一个
ClassCastException
,即使它是
对象中的一个方法。。。因为您以实际的
FileInputStream
访问列表元素(这是列表的泛型类型断言的,请记住:要调用的方法由参数的静态类型决定),并且断言必须由类型转换强制执行。这就是为什么如果我们替换
System.out.println(“ints.get(0.getClass()=”+ints.get(0.getClass())),原始程序可以工作的原因带有
对象o=int.get(0);System.out.println(“ints.get(0.getClass()=”+o.getClass())()。
System.out.println("ints.get(0).toString() = " + ((FileInputStream) ints.get(0)).toString());