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 Twiadomo, ż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 foojej byłoby w porządku, ponieważ wiemy, że Tznajduje się Stringw tym punkcie kodu.
Jednak nie działa, gdy „wartość” Tjest 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 Objectjest 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 argsbycia 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.