Est-il possible de résoudre l'avertissement du compilateur «Un tableau générique de T est créé pour un paramètre varargs»?

153

Il s'agit d'une version simplifiée du code en question, une classe générique utilise une autre classe avec des paramètres de type générique et doit passer l'un des types génériques à une méthode avec des paramètres varargs:

class Assembler<X, Y> {
    void assemble(X container, Y... args) { ... }
}

class Component<T> {
    void useAssembler(T something) {

        Assembler<String, T> assembler = new Assembler<String, T>();

        //generates warning:
        // Type safety : A generic array of T is
        // created for a varargs parameter
        assembler.assemble("hello", something);
    }

}

Existe-t-il un moyen correct de transmettre le paramètre générique à une méthode varargs sans rencontrer cet avertissement?

Bien sûr, quelque chose comme

assembler.assemble("hello", new T[] { something });

ne fonctionne pas car vous ne pouvez pas créer de tableaux génériques.

mat b
la source
3
Un étrange. Il semble que le compilateur devrait être en mesure d'assurer une sécurité de type complète ici.
erickson
3
Entrée associée dans la FAQ Java Generics d'Angelika Langer: angelikalanger.com/GenericsFAQ/FAQSections/…
Flux du

Réponses:

88

À part ajouter @SuppressWarnings("unchecked"), je ne pense pas.

Ce rapport de bogue contient plus d'informations, mais il se résume au fait que le compilateur n'aime pas les tableaux de types génériques.

Kevin
la source
3
J'ai oublié de mentionner que je voulais éviter @SuppressWarnings ("non coché"). Ce rapport de bogue me donne peu d'espoir!
matt b
3
Comme Joshua Bloch le dit dans Effective Java: "Ne mélangez pas les génériques et les tableaux."
Timmos
20
alors, implicitement: n'utilisez pas Varargs avec Generics! C'est vrai ... la décision de mapper les varargs à Array et non à Collection continuera à piquer java pour toujours. Bien joué M. Gosling.
bernstein
57

Tom Hawtin l'a souligné dans un commentaire, mais pour être plus explicite: oui, vous pouvez résoudre cela sur le site de déclaration (plutôt que sur les (potentiellement nombreux) sites d'appel): passez à JDK7.

Comme vous pouvez le voir dans l'article de blog de Joseph Darcy , l'exercice Project Coin pour sélectionner de petites améliorations progressives du langage pour Java 7 a accepté la proposition de Bob Lee d'autoriser quelque chose comme @SuppressWarnings("varargs")du côté de la méthode pour faire disparaître cet avertissement dans des situations où il était connu sûr.

Cela a été implémenté dans OpenJDK avec ce commit .

Cela peut être utile ou non pour votre projet (beaucoup de gens ne seraient pas heureux de passer à une version instable de pré-version de la JVM!) Mais peut-être que c'est - ou peut-être quelqu'un qui trouvera cette question plus tard (après la sortie de JDK7 ) le trouvera utile.

Cowan
la source
7
La fonctionnalité Project Coin mentionnée est maintenant disponible - voir @SafeVarargs dans Java 7.
George Hawkins
La variante E dans la proposition de Bob est séduisante.
Christopher Perry
Java 8 semble utiliser @SafeVarargs au lieu de @SuppressWarnings ("varargs")
Paul Wintz
17

Si vous recherchez une interface de type fluide, vous pouvez essayer le modèle de générateur. Pas aussi concis que varargs mais il est de type sûr.

Une méthode statique de type générique peut éliminer une partie du passe-partout lors de l'utilisation du constructeur, tout en conservant la sécurité de type.

Le constructeur

public class ArgBuilder<T> implements Iterable<T> {

    private final List<T> args = new ArrayList<T>();

    public ArgBuilder<T> and(T arg) {
        args.add(arg);
        return this;
    }

    @Override
    public Iterator<T> iterator() {
        return args.iterator();
    }

    public static <T> ArgBuilder<T> with(T firstArgument) {
        return new ArgBuilder<T>().and(firstArgument);
    }
}

En l'utilisant

import static com.example.ArgBuilder.*;

public class VarargsTest {

    public static void main(String[] args) {
        doSomething(new ArgBuilder<String>().and("foo").and("bar").and("baz"));
        // or
        doSomething(with("foo").and("bar").and("baz"));
    }

