web-dev-qa-db-fra.com

Pouvons-nous distinguer les résultats de l’opérateur de diamants du constructeur brut?

J'ai du code que j'écrirais

GenericClass<Foo> foos = new GenericClass<>();

Tandis qu'un collègue l'écrirait 

GenericClass<Foo> foos = new GenericClass();

en faisant valoir que dans ce cas l'opérateur de diamant n'ajoute rien.

Je suis conscient que les constructeurs qui utilisent réellement des arguments liés au type générique peuvent provoquer une erreur de compilation avec <> au lieu d'une erreur d'exécution dans le cas brut. Et que l'erreur de compilation est bien meilleure. (Comme indiqué dans cette question )

Je suis également conscient du fait que le compilateur (et l'IDE) peuvent générer des avertissements pour l'affectation de types bruts à des génériques.

La question concerne plutôt le cas où il n'y a pas d'argument, ou pas d'argument lié au type générique. Dans ce cas, l'objet construit GenericClass<Foo> foos peut-il différer en fonction du constructeur utilisé? Ou bien, l'effacement de type Javas garantit-il qu'ils sont identiques?

26
Michael Anderson

Pour les instanciations de deux ArrayLists, l’un avec l’opérateur diamantifère à la fin et l’autre sans ...

List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();

... le bytecode généré est identique.

LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: Java.util.List<Java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: Java.util.List<Java.lang.Integer>

Donc, il n'y aurait pas de différence entre les deux selon le bytecode.

Cependant, compiler générera un avertissement non vérifié si vous utilisez la deuxième approche. Par conséquent, la deuxième approche n’a vraiment aucune valeur; tout ce que vous faites est de générer un avertissement non positif faussement positif avec le compilateur qui ajoute au bruit du projet.


J'ai réussi à démontrer un scénario dans lequel cela est activement nuisible. Le nom officiel pour ceci est pollution de tas . Ce n'est pas quelque chose que vous voulez voir apparaître dans votre base de code, et chaque fois que vous voyez ce type d'invocation, il devrait être supprimé.

Considérons cette classe qui étend certaines fonctionnalités de ArrayList.

class Echo<T extends Number> extends ArrayList<T> {
    public Echo() {

    }

    public Echo(Class<T> clazz)  {
        try {
            this.add(clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            System.out.println("YOU WON'T SEE ME THROWN");
            System.exit(-127);
        }
    }
}

Semble assez inoffensif; vous pouvez ajouter une instance de votre type lié.

Cependant, si nous jouons avec types bruts ..., cela peut avoir des effets secondaires malheureux.

final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);

System.out.println(oops);

Ceci affiche [[], 2, 3] au lieu de lancer n'importe quel type d'exception. Si nous voulions faire une opération sur toutes les Integers de cette liste, nous rencontrerions une ClassCastException, grâce à cette invocation ArrayList.class délicieuse.

Bien sûr, tout cela pourrait être évité si l’opérateur de diamants était ajouté, ce qui garantirait que nous n’aurions pas un tel scénario entre nos mains.

Maintenant, comme nous avons introduit un type brut dans le mélange, Java ne peut pas effectuer de vérification de type selon JLS 4.12.2:

Par exemple, le code:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning

donne lieu à un avertissement non coché lors de la compilation, car il ne l’est pas possible de vérifier, soit au moment de la compilation (dans les limites de les règles de vérification du type à la compilation), soit au moment de l'exécution, que le fichier la variable l fait en effet référence à un List<String>.

La situation ci-dessus est très similaire. Si nous examinons le premier exemple que nous avons utilisé, tout ce que nous faisons est de ne pas ajouter de variable supplémentaire à la question. La pollution de tas se produit tout de même.

List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;

Ainsi, alors que le code d'octet est identique (probablement dû à l'effacement), il n'en reste pas moins qu'un comportement différent ou aberrant peut découler d'une déclaration de ce type.

N'utilisez pas de types bruts , mmkay?

11
Makoto

Le JLS est en fait assez clair sur ce point. http://docs.Oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2

Tout d'abord, il indique "Une déclaration de classe générique définit un ensemble de types paramétrés (§4.5), un pour chaque paramétrage possible de la section de paramètre type par des arguments de type. Tous ces types paramétrés partagent la même classe au moment de l'exécution."

Ensuite, il nous donne le bloc de code

Vector<String>  x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
boolean b = x.getClass() == y.getClass();

et dit qu'il "entraînera la variable b contenant la valeur true."

Le test d'instance sur l'égalité (==) indique que x et y partagent exactement le même objet Class.

Maintenant, faites-le avec l'opérateur de diamants et sans.

Vector<Integer> z = new Vector<>();
Vector<Integer> w = new Vector();
boolean c = z.getClass() == w.getClass();
boolean d = y.getClass() == z.getClass();

Encore une fois, c est true, et il en est de même d.

