Możliwe zanieczyszczenie hałdy poprzez parametr varargs

433

Rozumiem, że dzieje się tak w przypadku Java 7, gdy używa się varargs z typem ogólnym;

Ale moje pytanie brzmi ...

Co dokładnie oznacza Eclipse, gdy mówi „jego użycie może potencjalnie zanieczyścić stertę?”

I

W jaki sposób nowa @SafeVarargsadnotacja temu zapobiega?

hertzsprung
źródło
8
Szczegóły tutaj: docs.oracle.com/javase/specs/jls/se7/html/…
assylias
Widzę to w moim edytorze:Possible heap pollution from parameterized vararg type
Alexander Mills

Odpowiedzi:

252

Zanieczyszczenie hałd to termin techniczny. Odnosi się do odwołań, których typ nie jest nadtypem obiektu, na który wskazują.

List<A> listOfAs = new ArrayList<>();
List<B> listOfBs = (List<B>)(Object)listOfAs; // points to a list of As

Może to prowadzić do „niewyjaśnienia” ClassCastException.

// if the heap never gets polluted, this should never throw a CCE
B b = listOfBs.get(0); 

@SafeVarargswcale tego nie zapobiega. Istnieją jednak metody, które prawdopodobnie nie zanieczyszczą sterty, kompilator po prostu nie może tego udowodnić. Wcześniej osoby wywołujące takie interfejsy API otrzymywały irytujące ostrzeżenia, które były całkowicie bezcelowe, ale musiały być tłumione na każdej stronie połączeń. Teraz autor interfejsu API może usunąć go raz w witrynie deklaracji.

Jednak jeśli metoda faktycznie nie jest bezpieczna, użytkownicy nie będą już ostrzegani.

Ben Schulz
źródło
2
Czy więc mówimy, że sterta jest zanieczyszczona, ponieważ zawiera odwołania, których typy nie są zgodne z oczekiwaniami? (Lista <A> vs. Lista <B> w twoim przykładzie)
hertzsprung
30
Ta odpowiedź jest dobrym wyjaśnieniem, czym jest zanieczyszczenie hałdy, ale tak naprawdę nie wyjaśnia, dlaczego varargs są tak szczególnie prawdopodobne, że spowodują to, że uzasadniają określone ostrzeżenie.
Dolda2000
4
Ja też brakuje mi informacji, jak upewnić się, że mój kod nie zawiera tego problemu (np. Skąd mam wiedzieć, że jest wystarczająco zahartowany, aby dodać @SafeVarargs)
Daniel Alder
237

Kiedy deklarujesz

public static <T> void foo(List<T>... bar) kompilator konwertuje go na

public static <T> void foo(List<T>[] bar) następnie do

public static void foo(List[] bar)

Powstaje wówczas niebezpieczeństwo, że przez pomyłkę przypiszesz niepoprawne wartości do listy, a kompilator nie spowoduje błędu. Na przykład, jeśli Tjest a, Stringto następujący kod zostanie skompilowany bez błędów, ale zakończy się niepowodzeniem w czasie wykonywania:

// First, strip away the array type (arrays allow this kind of upcasting)
Object[] objectArray = bar;

// Next, insert an element with an incorrect type into the array
objectArray[0] = Arrays.asList(new Integer(42));

// Finally, try accessing the original array. A runtime error will occur
// (ClassCastException due to a casting from Integer to String)
T firstElement = bar[0].get(0);

Jeśli sprawdziłeś metodę, aby upewnić się, że nie zawiera ona takich luk, możesz ją opatrzyć adnotacjami, @SafeVarargsaby ukryć ostrzeżenie. W przypadku interfejsów użyj @SuppressWarnings("unchecked").

Jeśli pojawi się ten komunikat o błędzie:

Metoda Varargs może powodować zanieczyszczenie hałdy na podstawie parametru varargs niepodlegającego zwrotowi

i masz pewność, że korzystanie z niego jest bezpieczne, powinieneś użyć @SuppressWarnings("varargs")zamiast tego. Zobacz Czy @SafeVarargs jest odpowiednią adnotacją dla tej metody? i https://stackoverflow.com/a/14252221/14731, aby uzyskać miłe wyjaśnienie tego drugiego rodzaju błędu.

