Pollution possible du tas via le paramètre varargs

433

Je comprends que cela se produit avec Java 7 lors de l'utilisation de varargs avec un type générique;

Mais ma question est ..

Que signifie exactement Eclipse quand il dit "son utilisation pourrait potentiellement polluer le tas?"

Et

Comment la nouvelle @SafeVarargsannotation empêche-t-elle cela?

hertzsprung
la source
Je vois cela dans mon éditeur:Possible heap pollution from parameterized vararg type
Alexander Mills

Réponses:

252

La pollution en tas est un terme technique. Il fait référence à des références dont le type n'est pas un sur-type de l'objet vers lequel elles pointent.

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

Cela peut conduire à des «inexplicables» ClassCastException.

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

@SafeVarargsne l'empêche pas du tout. Cependant, il existe des méthodes qui ne pollueront pas le tas, le compilateur ne peut tout simplement pas le prouver. Auparavant, les appelants de ces API recevaient des avertissements ennuyeux qui étaient complètement inutiles mais devaient être supprimés sur chaque site d'appel. L'auteur de l'API peut désormais le supprimer une fois sur le site de déclaration.

Cependant, si la méthode n'est pas sûre, les utilisateurs ne seront plus avertis.

Ben Schulz
la source
2
Sommes-nous donc en train de dire que le tas est pollué parce qu'il contient des références dont les types ne sont pas ceux auxquels on pourrait s'attendre? (Liste <A> vs Liste <B> dans votre exemple)
hertzsprung
30
Cette réponse est une bonne explication de ce qu'est la pollution en tas, mais elle n'explique pas vraiment pourquoi les varargs sont si particulièrement susceptibles de la provoquer au point de justifier un avertissement spécifique.
Dolda2000
4
Moi aussi, il me manque des informations pour m'assurer que mon code ne contient pas ce problème (par exemple, comment puis-je savoir qu'il est suffisamment durci pour ajouter @SafeVarargs)
Daniel Alder
237

Lorsque vous déclarez

public static <T> void foo(List<T>... bar) le compilateur le convertit en

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

public static void foo(List[] bar)

Le danger survient alors que vous attribuerez par erreur des valeurs incorrectes dans la liste et que le compilateur ne déclenchera aucune erreur. Par exemple, si Test un, Stringle code suivant se compilera sans erreur mais échouera au moment de l'exécution:

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

Si vous avez examiné la méthode pour vous assurer qu'elle ne contient pas de telles vulnérabilités, vous pouvez l'annoter avec @SafeVarargspour supprimer l'avertissement. Pour les interfaces, utilisez @SuppressWarnings("unchecked").

Si vous obtenez ce message d'erreur:

La méthode Varargs pourrait entraîner une pollution du tas par le paramètre Varargs non réifiable

et vous êtes sûr que votre utilisation est sûre, vous devez utiliser à la @SuppressWarnings("varargs")place. Voir @SafeVarargs une annotation appropriée pour cette méthode? et https://stackoverflow.com/a/14252221/14731 pour une belle explication de ce deuxième type d'erreur.

Références:

Gili
la source
2
Je pense que je comprends mieux. Le danger vient quand vous lancez des varargs Object[]. Tant que vous ne lancez pas Object[], il semble que vous devriez aller bien.
djeikyb
3
À titre d'exemple d'une chose stupide que vous pourriez faire: static <T> void bar(T...args) { ((Object[])args)[0] = "a"; }. Et puis appelez bar(Arrays.asList(1,2));.
djeikyb
1
@djeikyb si le danger ne survient que si je lance vers Object[]pourquoi le compilateur déclencherait-il un avertissement si je ne le fais pas? Cela devrait être assez facile à vérifier au moment de la compilation, après tout (dans le cas où je ne le passe pas à une autre fonction avec une signature similaire, auquel cas l'autre fonction devrait déclencher un avertissement). Je ne crois pas que ce soit vraiment le cœur de l'avertissement ("Vous êtes en sécurité si vous ne lancez pas"), et je ne comprends toujours pas dans quel cas je vais bien.
Qw3ry
5
@djeikyb Vous pouvez faire exactement la même chose stupide sans varargs paramétrés (par exemple bar(Integer...args)). Alors quel est l'intérêt de cet avertissement alors?
Vasiliy Vlasov
3
@VasiliyVlasov Ce problème ne concerne que les varargs paramétrés. Si vous essayez de faire la même chose avec des tableaux non typés, le runtime vous empêchera d'insérer le mauvais type dans le tableau. Le compilateur vous avertit que le temps d' exécution sera incapable d'empêcher un comportement incorrect parce que le type de paramètre est inconnu lors de l' exécution (en revanche, les tableaux ne connaissent le type de leurs éléments non génériques lors de l' exécution).
Gili
8

@SafeVarargs ne l'empêche pas de se produire, mais il exige que le compilateur soit plus strict lors de la compilation du code qui l'utilise.

http://docs.oracle.com/javase/7/docs/api/java/lang/SafeVarargs.html explique cela plus en détail.

La pollution de tas est lorsque vous obtenez un ClassCastExceptionlors d'une opération sur une interface générique et qu'elle contient un autre type que celui déclaré.

jontro
la source
Les restrictions supplémentaires du compilateur sur son utilisation ne semblent pas particulièrement pertinentes.
Paul Bellora
6

Lorsque vous utilisez des varargs, cela peut entraîner la création d'un Object[]pour contenir les arguments.

En raison de l'analyse d'échappement, le JIT peut optimiser cette création de tableau. (L'une des rares fois où je l'ai trouvé le fait) Ce n'est pas garanti d'être optimisé, mais je ne m'en inquiéterais pas à moins que vous ne voyiez un problème dans votre profileur de mémoire.

AFAIK @SafeVarargssupprime un avertissement du compilateur et ne change pas le comportement du JIT.

Peter Lawrey
la source
6
Intéressant mais cela ne répond pas vraiment à sa question @SafeVarargs.
Paul Bellora
1
Nan. Ce n'est pas ce qu'est la pollution en tas. "La pollution de tas se produit lorsqu'une variable d'un type paramétré fait référence à un objet qui n'est pas de ce type paramétré." Réf: docs.oracle.com/javase/tutorial/java/generics/…
Doradus
1

La raison en est que les varargs donnent la possibilité d'être appelés avec un tableau d'objets non paramétré. Donc, si votre type était List <A> ..., il peut également être appelé avec le type List [] non varargs.

Voici un exemple:

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

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

Comme vous pouvez le voir, List [] b peut contenir n'importe quel type de consommateur, et pourtant ce code se compile. Si vous utilisez des varargs, tout va bien, mais si vous utilisez la définition de méthode après l'effacement de type - test void (List []) - alors le compilateur ne vérifiera pas les types de paramètres de modèle. @SafeVarargs supprimera cet avertissement.

user1122069
la source