Java & 使用泛型的 Kotlin 转换。失去类型安全
Java & Kotlin casting with generics. Losing typesafety
在 Kotlin/Java 中编码时,我在使用转换和泛型时偶然发现了一些相当奇怪的东西。
似乎可以让类型系统相信列表是 List<Foo>
类型,而实际上它是 List<Object>
.
任何人都可以向我解释为什么这是可能的吗?
以下是 Kotlin 和 Java 问题的示例:
Kotlin 示例
fun <T> test(obj: Any): List<T> {
val ts = ArrayList<T>()
ts.add(obj as T)
return ts
}
fun <T> test2(obj: Any): T {
return obj as T
}
fun <T> test3(obj: Any): List<T> {
val ts = ArrayList<T>()
ts.add(test2(obj))
return ts
}
fun main(args: Array<String>) {
val x = test<Double>(1) // Returns a list of Integers and doesn't error
println(x)
val y = test2<Double>(1) // Casts the Int object to a Double.
println(y)
val z = test3<Double>(1) // Returns a list of Integers and doesn't error.
println(z)
}
Java
中的示例
public class Test {
public static <T> List<T> test(Object obj){
ArrayList<T> ts = new ArrayList<>();
ts.add((T) obj);
return ts;
}
public static <T> T test2(Object obj){
return (T) obj;
}
public static <T> List<T> test3(Object obj){
ArrayList<T> ts = new ArrayList<>();
ts.add(test2(obj));
return ts;
}
public static void main(String[] args) {
List<Double> x = test(1); // Returns a list of Integers and doesn't error
System.out.println(x);
// Double y = test2(1); // Errors in java an Integers cannot be converted into a Double.
// System.out.println(y);
List<Double> z = test3(1); // Returns a list of Integers and doesn't error.
System.out.println(z);
}
}
编译器在执行未经检查的 T 转换时发出警告。
当您的程序中出现此类警告时,类型安全性得不到保证。所以你看到的行为是意料之中的。
尝试像这样更改第二个测试:
Double y = test2(1.0);
System.out.println(y);
在您的代码 test2(1)
中,参数 1
被自动装箱为 Integer
,无法转换为 Double
。
Java 没有具体化的泛型。也就是说,泛型信息在运行时不存在,所有泛型代码都是"simplified" 被称为擦除的过程。当已知泛型类型以确保正确性时,编译器会抛出强制转换。你不能转换为泛型类型,因为泛型类型的存在不足以让运行时知道一个值是否为 1,这就是为什么 javac
对你这样做大喊大叫,因为它知道你要求 JVM 做一些它不可能做的事情,引入运行时不安全。
public class Test {
public static List test(Object obj) { // generic types => erasure = raw types
ArrayList ts = new ArrayList();
ts.add(obj); // No cast: List.add has erasure (Ljava.lang.Object;)V
return ts;
}
public static Object test2(Object obj) { // T is unbounded => erasure = Object
return obj; // No cast: all types <: Object
}
public static List test3(Object obj) {
ArrayList ts = new ArrayList();
ts.add(test2(obj)); // Note: we don't know what T is, so we can't cast to it and ensure test2 returned one.
return ts;
}
public static void main(String[] args) {
List x = test(1); // Returns a list and doesn't error
System.out.println(x);
Double y = (Double) test2(1); // Errors in java as an Integer cannot be converted into a Double
// This is because the compiler needs to insert casts to make generics work
System.out.println(y);
List z = test3(1);
// Unlike y, there isn't a cast in test3 because test3 doesn't know what T is, so the Integer passes through, uncast, into a List<Double>.
// The JVM can't detect this, because it doesn't even know what a List<Double> is.
System.out.println(z);
}
}
注意 test2
如何擦除到美化的身份函数,导致 test3
做与 test1
完全相同的事情,但有一定程度的间接性。
在 Kotlin/Java 中编码时,我在使用转换和泛型时偶然发现了一些相当奇怪的东西。
似乎可以让类型系统相信列表是 List<Foo>
类型,而实际上它是 List<Object>
.
任何人都可以向我解释为什么这是可能的吗?
以下是 Kotlin 和 Java 问题的示例:
Kotlin 示例
fun <T> test(obj: Any): List<T> {
val ts = ArrayList<T>()
ts.add(obj as T)
return ts
}
fun <T> test2(obj: Any): T {
return obj as T
}
fun <T> test3(obj: Any): List<T> {
val ts = ArrayList<T>()
ts.add(test2(obj))
return ts
}
fun main(args: Array<String>) {
val x = test<Double>(1) // Returns a list of Integers and doesn't error
println(x)
val y = test2<Double>(1) // Casts the Int object to a Double.
println(y)
val z = test3<Double>(1) // Returns a list of Integers and doesn't error.
println(z)
}
Java
中的示例public class Test {
public static <T> List<T> test(Object obj){
ArrayList<T> ts = new ArrayList<>();
ts.add((T) obj);
return ts;
}
public static <T> T test2(Object obj){
return (T) obj;
}
public static <T> List<T> test3(Object obj){
ArrayList<T> ts = new ArrayList<>();
ts.add(test2(obj));
return ts;
}
public static void main(String[] args) {
List<Double> x = test(1); // Returns a list of Integers and doesn't error
System.out.println(x);
// Double y = test2(1); // Errors in java an Integers cannot be converted into a Double.
// System.out.println(y);
List<Double> z = test3(1); // Returns a list of Integers and doesn't error.
System.out.println(z);
}
}
编译器在执行未经检查的 T 转换时发出警告。 当您的程序中出现此类警告时,类型安全性得不到保证。所以你看到的行为是意料之中的。
尝试像这样更改第二个测试:
Double y = test2(1.0);
System.out.println(y);
在您的代码 test2(1)
中,参数 1
被自动装箱为 Integer
,无法转换为 Double
。
Java 没有具体化的泛型。也就是说,泛型信息在运行时不存在,所有泛型代码都是"simplified" 被称为擦除的过程。当已知泛型类型以确保正确性时,编译器会抛出强制转换。你不能转换为泛型类型,因为泛型类型的存在不足以让运行时知道一个值是否为 1,这就是为什么 javac
对你这样做大喊大叫,因为它知道你要求 JVM 做一些它不可能做的事情,引入运行时不安全。
public class Test {
public static List test(Object obj) { // generic types => erasure = raw types
ArrayList ts = new ArrayList();
ts.add(obj); // No cast: List.add has erasure (Ljava.lang.Object;)V
return ts;
}
public static Object test2(Object obj) { // T is unbounded => erasure = Object
return obj; // No cast: all types <: Object
}
public static List test3(Object obj) {
ArrayList ts = new ArrayList();
ts.add(test2(obj)); // Note: we don't know what T is, so we can't cast to it and ensure test2 returned one.
return ts;
}
public static void main(String[] args) {
List x = test(1); // Returns a list and doesn't error
System.out.println(x);
Double y = (Double) test2(1); // Errors in java as an Integer cannot be converted into a Double
// This is because the compiler needs to insert casts to make generics work
System.out.println(y);
List z = test3(1);
// Unlike y, there isn't a cast in test3 because test3 doesn't know what T is, so the Integer passes through, uncast, into a List<Double>.
// The JVM can't detect this, because it doesn't even know what a List<Double> is.
System.out.println(z);
}
}
注意 test2
如何擦除到美化的身份函数,导致 test3
做与 test1
完全相同的事情,但有一定程度的间接性。