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



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




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


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


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


504   2  

2 ответов:

1) Есть много примеров в интернете и на StackOverflow о конкретной проблеме с generics и varargs. В принципе, это когда у вас есть переменное количество аргументов типа-параметр типа:

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 невозможно создать массив типа-параметр component type (new T[] { ... }). Поэтому Java вместо этого использует new Object[] { ... } (здесь Object верхний предел T; если бы верхняя граница была чем-то другим, это было бы то, что вместо Object), а затем дает вам предупреждение компилятора.

так что же не так с созданием new Object[] вместо new T[] или что? Ну, массивы в Java знают свой тип компонента во время выполнения. Таким образом, переданный объект массива будет иметь неправильный тип компонента во время выполнения.

для, Вероятно, наиболее распространенного использования varargs, просто перебирать элементы, это не проблема (вы не заботитесь о типе времени выполнения массива), так что это безопасно:

@SafeVarargs
final 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-любой параметр типа), то:

  • Safe: если ваш метод зависит только от того, что элементы массива являются экземплярами T
  • небезопасно: если это зависит от того, что массив является экземпляром T[]

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

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 выше, и у меня недостаточно репутации, чтобы просто оставить это в качестве комментария. : P

Comments

    Ничего не найдено.