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