web-dev-qa-db-fra.com

Que signifient les arguments de type constructeur lorsqu'ils sont placés * avant * le type?

J'ai récemment rencontré cette inhabituelle (pour moi) Java ... en voici un exemple:

List list = new <String, Long>ArrayList();

Notez le positionnement du <String, Long> arguments de type ... ce n'est pas après le type comme d'habitude mais avant. Cela ne me dérange pas d'admettre que je n'ai jamais vu cette syntaxe auparavant. Notez également qu'il existe 2 arguments de type lorsque ArrayList n'en a qu'un.

Le positionnement des arguments de type a-t-il la même signification que de les placer après le type? Sinon, que signifie le positionnement différent?

Pourquoi est-il légal d'avoir 2 arguments de type alors que ArrayList n'en a qu'un?

J'ai fouillé les endroits habituels, par exemple. Angelika Langer et ici, mais ne trouve aucune mention de cette syntaxe ailleurs que les règles de grammaire dans le fichier de grammaire Java Java sur le projet ANTLR).

127
Nathan Adams

Appel d'un constructeur générique

Ceci est bien inhabituel, mais Java entièrement valide. Pour comprendre, nous devons savoir qu'une classe peut avoir un constructeur générique, par exemple:

public class TypeWithGenericConstructor {

    public <T> TypeWithGenericConstructor(T arg) {
        // TODO Auto-generated constructor stub
    }

}

Je suppose que le plus souvent, lors de l'instanciation de la classe via le constructeur générique, nous n'avons pas besoin de rendre l'argument type explicite. Par exemple:

    new TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

Maintenant, T est clairement LocalDate. Cependant, il peut y avoir des cas où Java ne peut pas déduire (déduire) l'argument type. Ensuite, nous le fournissons explicitement en utilisant la syntaxe de votre question:

    new <LocalDate>TypeWithGenericConstructor(null);

Bien sûr, nous pouvons également le fournir même s'il n'est pas nécessaire si nous pensons qu'il aide à la lisibilité ou pour une raison quelconque:

    new <LocalDate>TypeWithGenericConstructor(LocalDate.now(ZoneId.systemDefault()));

Dans votre question, vous semblez appeler le constructeur Java.util.ArrayList. Ce constructeur n'est pas générique (seule la classe ArrayList dans son ensemble, c'est autre chose). Pour savoir pourquoi Java vous permet de fournir des arguments de type dans l'appel lorsqu'ils ne sont pas utilisés, voir ma modification ci-dessous. Mon Eclipse me donne un avertissement :

Arguments de type inutilisés pour le constructeur non générique ArrayList () de type ArrayList; il ne doit pas être paramétré avec des arguments

Mais ce n'est pas une erreur, et le programme fonctionne correctement (je reçois également des avertissements sur les arguments de type manquants pour List et ArrayList, mais c'est encore une autre histoire).

Classe générique contre constructeur générique

Le positionnement des arguments de type a-t-il la même signification que de les placer après le type? Sinon, que signifie le positionnement différent?

Non, c'est différent. L'argument/s de type habituel après le type (ArrayList<Integer>()) concerne la classe générique . Les arguments de type avant sont pour le constructeur .

Les deux formes peuvent également être combinées:

    List<Integer> list = new <String, Long>ArrayList<Integer>();

Je considérerais cela un peu plus correct puisque nous pouvons maintenant voir que la liste stocke Integer objets (je préférerais toujours laisser de côté le <String, Long> Vide de sens, bien sûr).

Pourquoi est-il légal d'avoir 2 arguments de type alors qu'ArrayList n'en a qu'un?

Premièrement, si vous fournissez des arguments de type avant le type, vous devez fournir le numéro correct pour le constructeur, pas pour la classe, donc cela n'a rien à voir avec le nombre d'arguments de type que la classe ArrayList a . Cela signifie vraiment que dans ce cas, vous ne devez pas en fournir car le constructeur ne prend pas d'arguments de type (ce n'est pas générique). Quand vous en fournissez quand même, ils sont ignorés, c'est pourquoi peu importe le nombre ou le peu que vous fournissez.

Pourquoi les arguments de type sans signification sont-ils autorisés?

Modifiez avec grâce à @Slaw pour le lien: Java autorise les arguments de type sur tous les appels de méthode. Si la méthode appelée est générique, les arguments de type sont utilisés; sinon, ils sont ignorés. Par exemple :

    int length = "My string".<List>length();

Oui, c'est absurde. La spécification de langage Java JLS) donne cette justification dans la sous-section 15.12.2.1:

Cette règle découle de problèmes de compatibilité et de principes de substituabilité. Étant donné que les interfaces ou les superclasses peuvent être générées indépendamment de leurs sous-types, nous pouvons remplacer une méthode générique par une méthode non générique. Cependant, la méthode prioritaire (non générique) doit être applicable aux appels à la méthode générique, y compris les appels qui passent explicitement des arguments de type. Sinon, le sous-type ne serait pas substituable à son supertype généré.

L'argument ne s'applique pas aux constructeurs, car ils ne peuvent pas être directement remplacés. Mais je suppose qu'ils voulaient avoir la même règle afin de ne pas rendre les règles déjà compliquées trop compliquées. Dans tous les cas, la section 15.9.3 sur l'instanciation et new plus d'une fois fait référence au 15.12.2.

Liens

142
Ole V.V.

Apparemment, vous pouvez préfixer n'importe quelle méthode/constructeur non générique avec n'importe quel paramètre générique que vous aimez:

new <Long>String();
Thread.currentThread().<Long>getName();

Le compilateur s'en fiche, car il n'a pas à faire correspondre ces arguments de type aux paramètres génériques réels.

Dès que le compilateur doit vérifier les arguments, il se plaint d'un décalage:

Collections.<String, Long>singleton("A"); // does not compile

Cela me semble être un bug du compilateur.

1
svenmeier