Mögliche Haufenverschmutzung über den Parameter varargs

433

Ich verstehe, dass dies bei Java 7 auftritt, wenn Varargs mit einem generischen Typ verwendet werden.

Aber meine Frage ist ..

Was genau bedeutet Eclipse, wenn es heißt "seine Verwendung könnte möglicherweise den Haufen verschmutzen"?

Und

Wie @SafeVarargsverhindert die neue Anmerkung dies?

Hertzsprung
quelle
Ich sehe dies in meinem Herausgeber:Possible heap pollution from parameterized vararg type
Alexander Mills

Antworten:

252

Haufenverschmutzung ist ein Fachbegriff. Es bezieht sich auf Referenzen, deren Typ kein Supertyp des Objekts ist, auf das sie verweisen.

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

Dies kann zu "unerklärlichen" ClassCastExceptions führen.

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

@SafeVarargsverhindert dies überhaupt nicht. Es gibt jedoch Methoden, die den Heap nachweislich nicht verschmutzen. Der Compiler kann dies einfach nicht beweisen. Bisher erhielten Anrufer solcher APIs nervige Warnungen, die völlig sinnlos waren, aber an jedem Anrufort unterdrückt werden mussten. Jetzt kann der API-Autor es einmal an der Deklarationsstelle unterdrücken.

Wenn die Methode jedoch nicht sicher ist, werden Benutzer nicht mehr gewarnt.

Ben Schulz
quelle
2
Wollen wir damit sagen, dass der Heap verschmutzt ist, weil er Referenzen enthält, deren Typen nicht den Erwartungen entsprechen? (Liste <A> vs Liste <B> in Ihrem Beispiel)
Hertzsprung
30
Diese Antwort ist eine gute Erklärung dafür, was Haufenverschmutzung ist, aber sie erklärt nicht wirklich, warum Varargs so wahrscheinlich sind, dass sie eine bestimmte Warnung rechtfertigen.
Dolda2000
4
Mir fehlen auch Informationen, wie ich sicherstellen kann, dass mein Code dieses Problem nicht enthält (z. B. woher weiß ich, dass er hart genug ist, um @SafeVarargs hinzuzufügen)
Daniel Alder
235

Wenn Sie erklären

public static <T> void foo(List<T>... bar) Der Compiler konvertiert es in

public static <T> void foo(List<T>[] bar) dann zu

public static void foo(List[] bar)

Es besteht dann die Gefahr, dass Sie der Liste fälschlicherweise falsche Werte zuweisen und der Compiler keinen Fehler auslöst. Wenn dies beispielsweise a ist, Twird Stringder folgende Code fehlerfrei kompiliert, schlägt jedoch zur Laufzeit fehl:

// 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);

Wenn Sie die Methode überprüft haben, um sicherzustellen, dass sie keine solchen Sicherheitsanfälligkeiten enthält, können Sie sie mit Anmerkungen versehen @SafeVarargs, um die Warnung zu unterdrücken. Verwenden Sie für Schnittstellen @SuppressWarnings("unchecked").

Wenn Sie diese Fehlermeldung erhalten:

Die Varargs-Methode kann eine Haufenverschmutzung durch nicht reifizierbare Varargs-Parameter verursachen

und Sie sind sicher, dass Ihre Verwendung sicher ist, dann sollten Sie @SuppressWarnings("varargs")stattdessen verwenden. Siehe Ist @SafeVarargs eine geeignete Anmerkung für diese Methode? und https://stackoverflow.com/a/14252221/14731 für eine nette Erklärung dieser zweiten Art von Fehler.

Verweise:

