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

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

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

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

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

Ответ 1

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

Ответ 2

Для лучшей практики, учтите это.

Если у вас есть это:

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, чтобы сделать его более удобным для моих абонентов. Для моей внутренней реализации почти всегда было бы удобнее использовать List<>. Таким образом, чтобы Arrays.asList() и убедиться, что я не могу представить загрязнение кучи, это то, что я делаю.

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