    static void doSomething(Iterable<String> args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}
npgall
la source
1
Le pouvoir de la composition. J'aime bien plus que les varargs, c'est plus expressif.
Christopher Perry
1
@ChristopherPerry eh bien, vous devez également tenir compte de votre base de code. Le sous-jacent Collection(dans ce cas, un ArrayList) est forcé sur l'appelant, alors qu'ils peuvent savoir que a LinkedListest plus approprié, ou un tableau immuable lui-même (comme les varargs de la question OP). Dans un cas d'utilisation non spécialisé, cela peut être approprié, mais il suffit de souligner que c'est aussi une limitation, d'une certaine manière, en fonction du code qui l'entoure et de vos besoins.
searchengine27
5

Le cast explicite des paramètres en Object dans l'invocation de la méthode vararg rendra le compilateur heureux sans recourir à @SuppressWarnings.

public static <T> List<T> list( final T... items )
{
    return Arrays.asList( items );
}

// This will produce a warning.
list( "1", 2, new BigDecimal( "3.5" ) )

// This will not produce a warning.
list( (Object) "1", (Object) 2, (Object) new BigDecimal( "3.5" ) )

// This will not produce a warning either. Casting just the first parameter to 
// Object appears to be sufficient.
list( (Object) "1", 2, new BigDecimal( "3.5" ) )

Je crois que le problème ici est que le compilateur doit déterminer quel type concret de tableau créer. Si la méthode n'est pas générique, le compilateur peut utiliser les informations de type de la méthode. Si la méthode est générique, elle essaie de déterminer le type de tableau en fonction des paramètres utilisés lors de l'appel. Si les types de paramètres sont homogènes, cette tâche est facile. S'ils varient, le compilateur essaie d'être trop intelligent à mon avis et crée un tableau générique de type union. Ensuite, il se sent obligé de vous en avertir. Une solution plus simple aurait été de créer Object [] lorsque le type ne peut pas être mieux réduit. La solution ci-dessus impose justement cela.

Pour mieux comprendre cela, jouez avec les appels à la méthode list ci-dessus par rapport à la méthode list2 suivante.

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}
Konstantin Komissarchik
la source
Cela fonctionne également par exemple: Iterator <?> It = Arrays.asList ((Object) t) .iterator; if (if, hasNext ()) {class = it.next (). getClass (); } par exemple pour extraire la classe d'un objet d'un tableau de type inconnu.
ggb667
2

Vous pouvez ajouter @SafeVarargs à la méthode depuis Java 7, et vous n'avez pas à annoter sur le code client.

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}
Daniel Hári
la source
1

Vous pouvez surcharger les méthodes. Cela ne résout pas votre problème mais cela minimise le nombre d'avertissements (et oui, c'est un hack!)

class Assembler<X, Y> {
  void assemble(X container, Y a1) { ... }
  void assemble(X container, Y a1, Y a2) { ... }
  void assemble(X container, Y a1, Y a2, Y a3) { ... }
  void assemble(X container, Y a1, Y a2, Y a3, Y a4) { ... }
  void assemble(X container, Y... args) { ... }
}
EA.
la source
23
Ew. C'est exactement le genre de piratage que les varargs sont censés empêcher.
Amanda S
1
Cela peut être une approche valable, par exemple, jetez un oeil à Goyave de la ImmutableSet.of .
Jonathan
1

C'est un problème très facile à résoudre: utilisez List<T>!

Les tableaux de type référence doivent être évités.

Dans la version actuelle de Java (1.7), vous pouvez marquer la méthode avec @SafeVargslaquelle supprimera l'avertissement de l'appelant. Attention cependant, et vous êtes toujours mieux sans les baies héritées.

Consultez également la note technique Avertissements et erreurs du compilateur améliorés lors de l'utilisation de paramètres formels non réifiables avec les méthodes Varargs .

Tom Hawtin - Tacle
la source
6
c'est inévitable avec un paramètre varargs, n'est-ce pas?
matt b
4
Il existe une proposition pour JDK7 d'autoriser la suppression des avertissements sur la déclaration de la méthode varargs plutôt que sur son utilisation.
Tom Hawtin - tackline
11
Cela ignore complètement un aspect important de la question de l'auteur - les paramètres varargs créent un tableau, et cela génère cet avertissement.
Daniel Yankowsky
2
Je suis d'accord avec @Tom Hawtin - tackline. Pour plus de détails, voir Bloch << Java efficace >> Item 25: Préférer les listes aux tableaux.
Stan Kurilin le
2
Je suis généralement d'accord avec Bloch sur celui-ci, mais varargs est une exception claire à la règle ...
Joeri Hendrickx
0

Lorsque je travaille avec des tableaux de type générique, je suis obligé de passer une référence au type générique. Avec cela, je peux réellement faire le code générique, en utilisant java.lang.reflect.Array.

http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html

KLE
la source
Je ne travaille pas avec des tableaux de type générique, pas directement, juste des varargs de type générique.
matt b