Возможное загрязнение кучи через параметр varargs

433

Я понимаю, что это происходит с Java 7 при использовании varargs с универсальным типом;

Но мой вопрос ..

Что именно означает «Затмение», когда говорит, что «его использование может привести к загрязнению кучи?»

А также

Как новая @SafeVarargsаннотация предотвращает это?

Герцшпрунга
источник
8
Подробности здесь: docs.oracle.com/javase/specs/jls/se7/html/…
assylias
Я вижу это в моем редакторе:Possible heap pollution from parameterized vararg type
Александр Миллс

Ответы:

252

Загрязнение кучи - это технический термин. Это относится к ссылкам, которые имеют тип, который не является супертипом объекта, на который они указывают.

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

Это может привести к "необъяснимым" ClassCastExceptionс.

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargsне мешает этому вообще. Однако есть методы, которые доказуемо не будут загрязнять кучу, компилятор просто не может этого доказать. Раньше вызывающие такие API получали раздражающие предупреждения, которые были совершенно бессмысленными, но их нужно было подавлять на каждом сайте вызовов. Теперь автор API может отключить его один раз на сайте декларации.

Однако, если метод на самом деле не безопасно, пользователи больше не будут предупреждены.

Бен Шульц
источник
2
Итак, мы говорим, что куча загрязнена, потому что она содержит ссылки, типы которых не соответствуют нашим ожиданиям? (Список <A> против Списка <B> в вашем примере)
hertzsprung
30
Этот ответ является прекрасным объяснением того, что такое загрязнение кучи, но на самом деле он не объясняет, почему varargs с такой вероятностью могут вызвать его, чтобы оправдать конкретное предупреждение.
Dolda2000
4
Мне тоже не хватает информации о том, как убедиться, что мой код не содержит этой проблемы (например, как я узнаю, что он достаточно защищен, чтобы добавить @SafeVarargs)
Даниэль Алдер,
238

Когда вы объявляете

public static <T> void foo(List<T>... bar) компилятор преобразует его в

public static <T> void foo(List<T>[] bar) затем к

public static void foo(List[] bar)

Тогда возникает опасность, что вы ошибочно назначите неверные значения в список, и компилятор не вызовет никаких ошибок. Например, если Tis, Stringто следующий код скомпилируется без ошибок, но завершится с ошибкой во время выполнения:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

Если вы проверили метод, чтобы убедиться, что он не содержит таких уязвимостей, вы можете аннотировать его, @SafeVarargsчтобы подавить предупреждение. Для интерфейсов используйте @SuppressWarnings("unchecked").

Если вы получили это сообщение об ошибке:

Метод Varargs может вызвать загрязнение кучи из-за неперечисляемого параметра varargs

и вы уверены, что ваше использование безопасно, тогда вы должны использовать @SuppressWarnings("varargs")вместо этого. См. @SafeVarargs соответствующую аннотацию для этого метода? и https://stackoverflow.com/a/14252221/14731 для хорошего объяснения этого второго вида ошибки.

Ссылки:

Гили
источник
2
Я думаю, что я понимаю лучше. Опасность наступает, когда вы применяете варагги к Object[]. До тех пор, пока вы не бросаете Object[], это звучит так, как будто вы должны быть в порядке.
djeikyb
3
В качестве примера глупой вещи , которую вы могли бы сделать: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. А потом позвони bar(Arrays.asList(1,2));.
djeikyb
1
@djeikyb, если опасность возникает, только если я приведу, Object[]почему компилятор выдает предупреждение, если я этого не сделаю? В конце концов, это должно быть довольно легко проверить во время компиляции (в случае, если я не передаю его другой функции с аналогичной сигнатурой, в этом случае другая функция должна вызвать предупреждение). Я не верю, что это действительно суть предупреждения («Ты в безопасности, если не произносишь»), и я до сих пор не понимаю, в каком случае я в порядке.
Qw3ry
5
@djeikyb Вы можете сделать точно такую ​​же глупость без параметризованных переменных (например bar(Integer...args)). Так в чем же смысл этого предупреждения?
Василий Власов
3
@VasiliyVlasov Эта проблема относится только к параметризованным переменным. Если вы попытаетесь сделать то же самое с нетипизированными массивами, среда выполнения не позволит вам вставить неправильный тип в массив. Компилятор предупреждает вас, что среда выполнения не сможет предотвратить некорректное поведение, поскольку тип параметра неизвестен во время выполнения (напротив, массивы действительно знают тип своих неуниверсальных элементов во время выполнения).
Гили
8

@SafeVarargs не препятствует этому, однако требует, чтобы компилятор был более строгим при компиляции кода, который его использует.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html объясняет это более подробно.

Загрязнение кучи происходит, когда вы получаете, ClassCastExceptionкогда выполняете операцию с универсальным интерфейсом, и оно содержит другой тип, чем объявленный.

jontro
источник
Дополнительные ограничения компилятора на его использование не кажутся особенно актуальными.
Пол Беллора
6

Когда вы используете varargs, это может привести к созданию Object[]аргумента для хранения аргументов.

Благодаря экранированию, JIT может оптимизировать создание этого массива. (Один из немногих случаев, когда я обнаружил, что это так). Его нельзя оптимизировать, но я не буду беспокоиться об этом, если вы не обнаружите проблему в профилировщике памяти.

AFAIK @SafeVarargsподавляет предупреждение от компилятора и не меняет поведение JIT.

Питер Лори
источник
6
Интересно, что это не отвечает на его вопрос @SafeVarargs.
Пол Беллора
1
Нет. Это не загрязнение кучи. «Загрязнение кучи происходит, когда переменная параметризованного типа относится к объекту, который не относится к этому параметризованному типу». Ссылка: docs.oracle.com/javase/tutorial/java/generics/…
Дорадус
1

Причина в том, что varargs дают возможность вызываться с непараметризованным массивом объектов. Так что, если ваш тип был List <A> ..., он также может быть вызван с типом List [] не-varargs.

Вот пример:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

Как видите, List [] b может содержать любой тип потребителя, и все же этот код компилируется. Если вы используете varargs, то у вас все хорошо, но если вы используете определение метода после стирания типа - void test (List []) - тогда компилятор не будет проверять типы параметров шаблона. @SafeVarargs отключит это предупреждение.

user1122069
источник