Варварское загрязнение кучи: какая большая сделка?
Я читал о varargs heap загрязнения, и я действительно не понимаю, как varargs или невосстанавливаемые типы будут отвечать за проблемы, которые еще не существуют без типичности, Действительно, я могу легко заменить
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42);
String s = l[0].get(0); // ClassCastException thrown here
}
с
public static void faultyMethod(String... l) {
Object[] objectArray = l; // Valid
objectArray[0] = 42; // ArrayStoreException thrown here
String s = l[0];
}
Второй просто использует ковариацию массивов, что действительно является проблемой здесь. (Даже если List<String>
был reifiable, я думаю, он все равно будет подклассом Object
, и я все равно мог бы назначить какой-либо объект для массива.) Конечно, я вижу там небольшую разницу между ними, но этот код неисправен, использует ли он дженерики или нет.
Что они подразумевают под загрязнением кучи (это заставляет меня задуматься об использовании памяти, но единственная проблема, о которой они говорят, это потенциальная небезопасность типа) и как она отличается от любого нарушения типа с использованием ковариации массивов?
Ответы
Ответ 1
Вы правы, что общая (и фундаментальная) проблема связана с ковариацией массивов. Но из этих двух примеров, которые вы дали, первый из них более опасен, потому что может модифицировать ваши структуры данных и помещать их в состояние, которое будет ломаться значительно позже.
Рассмотрим, если ваш первый пример не вызвал ClassCastException:
public static void faultyMethod(List<String>... l) {
Object[] objectArray = l; // Valid
objectArray[0] = Arrays.asList(42); // Also valid
}
И вот как кто-то использует его:
List<String> firstList = Arrays.asList("hello", "world");
List<String> secondList = Arrays.asList("hello", "dolly");
faultyMethod(firstList, secondList);
return secondList.isEmpty()
? firstList
: secondList;
Итак, теперь мы имеем List<String>
, который фактически содержит Integer
, и он плавно перемещается. Через некоторое время? возможно, намного позже, и если он будет сериализован, возможно, намного позже и в другой JVM-среде; кто-то наконец выполняет String s = theList.get(0)
. Этот провал настолько далек от того, что вызвало его, что его было очень сложно отследить.
Обратите внимание, что трассировка стека ClassCastException не говорит нам, где действительно произошла ошибка; это просто говорит нам, кто его вызвал. Другими словами, это не дает нам много информации о том, как исправить ошибку; и что делает его более крупным, чем исключение ArrayStoreException.
Ответ 2
Разница между массивом и списком заключается в том, что массив проверяет его ссылки. например.
Object[] array = new String[1];
array[0] = new Integer(1); // fails at runtime.
Однако
List list = new ArrayList<String>();
list.add(new Integer(1)); // doesn't fail.
Ответ 3
Из связанного документа я считаю, что Oracle означает "загрязнение кучи" - это иметь значения данных, которые технически разрешены спецификацией JVM, но запрещены правилами для генериков на языке программирования Java.
Чтобы дать вам пример, скажем, мы определяем простой контейнер List
следующим образом:
class List<E> {
Object[] values;
int len = 0;
List() { values = new Object[10]; }
void add(E obj) { values[len++] = obj; }
E get(int i) { return (E)values[i]; }
}
Это пример кода, который является общим и безопасным:
List<String> lst = new List<String>();
lst.add("abc");
Это пример кода, который использует необработанные типы (обход генераторов), но по-прежнему уважает безопасность типов на семантическом уровне, поскольку добавленное нами значение имеет совместимый тип:
String x = (String)lst.values[0];
Твист - теперь вот код, который работает с сырыми типами и делает что-то плохое, вызывая "загрязнение кучи":
lst.values[lst.len++] = new Integer("3");
Этот код работает, потому что массив имеет тип Object[]
, который может хранить Integer
. Теперь, когда мы попытаемся получить значение, это приведет к появлению ClassCastException
- времени поиска (что происходит после возникновения коррупции) вместо времени добавления:
String y = lst.get(1); // ClassCastException for Integer(3) -> String
Обратите внимание, что ClassCastException
происходит в нашем текущем фрейме стека, даже не в List.get()
, потому что приведение в List.get()
не работает во время выполнения из-за стирающей системы типа Java.
В принципе, мы вставили Integer
в List<String>
путем обхода дженериков. Затем, когда мы попытались get()
создать элемент, объект списка не смог подтвердить свое обещание, что он должен вернуть String
(или null
).
Ответ 4
До родословных не было абсолютно никакой возможности, чтобы тип времени выполнения объекта не соответствовал его статическому типу. Это, очевидно, очень желательное свойство.
Мы можем применить объект к некорректному типу времени выполнения, но приведение в литье немедленно произойдет, на точном сайте кастинга; ошибка останавливается.
Object obj = "string";
((Integer)obj).intValue();
// we are not gonna get an Integer object
С введением дженериков наряду с стиранием типа (корень всех зол) теперь возможно, что метод возвращает String
во время компиляции, но возвращает Integer
во время выполнения. Это испорчено. И мы должны сделать все возможное, чтобы остановить его из источника. Именно поэтому компилятор настолько вокал о каждом виде непроверенных бросков.
Самое худшее в загрязнении кучи заключается в том, что поведение во время работы undefined! Различные компилятор/среда выполнения могут выполнять программу по-разному. См. case1 и case2
Ответ 5
Они различны, потому что ClassCastException
и ArrayStoreException
отличаются.
Общие правила проверки типа компиляции должны гарантировать, что невозможно получить ClassCastException
в месте, где вы не поместили явное приведение, если только ваш код (или какой-либо код, который вы вызывали или не вызывали вас) не делал что-то небезопасное во время компиляции, и в этом случае вы должны (или независимо от того, что код сделал небезопасную вещь), получать предупреждение об этом во время компиляции.
ArrayStoreException
, с другой стороны, является нормальной частью того, как массивы работают на Java, и пред-даты Generics. Невозможно, чтобы проверка типа компиляции предотвращала ArrayStoreException
из-за того, как система типов для массивов сконструирована в Java.