Bibliografia:

Gili
źródło
2
Myślę, że rozumiem lepiej. Niebezpieczeństwo pojawia się, gdy rzucisz varargsa na Object[]. Tak długo, jak nie rzucasz Object[], wygląda na to, że powinieneś być w porządku.
djeikyb
3
Jako przykład głupie rzeczy można zrobić: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. A potem zadzwoń bar(Arrays.asList(1,2));.
djeikyb
1
@ djeikyb, jeśli niebezpieczeństwo powstaje tylko wtedy, gdy rzucę Object[]pytanie, dlaczego kompilator uruchomiłby ostrzeżenie, jeśli nie? W końcu powinno to być dość łatwe do sprawdzenia w czasie kompilacji (w przypadku, gdy nie przekażę go innej funkcji o podobnej sygnaturze, w takim przypadku druga funkcja powinna wywołać ostrzeżenie). Nie wierzę, że to naprawdę jest sedno ostrzeżenia („Jesteś bezpieczny, jeśli nie rzucisz”) i nadal nie rozumiem, w którym przypadku mam się dobrze.
Qw3ry
5
@ djeikyb Możesz zrobić dokładnie to samo głupie bez sparametryzowanych varargs (np bar(Integer...args).). Jaki jest więc sens tego ostrzeżenia?
Wasilij Własow
3
@VasiliyVlasov Ten problem dotyczy tylko sparametryzowanych varargs. Jeśli spróbujesz zrobić to samo z tablicami nietypowymi, środowisko wykonawcze uniemożliwi wstawienie niewłaściwego typu do tablicy. Kompilator jest ostrzeżeniem, że środowisko wykonawcze nie będzie w stanie zapobiec nieprawidłowe zachowanie, ponieważ typ parametr nie jest znana w środowisku wykonawczym (natomiast tablice nie znać rodzaj ich elementów nierodzajową w czasie wykonywania).
Gili
8

@SafeVarargs nie zapobiega temu, jednak nakazuje, aby kompilator był bardziej rygorystyczny podczas kompilowania kodu, który go używa.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html wyjaśnia to bardziej szczegółowo.

Zanieczyszczenie sterty występuje wtedy, ClassCastExceptiongdy wykonujesz operację na interfejsie ogólnym i zawiera on inny typ niż zadeklarowany.

jontro
źródło
Dodatkowe ograniczenia kompilatora dotyczące jego używania nie wydają się szczególnie istotne.
Paul Bellora,
6

Gdy używasz varargs, może to spowodować utworzenie Object[]argumentu do przechowywania argumentów.

Dzięki analizie ucieczki JIT może zoptymalizować tworzenie tablicy. (Jeden z niewielu razy to stwierdziłem). Nie gwarantuje się, że zostanie zoptymalizowany, ale nie martwiłbym się tym, dopóki nie zobaczysz problemu w twoim profilu pamięci.

AFAIK @SafeVarargspomija kompilator i nie zmienia zachowania JIT.

Peter Lawrey
źródło
6
Ciekawe, ale tak naprawdę nie odpowiada na jego pytanie @SafeVarargs.
Paul Bellora,
1
Nie. To nie jest zanieczyszczenie hałdy. „Zanieczyszczenie hałd występuje, gdy zmienna sparametryzowanego typu odnosi się do obiektu, który nie jest sparametryzowanego typu.” Ref: docs.oracle.com/javase/tutorial/java/generics/…
Doradus
1

Powodem jest to, że varargs dają możliwość wywoływania z nieparametryzowaną tablicą obiektów. Więc jeśli twój typ to List <A> ..., można go również wywołać z List [] bez typu varargs.

Oto przykład:

public static void testCode(){
    List[] b = new List[1];
    test(b);
}

@SafeVarargs
public static void test(List<A>... a){
}

Jak widać List [] b może zawierać dowolnego rodzaju konsumenta, a jednak ten kod się kompiluje. Jeśli używasz varargs, wszystko jest w porządku, ale jeśli użyjesz definicji metody po usunięciu typu - void test (List []) - kompilator nie sprawdzi typów parametrów szablonu. @SafeVarargs usunie to ostrzeżenie.

użytkownik1122069
źródło