Gili
quelle
2
Ich denke, ich verstehe besser. Die Gefahr besteht, wenn Sie Varargs anwerfen Object[]. Solange Sie nicht besetzen Object[], klingt es so, als ob es Ihnen gut gehen sollte.
Djeikyb
3
Als Beispiel für eine dumme Sache, die Sie tun könnten : static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. Und dann anrufen bar(Arrays.asList(1,2));.
Djeikyb
1
@djeikyb wenn die Gefahr nur entsteht, wenn ich darüber nachdenke, Object[]warum der Compiler eine Warnung auslösen würde, wenn ich es nicht tue? Es sollte schließlich ziemlich einfach sein, dies zur Kompilierungszeit zu überprüfen (falls ich es nicht an eine andere Funktion mit einer ähnlichen Signatur weitergebe, in diesem Fall sollte die andere Funktion eine Warnung auslösen). Ich glaube nicht, dass dies wirklich der Kern der Warnung ist ("Du bist in Sicherheit, wenn du nicht wirfst"), und ich verstehe immer noch nicht, in welchem ​​Fall es mir gut geht.
Qw3ry
5
@djeikyb Du kannst genau das Gleiche tun, ohne parametrisierte Varargs (zB bar(Integer...args)). Wozu dient diese Warnung dann?
Vasiliy Vlasov
3
@VasiliyVlasov Dieses Problem ist nur für parametrisierte Varargs relevant. Wenn Sie versuchen, dasselbe mit nicht typisierten Arrays zu tun, verhindert die Laufzeit, dass Sie den falschen Typ in das Array einfügen. Der Compiler warnt Sie, dass die Laufzeit ein falsches Verhalten nicht verhindern kann, da der Parametertyp zur Laufzeit unbekannt ist (im Gegensatz dazu kennen Arrays zur Laufzeit den Typ ihrer nicht generischen Elemente).
Gili
8

@SafeVarargs verhindert dies nicht, erfordert jedoch, dass der Compiler beim Kompilieren von Code, der ihn verwendet, strenger ist.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html erklärt dies ausführlicher.

Heap-Verschmutzung tritt auf, wenn Sie ClassCastExceptioneine Operation an einer generischen Schnittstelle ausführen und diese einen anderen Typ als deklariert enthält.

jontro
quelle
Die zusätzlichen Einschränkungen des Compilers für seine Verwendung scheinen nicht besonders relevant zu sein.
Paul Bellora
6

Wenn Sie varargs verwenden, kann dies dazu führen, dass ein Object[]Argument für die Argumente erstellt wird.

Aufgrund der Escape-Analyse kann die JIT diese Array-Erstellung optimieren. (Eines der wenigen Male, bei denen ich festgestellt habe, dass dies der Fall ist.) Es ist nicht garantiert, dass es wegoptimiert wird, aber ich würde mir darüber keine Sorgen machen, es sei denn, Sie sehen, dass es ein Problem in Ihrem Speicherprofiler ist.

AFAIK @SafeVarargsunterdrückt eine Warnung des Compilers und ändert nicht das Verhalten der JIT.

Peter Lawrey
quelle
6
Interessant, obwohl es seine Frage nicht wirklich beantwortet @SafeVarargs.
Paul Bellora
1
Nee. Das ist nicht was Haufenverschmutzung ist. "Heap-Verschmutzung tritt auf, wenn sich eine Variable eines parametrisierten Typs auf ein Objekt bezieht, das nicht von diesem parametrisierten Typ ist." Ref: docs.oracle.com/javase/tutorial/java/generics/…
Doradus
1

Der Grund dafür ist, dass varargs die Option bieten, mit einem nicht parametrisierten Objektarray aufgerufen zu werden. Wenn Ihr Typ also List <A> ... war, kann er auch mit dem Typ List [] non-varargs aufgerufen werden.

Hier ist ein Beispiel:

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

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

Wie Sie sehen können, kann List [] b jeden Verbrauchertyp enthalten, und dennoch wird dieser Code kompiliert. Wenn Sie varargs verwenden, ist das in Ordnung. Wenn Sie jedoch die Methodendefinition nach dem Löschen des Typs - void test (List []) - verwenden, überprüft der Compiler die Vorlagenparametertypen nicht. @SafeVarargs unterdrückt diese Warnung.

user1122069
quelle