Можливе забруднення купи за допомогою параметра varargs


433

Я розумію, це відбувається з Java 7 під час використання варагрів із загальним типом;

Але моє питання:

Що саме означає Eclipse, коли йдеться про те, що "його використання може потенційно забруднити купу?"

І

Як нова @SafeVarargsанотація запобігає цьому?




Я бачу це у своєму редакторі: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
Ця відповідь є чітким поясненням того, що таке забруднення купи, але насправді це не пояснює, чому вараги настільки особливо ймовірні, що можуть викликати це, щоб вимагати конкретного попередження.
Dolda2000

4
Мені теж не вистачає інформації, як переконатися, що мій код не містить цієї проблеми (наприклад, як я знаю, що досить загартований, щоб додати @SafeVarargs)
Daniel Alder

237

Коли ви заявляєте

public static <T> void foo(List<T>... bar) компілятор перетворює його в

public static <T> void foo(List<T>[] bar) то до

public static void foo(List[] bar)

Тоді виникає небезпека, що ви помилково призначите неправильні значення у списку, і компілятор не спровокує жодної помилки. Наприклад, якщо Tє, 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[], це здається, що вам слід добре.
джейкіб

3
В якості прикладу дурною речі , яку ви могли б зробити: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. А потім дзвоніть bar(Arrays.asList(1,2));.
джейкіб

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операцію на загальному інтерфейсі, і вона містить інший тип, ніж заявлений.


Додаткові обмеження компілятора щодо його використання не здаються особливо актуальними.
Пол Беллора

6

Якщо ви використовуєте varargs, це може призвести до створення Object[]аргументу для проведення аргументів.

Завдяки аналізу втечі JIT може оптимізувати створення цього масиву. (Один з небагатьох разів, коли я виявив це так) Не гарантовано оптимізувати його, але я б не хвилювався з цього приводу, якщо ви не побачите її проблему у своєму профілі пам'яті.

AFAIK @SafeVarargsпригнічує попередження компілятора і не змінює поведінку JIT.


6
Цікаво, хоча він насправді не відповідає на його запитання @SafeVarargs.
Пол Беллора

1
Ні. Це не те, що забруднення купи. "Забруднення купи відбувається, коли змінна параметризованого типу посилається на об'єкт, який не має такого параметризованого типу." Ref: docs.oracle.com/javase/tutorial/java/generics/…
Doradus

1

Причина полягає в тому, що varargs дають можливість викликатися з непараметризованим масивом об'єктів. Отже, якщо вашим типом був Список <A> ..., його також можна викликати за допомогою не [varargs] List [].

Ось приклад:

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

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

Як ви бачите, Список [] b може містити будь-який тип споживача, і все ж цей код складається. Якщо ви використовуєте varargs, то вам все в порядку, але якщо ви використовуєте визначення методу після стирання типу - тест недійсності (Список []) - тоді компілятор не перевірить типи параметрів шаблону. @SafeVarargs придушить це попередження.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.