Java SafeVarargs аннотация, существует стандарт или лучшие практики?

175

Я недавно сталкивался с @SafeVarargsаннотацией Java . Поиск в Google, что делает небезопасным функцию с переменным числом аргументов, оставил меня в замешательстве (отравление кучи? Стертые типы?), Поэтому я хотел бы знать несколько вещей:

  1. Что делает вариативную функцию Java небезопасной в этом @SafeVarargsсмысле (желательно, в форме подробного примера)?

  2. Почему эта аннотация оставлена ​​на усмотрение программиста? Разве это не то, что должен проверять компилятор?

  3. Есть ли какой-то стандарт, которому нужно следовать, чтобы гарантировать, что его функция действительно безопасна? Если нет, каковы лучшие методы для обеспечения этого?

Орен
источник
1
Вы видели пример (и объяснение) в JavaDoc ?
Джордо
2
Для вашего третьего вопроса, то одна практики всегда есть первый элемент , и другие: retType myMethod(Arg first, Arg... others). Если вы не укажете first, пустой массив будет разрешен, и у вас вполне может быть метод с тем же именем и с тем же типом возврата, не принимающий аргументов, а это означает, что JVM будет трудно определить, какой метод следует вызывать.
2013 года
4
@jlordo Я сделал, но я не понимаю, почему это дано в контексте varargs, поскольку можно легко воспроизвести эту ситуацию вне функции varargs (проверено это, компилируется с ожидаемыми предупреждениями безопасности типов и ошибками во время выполнения) ..
Орен

Ответы:

244

1) В Интернете и на StackOverflow есть много примеров конкретной проблемы с дженериками и переменными. По сути, это когда у вас есть переменное число аргументов типа параметр типа:

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

В Java varargs - это синтаксический сахар, который подвергается простой «перезаписи» во время компиляции: параметр типа varargs X...преобразуется в параметр типа X[]; и каждый раз, когда вызывается этот метод varargs, компилятор собирает все «переменные аргументы», которые входят в параметр varargs, и создает массив точно так же new X[] { ...(arguments go here)... }.

Это хорошо работает, когда тип varargs подобен бетону String.... Когда это переменная типа T..., например , она также работает, когда Tизвестно, что это конкретный тип для этого вызова. например , если метод выше , были частью класса Foo<T>, и у вас есть Foo<String>ссылка, то вызов fooна это будет хорошо , потому что мы знаем , Tэто Stringв тот момент в коде.

Однако он не работает, когда «значение» Tявляется другим параметром типа. В Java невозможно создать массив типа компонента с параметром типа ( 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) Различие, которое я упомянул выше, слишком сложно, чтобы его можно было легко различить автоматически.

newacct
источник
7
ну теперь все это имеет смысл. Спасибо. просто чтобы убедиться, что я вас полностью понимаю, допустим, у нас есть класс, FOO<T extends Something>было бы безопасно возвращать T [] метода varargs в виде массива Somthings?
Орен
30
Возможно, стоит отметить, что один из случаев, когда он (предположительно) всегда корректен для использования, @SafeVarargs- это когда единственное, что вы делаете с массивом, - это передаете его другому методу, который уже так аннотирован (например: я часто нахожу себя пишущим методы vararg, которые Существуют, чтобы преобразовать свой аргумент в список с помощью Arrays.asList(...)и передать его другому методу; такой случай всегда может быть аннотирован, @SafeVarargsпотому что Arrays.asListимеет аннотацию).
Жюль
3
@newacct Я не знаю, так ли это было в Java 7, но Java 8 не позволяет, @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
}

Я обнаружил, что обычно добавляю varargs, чтобы сделать его более удобным для моих абонентов. Для моей внутренней реализации почти всегда было бы удобнее использовать a List<>. Таким образом, чтобы закрепить Arrays.asList()и убедиться, что я не могу представить загрязнение кучи, это то, что я делаю.

Я знаю, что это только отвечает на ваш # 3. newacct дал отличный ответ для № 1 и № 2 выше, и у меня недостаточно репутации, чтобы оставить это как комментарий. :П

Холодильник
источник