Анотація Java SafeVarargs, чи існує стандарт чи найкраща практика?


175

Нещодавно я натрапив на @SafeVarargsанотацію Java . Гуглінг за те, що робить різноманітну функцію в Java небезпечною, залишив мене досить розгубленим (отруєння купу? Стерти типи?), Тому я хотів би знати кілька речей:

  1. Що робить різноманітну функцію Java небезпечною у @SafeVarargsсенсі (бажано пояснити у вигляді глибокого прикладу)?

  2. Чому ця примітка залишається на розсуд програміста? Це не те, що компілятор повинен мати можливість перевірити?

  3. Чи потрібно дотримуватися якогось стандартного, щоб переконатися, що його функція справді вараги безпечна? Якщо ні, то які найкращі практики для її забезпечення?


1
Ви бачили приклад (і пояснення) в JavaDoc ?
jlordo

2
Для вашого третього питання, то одна практики завжди є перший елемент , і інші: retType myMethod(Arg first, Arg... others). Якщо ви не вказуєте first, пустий масив дозволений, і вам цілком може бути метод з тим самим іменем з тим самим типом повернення, не беручи аргументів, це означає, що JVM буде важко визначати, який метод слід викликати.
fge

4
@jlordo Я це зробив, але я не розумію, чому його дано в контексті varargs, оскільки можна легко поповнити цю ситуацію поза функцією varargs (перевірено це, компілюється з очікуваними попередженнями безпеки та помилками під час виконання) ..
Oren

Відповіді:


244

1) В Інтернеті та на StackOverflow є багато прикладів щодо конкретної проблеми з дженериками та вараггами. В основному, це коли у вас є змінна кількість аргументів типу типу параметра:

<T> void foo(T... args);

У Java varargs - це синтаксичний цукор, який під час компіляції зазнає простого "переписування": параметр типу varargs X...перетворюється в параметр типу X[]; і кожного разу, коли виконується виклик цього методу varargs, компілятор збирає всі "змінні аргументи", які входять у параметр varargs, і створює масив так само, як new X[] { ...(arguments go here)... }.

Це добре працює, коли тип вараггів конкретний String.... Коли це змінна тип типу T..., вона також працює, коли, Tяк відомо, конкретний тип для цього виклику. наприклад, якщо вищевказаний метод був частиною класу Foo<T>, і у вас є Foo<String>посилання, тоді закликати fooйого було б добре, оскільки ми знаємо T, Stringщо це в коді.

Однак воно не працює, коли "значення" параметра Tє іншим типом. У Java неможливо створити масив компонента типу type-parameter ( new T[] { ... }). Тож Java замість цього використовує new Object[] { ... }(ось Objectверхня межа T; якщо там верхня межа була чимось іншою, то це було б замість цього Object), а потім дає вам попередження компілятора.

То що не так у створенні new Object[]замість того new T[]чи іншого? Ну а масиви в Java знають тип компонента під час виконання. Таким чином, переданий об'єкт масиву матиме неправильний тип компонента під час виконання.

Для, мабуть, найпоширенішого використання varargs, просто перебирати елементи, це не проблема (вас не хвилює тип виконання масиву), так що це безпечно:

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

Однак для будь-якого, що залежить від типу компонента часу виконання пройденого масиву, це не буде безпечно. Ось простий приклад чогось небезпечного та аварійного:

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

Проблема тут полягає в тому, що ми залежимо від типу, argsщоб бути T[], щоб повернути його як T[]. Але насправді тип аргументу під час виконання не є примірником T[].

3) Якщо ваш метод має аргумент типу T...(де T - параметр будь-якого типу), то:

  • Безпечно: Якщо ваш метод залежить лише від того, що елементи масиву є екземплярами T
  • Небезпечно: якщо це залежить від того, що масив є екземпляром T[]

До речей, що залежать від типу виконання масиву, належать: повернення його як типу T[], передача його як аргумент параметру типу T[], отримання типу масиву з використанням .getClass(), передача його методам, які залежать від типу часу виконання масиву, як List.toArray()і Arrays.copyOf()тощо.

2) Відмінність, про яку я згадував вище, є надто складною, щоб легко її розрізнити автоматично.


7
ну тепер це все має сенс. Дякую. так щоби побачити, що я вас повністю розумію, скажімо, у нас є клас, FOO<T extends Something>чи було б безпечно повернути T [] методу varargs як масив Somthings?
Орен

30
Можливо, варто зауважити, що один випадок, коли це (імовірно) завжди правильно використовувати @SafeVarargs- це коли єдине, що ви робите з масивом, це передати його іншому методу, який уже так зазначається (наприклад: Я часто знаходжу себе як пишу варагг-методи, які існують, щоб перетворити їх аргумент до списку, використовуючи Arrays.asList(...)та передати його іншому методу; такий випадок завжди може бути анотований, @SafeVarargsоскільки Arrays.asListмає анотацію)
Жуль

3
@newacct я не знаю , якщо це було в Java 7, але Java-не дозволяє , @SafeVarargsякщо метод не є staticабо final, тому що в іншому випадку він може отримати переопределяется в похідному класі з чим - то небезпечним.
Енді

3
а в Java 9 це також буде дозволено для privateметодів, які також неможливо змінити.
Дейв Л.

4

Для кращої практики врахуйте це.

Якщо у вас є це:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Змініть це на це:

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

Я виявив, що зазвичай додаю лише вараги, щоб зробити це зручнішим для своїх абонентів. Майже завжди було б зручнішим для моєї внутрішньої реалізації використовувати List<>. Тож, щоб повернутись назад Arrays.asList()і переконатися, що я не можу ввести забруднення купи, це те, що я роблю.

Я знаю, що це відповідає лише на ваш номер 3. newacct дав чудову відповідь на №1 та №2 вище, і мені не вистачає репутації, щоб просто залишити це як коментар. : P

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