使用与从通用集合中定义的对象不同的对象
Consuming different objects than defined from a generic collection
我昨天在回答 问题时发现了一些很奇怪的事情。
我可以将集合定义为 O
类型,即使它的实际类型是 T
。
这可能是因为我使用的是原始类型。
然而,最令人惊讶的部分是我能够从这个 List<FileInputStream>
的集合中消费,尽管很明显它是 List<Integer>
.
下面是有问题的代码
public static void main(String[] args) {
String value = "a string with numb3r5";
Function<String, List<String>> fn1 = List::of;
Function<List<String>, String> fn2 = x -> x.get(0);
Function<String, List<Integer>> fn3 = x -> List.of(x.length());
InputConverter<String> converter = new InputConverter<>(value);
List<FileInputStream> 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());
}
public static class InputConverter<T> {
private final T src;
public InputConverter(T src) {
this.src = src;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public <R> R convertBy(Function... functions) {
Function functionsChain = Function.identity();
for (Function function : functions) {
functionsChain = functionsChain.andThen(function);
}
return (R) functionsChain.apply(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)
为什么可以从这个集合中消费?
我注意到 follwing 在使用它时不会抛出异常
System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());
但是以下情况
System.out.println("ints.get(0).toString() = " + ints.get(0).toString());
泛型不是 java 类 的二进制表示的一部分。泛型是通过强制转换实现的。
“Behind the scenes, the compiler replaces all references to T in Crate
with Object. In other words, after the code compiles, your generics
are actually just Object types. ” Excerpt From: Jeanne Boyarsky. “OCP
Oracle Certified Professional Java SE 11 Developer Complete Study
Guide”. Apple Books.
这意味着编译器会确保 convertBy return 列表,但无法确认所有类型。但是编译器会替换:
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());
结论:
如果我们想要类型安全的代码,我们必须确保我们不使用原始类型。
InputConverter#convertBy
使用原始类型 Function
声明作为其输入,其 return 类型使用泛型类型推断 <R>
因此它将解析为分配的类型,在你的情况下它将是 List<FileInputStream>
.
也就是说,您可以将转换后的类型,即 InputConverter#convertBy
的结果分配给您想要的任何类型:List<FileInputStream>
、List<HashSet<FileInputStream>>
、List<Charset>
.. . 只要它与转换类型的结果(最后一个函数结果)在运行时兼容,您仍然不会出错,直到在运行时,JVM 将执行 implicit cast在您的情况下,收集擦除类型是 FileInputStream
并且会因为实际类型是 Integer
.
而失败
下面的语句不会失败,因为您将集合元素向上转换为 Object
超类型并从 java.lang.Object
类型调用 #toString
方法(您是在访问任何成员/方法之前转换变量):
System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());
下面的语句实际上与前面的语句相似:
System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
给定的Object#valueOf
方法实现如下:
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());
我昨天在回答
我可以将集合定义为 O
类型,即使它的实际类型是 T
。
这可能是因为我使用的是原始类型。
然而,最令人惊讶的部分是我能够从这个 List<FileInputStream>
的集合中消费,尽管很明显它是 List<Integer>
.
下面是有问题的代码
public static void main(String[] args) {
String value = "a string with numb3r5";
Function<String, List<String>> fn1 = List::of;
Function<List<String>, String> fn2 = x -> x.get(0);
Function<String, List<Integer>> fn3 = x -> List.of(x.length());
InputConverter<String> converter = new InputConverter<>(value);
List<FileInputStream> 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());
}
public static class InputConverter<T> {
private final T src;
public InputConverter(T src) {
this.src = src;
}
@SuppressWarnings({"unchecked", "rawtypes"})
public <R> R convertBy(Function... functions) {
Function functionsChain = Function.identity();
for (Function function : functions) {
functionsChain = functionsChain.andThen(function);
}
return (R) functionsChain.apply(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)
为什么可以从这个集合中消费?
我注意到 follwing 在使用它时不会抛出异常
System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());
但是以下情况
System.out.println("ints.get(0).toString() = " + ints.get(0).toString());
泛型不是 java 类 的二进制表示的一部分。泛型是通过强制转换实现的。
“Behind the scenes, the compiler replaces all references to T in Crate with Object. In other words, after the code compiles, your generics are actually just Object types. ” Excerpt From: Jeanne Boyarsky. “OCP Oracle Certified Professional Java SE 11 Developer Complete Study Guide”. Apple Books.
这意味着编译器会确保 convertBy return 列表,但无法确认所有类型。但是编译器会替换:
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());
结论: 如果我们想要类型安全的代码,我们必须确保我们不使用原始类型。
InputConverter#convertBy
使用原始类型 Function
声明作为其输入,其 return 类型使用泛型类型推断 <R>
因此它将解析为分配的类型,在你的情况下它将是 List<FileInputStream>
.
也就是说,您可以将转换后的类型,即 InputConverter#convertBy
的结果分配给您想要的任何类型:List<FileInputStream>
、List<HashSet<FileInputStream>>
、List<Charset>
.. . 只要它与转换类型的结果(最后一个函数结果)在运行时兼容,您仍然不会出错,直到在运行时,JVM 将执行 implicit cast在您的情况下,收集擦除类型是 FileInputStream
并且会因为实际类型是 Integer
.
下面的语句不会失败,因为您将集合元素向上转换为 Object
超类型并从 java.lang.Object
类型调用 #toString
方法(您是在访问任何成员/方法之前转换变量):
System.out.println("((Object) ints.get(0)).toString() = " + ((Object) ints.get(0)).toString());
下面的语句实际上与前面的语句相似:
System.out.println("String.valueOf(ints.get(0)) = " + String.valueOf(ints.get(0)));
给定的Object#valueOf
方法实现如下:
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());