Java и Kotlin кастинг с дженериками. Потеря безопасности

При кодировании в Kotlin / Java я наткнулся на что-то довольно странное при использовании кастингов и дженериков. Кажется, что система типа считает, что список имеет тип List<Foo> , в то время как это фактически List<Object> .

Может ли кто-нибудь объяснить мне, почему это возможно?

Вот пример и в Kotlin, и в Java:

Пример в Котлине

 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); } } 

Java не имеет повторных дженериков. То есть, общая информация не существует во время выполнения, и весь общий код «упрощается» процессом, называемым стиранием. Компилятор бросает броски, когда известно, что общие типы обеспечивают правильность. Вы не можете применять к родовому типу, тогда как общие типы не существуют достаточно для среды выполнения, чтобы узнать, является ли значение одним или нет, и именно поэтому 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 , но с уровнем косвенности.

Компилятор выдает предупреждение, когда выполняется непроверенная передача в T. Если в вашем программном обеспечении есть такое предупреждение, безопасность не гарантируется. Таким образом, поведение, которое вы видите, ожидается.

Попробуйте изменить второй тест следующим образом:

  Double y = test2(1.0); System.out.println(y); 

В вашем коде test2(1) аргумент 1 автобоксирован в Integer , которое нельзя отнести в Double .

Intereting Posts