1) W Internecie i na StackOverflow jest wiele przykładów dotyczących konkretnego problemu z typami generycznymi i varargami. Zasadniczo dzieje się tak, gdy masz zmienną liczbę argumentów typu parametru typu:
<T> void foo(T... args);
W Javie varargs są cukrem składniowym, który podlega prostemu „przepisaniu” w czasie kompilacji: parametr varargs typu X...
jest konwertowany na parametr typu X[]
; i za każdym razem, gdy wywoływana jest ta metoda varargs, kompilator zbiera wszystkie „argumenty zmiennych”, które znajdują się w parametrze varargs, i tworzy tablicę tak samo jak new X[] { ...(arguments go here)... }
.
Działa to dobrze, gdy typ varargs jest podobny do betonu String...
. Kiedy jest to zmienna typu T...
, taka jak , działa również, gdy T
wiadomo, że jest konkretnym typem dla tego wywołania. np. jeśli powyższa metoda byłaby częścią klasy Foo<T>
i masz Foo<String>
odniesienie, to wywołanie foo
jej byłoby w porządku, ponieważ wiemy, że T
znajduje się String
w tym punkcie kodu.
Jednak nie działa, gdy „wartość” T
jest parametrem innego typu. W Javie nie jest możliwe utworzenie tablicy składającej się z parametru typu type ( new T[] { ... }
). Więc zamiast tego Java używa new Object[] { ... }
(tutaj Object
jest górna granica T
; gdyby górna granica była czymś innym, byłaby to zamiast Object
), a następnie wyświetla ostrzeżenie kompilatora.
Więc co jest złego w tworzeniu new Object[]
zamiast new T[]
czy w czymkolwiek? Cóż, tablice w Javie znają swój typ komponentu w czasie wykonywania. Dlatego przekazany obiekt tablicy będzie miał nieprawidłowy typ komponentu w czasie wykonywania.
Prawdopodobnie najbardziej powszechne użycie varargs, po prostu do iteracji po elementach, nie stanowi problemu (nie przejmujesz się typem wykonawczym tablicy), więc jest to bezpieczne:
@SafeVarargs
final <T> void foo(T... args) {
for (T x : args) {
// do stuff with x
}
}
Jednak w przypadku wszystkiego, co zależy od typu składnika środowiska wykonawczego przekazanej tablicy, nie będzie to bezpieczne. Oto prosty przykład czegoś, co jest niebezpieczne i ulega awarii:
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");
}
}
Problem polega na tym, że zależymy od typu args
bycia T[]
, aby zwrócić go jako T[]
. Ale w rzeczywistości typ argumentu w czasie wykonywania nie jest wystąpieniem T[]
.
3) Jeśli twoja metoda ma argument typu T...
(gdzie T jest dowolnym parametrem typu), to:
- Bezpieczne: Jeśli twoja metoda zależy tylko od tego, że elementy tablicy są instancjami
T
- Niebezpieczne: jeśli zależy to od faktu, że tablica jest instancją
T[]
Rzeczy, które zależą od typu środowiska wykonawczego tablicy, obejmują: zwrócenie jej jako typu T[]
, przekazanie go jako argumentu do parametru typu T[]
, pobranie typu tablicy przy użyciu .getClass()
, przekazanie do metod, które zależą od typu środowiska wykonawczego tablicy, takich jak List.toArray()
i Arrays.copyOf()
itp.
2) Rozróżnienie, o którym wspomniałem powyżej, jest zbyt skomplikowane, aby można je było łatwo rozróżnić automatycznie.