Adnotacja Java SafeVarargs, czy istnieje standard lub najlepsza praktyka?


175

Niedawno natknąłem się na @SafeVarargsadnotację java . Szukanie w Google, co sprawia, że ​​funkcja wariadyczna w Javie jest niebezpieczna, wprawiło mnie w zakłopotanie (zatrucie sterty? Usunięte typy?), Więc chciałbym wiedzieć kilka rzeczy:

  1. Co sprawia, że ​​zmienna funkcja Java jest niebezpieczna w tym @SafeVarargssensie (najlepiej wyjaśnione w formie szczegółowego przykładu)?

  2. Dlaczego ta adnotacja jest pozostawiona uznaniu programisty? Czy to nie jest coś, co kompilator powinien być w stanie sprawdzić?

  3. Czy jest jakiś standard, do którego należy się stosować, aby jego funkcja była rzeczywiście bezpieczna dla waragów? Jeśli nie, jakie są najlepsze praktyki, aby to zapewnić?


1
Czy widziałeś przykład (i wyjaśnienie) w JavaDoc ?
jlordo

2
Na swoim trzecim pytaniu, jeden praktyką jest zawsze pierwszy element i inne: retType myMethod(Arg first, Arg... others). Jeśli nie określisz first, dozwolona jest pusta tablica i możesz mieć metodę o tej samej nazwie z tym samym typem zwracania, która nie pobiera żadnych argumentów, co oznacza, że ​​maszyna JVM będzie miała trudności z określeniem, którą metodę należy wywołać.
fge

4
@jlordo Zrobiłem, ale nie rozumiem, dlaczego jest to podane w kontekście varargs, ponieważ można łatwo powtórzyć tę sytuację poza funkcją varargs (zweryfikowałem to, kompiluje się z oczekiwanymi ostrzeżeniami bezpieczeństwa typu i błędami w czasie wykonywania).
Oren

Odpowiedzi:


244

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.


7
teraz to wszystko ma sens. dzięki. więc żeby zobaczyć, że całkowicie cię rozumiem, powiedzmy, że mamy klasę, FOO<T extends Something>czy byłoby bezpiecznie zwrócić T [] metody varargs jako tablicę Somthings?
Oren

30
Warto zauważyć, że jeden przypadek, w którym jest (prawdopodobnie) zawsze poprawny w użyciu, @SafeVarargsto sytuacja, w której jedyną rzeczą, jaką robisz z tablicą, jest przekazanie jej do innej metody, która jest już tak opisana (na przykład: często piszę metody vararg, które istnieją, aby przekonwertować swój argument na listę przy użyciu Arrays.asList(...)i przekazać go do innej metody; taki przypadek zawsze można opatrzyć adnotacją, @SafeVarargsponieważ Arrays.asListma adnotację).
Jules,

3
@newacct Nie wiem, czy tak było w Javie 7, ale Java 8 nie zezwala, @SafeVarargschyba że metoda jest staticlub final, ponieważ w przeciwnym razie mogłaby zostać zastąpiona w klasie pochodnej czymś niebezpiecznym.
Andy

3
aw Javie 9 będzie to również dozwolone dla privatemetod, których również nie można przesłonić.
Dave L.

4

Aby uzyskać najlepsze praktyki, rozważ to.

Jeśli masz to:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

Zmień to na to:

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
}

Zauważyłem, że zwykle dodam varargy tylko po to, aby było wygodniej dla moich rozmówców. Dla mojej wewnętrznej implementacji prawie zawsze wygodniej byłoby użyć pliku List<>. Tak więc, aby wrócić na Arrays.asList()barana i upewnić się, że nie ma możliwości wprowadzenia Heap Pollution, oto co robię.

Wiem, że to tylko odpowiada na twoje # 3. newacct udzielił świetnej odpowiedzi na punkty 1 i 2 powyżej, a nie mam wystarczającej reputacji, aby zostawić to w komentarzu. : P

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.