Donc, si, si j'ai bien compris, vous vous demandez s'il existe une différence, à l'exécution ou dans le bytecode, entre l'utilisation du diamant et non, la réponse est simple. Il n'y a pas de différence.

Que ce soit mieux d'utiliser l'opérateur diamant dans ce cas est une question de style et d'opinion.

P.S. Ne tirez pas sur le messager. J'utiliserais toujours l'opérateur de diamant dans ce cas. Mais c'est simplement parce que j'aime ce que le compilateur fait pour moi en général, les génériques, et je ne veux pas tomber dans de mauvaises habitudes.

P.P.S. N'oubliez pas que cela peut être un phénomène temporaire. http://docs.Oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 nous avertit que "l'utilisation de types bruts dans du code écrit après l'introduction de génériques Le langage Java est fortement déconseillé. Il est possible que les versions futures du langage Java interdisent l’utilisation de types bruts. "

3
Erick G. Hagstrom

Vous pouvez avoir un problème avec le constructeur par défaut si vos arguments génériques sont limités. Par exemple, voici une implémentation incomplète et incomplète de la liste de nombres qui suit la somme totale:

public class NumberList<T extends Number> extends AbstractList<T> {
    List<T> list = new ArrayList<>();
    double sum = 0;

    @Override
    public void add(int index, T element) {
        list.add(index, element);
        sum += element.doubleValue();
    }

    @Override
    public T remove(int index) {
        T removed = list.remove(index);
        sum -= removed.doubleValue();
        return removed;
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }

    public double getSum() {
        return sum;
    }
}

L'omission des arguments génériques pour le constructeur par défaut peut conduire à ClassCastException au moment de l'exécution:

List<String> list = new NumberList(); // compiles with warning and runs normally
list.add("test"); // throws CCE

Cependant, l'ajout de l'opérateur diamant produira une erreur de compilation:

List<String> list = new NumberList<>(); // error: incompatible types
list.add("test");
1
Tagir Valeev

Dans votre exemple spécifique: Oui, ils sont identiques.

Généralement: Attention, ils peuvent ne pas l'être!

La raison en est que différents constructeurs/méthodes overloaded peuvent être appelés lorsque le type brut est utilisé; c’est pas seulement que vous obtenez une meilleure sécurité de type et évitez une exécution ClassCastException.

Constructeurs surchargés

public class Main {

    public static void main(String[] args) {
        Integer anInteger = Integer.valueOf(1);
        GenericClass<Integer> foosRaw = new GenericClass(anInteger);
        GenericClass<Integer> foosDiamond = new GenericClass<>(anInteger);
    }

    private static class GenericClass<T> {

        public GenericClass(Number number) {
            System.out.println("Number");
        }

        public GenericClass(T t) {
            System.out.println("Parameter");
        }
    }
}

La version avec diamant appelle le constructeur différent; le résultat du programme ci-dessus est:

Number
Parameter

Méthodes surchargées

public class Main {

    public static void main(String[] args) {
        method(new GenericClass());
        method(new GenericClass<>());
    }

    private static void method(GenericClass<Integer> genericClass) {
        System.out.println("First method");
    }

    private static void method(Object object) {
        System.out.println("Second method");
    }

    private static class GenericClass<T> { }
}

La version avec diamant appelle la méthode différente; le résultat:

First method
Second method
1
Dragan Bozanovic

Ce n'est pas une réponse complète - mais fournit quelques détails supplémentaires.

Bien que vous ne puissiez pas distinguer les appels comme

GenericClass<T> x1 = new GenericClass<>();
GenericClass<T> x2 = new GenericClass<T>();
GenericClass<T> x3 = new GenericClass();

Il existe des outils qui vous permettront de distinguer entre

GenericClass<T> x4 = new GenericClass<T>() { };
GenericClass<T> x5 = new GenericClass() { };

Remarque: Bien qu'il semble qu'il nous manque new GenericClass<>() { }, il ne s'agit pas actuellement de Java valide.

La clé étant que les informations de type sur les paramètres génériques sont stockées pour les classes anonymes. En particulier, nous pouvons accéder aux paramètres génériques via

Type superclass = x.getClass().getGenericSuperclass();
Type tType = (superclass instanceof ParameterizedType) ?
             ((ParameterizedType) superclass).getActualTypeArguments()[0] : 
             null;
  • Pour x1, x2 et x3tType sera une instance de TypeVariableImpl (la même instance dans les trois cas, ce qui n’est pas surprenant puisque getClass() renvoie le même objet pour les trois cas.

  • Pour x4tType sera T.class

  • Pour x5getGenericSuperclass() ne renvoie pas une instance de ParameterizedType, mais plutôt une Class (infact GenericClass.class)

Nous pourrions ensuite utiliser ceci pour déterminer si notre obect était construit avec (x1, x2 ou x3) ou x4 ou x5.

0
Michael Anderson