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) Відмінність, про яку я згадував вище, є надто складною, щоб легко її розрізнити автоматично.