web-dev-qa-db-fra.com

Référence constructeur - aucun avertissement lors de la création d'un tableau générique

En Java, il n'est pas possible de créer directement un tableau de type générique:

Test<String>[] t1 = new Test<String>[10]; // Compile-time error

Cependant, nous pouvons le faire en utilisant le type brut:

Test<String>[] t2 = new Test[10]; // Compile warning "unchecked"

Dans Java 8, il est également possible d'utiliser une référence constructeur:

interface ArrayCreator<T> {
    T create(int n);
}

ArrayCreator<Test<String>[]> ac = Test[]::new; // No warning
Test<String>[] t3 = ac.create(10);

Pourquoi le compilateur n'affiche-t-il pas l'avertissement dans le dernier cas? Il utilise toujours le type brut pour créer le tableau, non?

55
Vladimir M.

Votre question est justifiée. En bref, la référence de méthode utilise en effet le type brut (ou devrait utiliser le type brut) et la raison pour laquelle la création de tableaux génériques est interdite, s'applique toujours lors de l'utilisation de références de méthode, par conséquent, la possibilité de créer silencieusement une fonction créant un tableau générique viole clairement l'intention de la conception du langage.

La raison pour laquelle la création d'un tableau générique est interdite est que l'héritage de type tableau, issu d'une ère pré-générique, est incompatible avec le système de type générique. C'est à dire. tu peux écrire:

IntFunction<List<String>[]> af = List[]::new; // should generate warning
List<String>[] array = af.apply(10);
Object[] objArray = array;
objArray[0] = Arrays.asList(42);
List<String> list = array[0]; // heap pollution

À cet endroit, il faut souligner que contrairement à certaines réponses ici, le compilateur effectue pas effectue une inférence de type sur l'expression List[]::new pour déduire le type d'élément générique List<String>. Il est facile de prouver que la création de tableaux génériques est toujours interdite:

IntFunction<List<String>[]> af = List<String>[]::new; // does not compile

Puisque List<String>[]::new est illégal, il serait étrange que List[]::new a été accepté sans avertissement, en supposant qu'il s'agissait effectivement de l'illégal List<String>[]::new.

JLS §15.1 indique clairement:

Si une expression de référence de méthode a la forme ArrayType::new, alors ArrayType doit indiquer un type qui est réifiable (§4.7), sinon une erreur de compilation se produit.

Cela implique déjà que List<String>[]::new est illégal, car List<String> n'est pas vérifiable, tandis que List<?>[]::new est légal, car List<?> est réifiable et List[]::new est légal si l'on considère que List est un type brut, car le type brutList est réifiable.

Le §15.13.1 indique ensuite:

Si l'expression de référence de la méthode a la forme ArrayType::new, une seule méthode notionnelle est considérée. La méthode a un seul paramètre de type int, renvoie le ArrayType, et n'a pas de clause throws. Si n = 1, c'est la seule méthode potentiellement applicable; sinon, il n'y a pas de méthodes potentiellement applicables.

En d'autres termes, le comportement du List[]::new l'expression ci-dessus est la même que si vous aviez écrit:

    IntFunction<List<String>[]> af = MyClass::create;
…
private static List[] create(int i) {
    return new List[i];
}

sauf que la méthode create n'est que théorique. Et en effet, avec cette déclaration de méthode explicite, il n'y a que type brut des avertissements à la méthode create, mais pas non coché des avertissements concernant la conversion de List[] à List<String>[] à la référence de la méthode. Il est donc compréhensible, ce qui se passe dans le compilateur dans le List[]::new cas, où la méthode utilisant des types bruts n'est que théorique, c'est-à-dire n'existe pas dans le code source.

Mais l'absence d'avertissements non vérifiés est une violation claire de JLS §5.1.9, Conversion non vérifiée:

Laissez G nommer une déclaration de type générique avec les paramètres de type n.

Il existe une conversion non vérifiée de la classe brute ou du type d'interface (§4.8) G vers tout type paramétré de la forme G<T₁,...,Tₙ>.

Il existe une conversion non vérifiée à partir du type de tableau brut G[]ᵏ à n'importe quel type de tableau du formulaire G<T₁,...,Tₙ>[]ᵏ. (La notation []ᵏ indique un type de tableau de k dimensions.)

L'utilisation d'une conversion non vérifiée provoque un temps de compilation avertissement non vérifié sauf si tous les arguments de type Tᵢ (1 ≤ i n ) sont des caractères génériques non limités (§4.5.1), ou l'avertissement non contrôlé est supprimé par l'annotation SuppressWarnings (§9.6.4.5).

Donc, une conversion de List[] à List<?>[] est légal, car List est paramétré avec un caractère générique illimité, mais la conversion de List[] à List<String>[] doit produire un avertissement non vérifié, ce qui est crucial ici, car l'utilisation de List[]::new ne produit pas l'avertissement type brut qui apparaît avec une méthode de création explicite. L'absence d'avertissements type brut ne semble pas être une violation (pour autant que je sache §4.8 ) et ce ne serait pas un problème si javac a créé l'avertissement requis non vérifié.

28
Holger

Le mieux que je puisse trouver est que JLS spécifie qu'une référence de méthode au constructeur d'un type générique infère les paramètres génériques: "Si une méthode ou le constructeur est générique, les arguments de type appropriés peuvent être déduits ou fournis explicitement. " Plus tard, cela donne ArrayList::new comme exemple et le décrit comme "des arguments de type déduit pour la classe générique", établissant ainsi que ArrayList::new (et pas ArrayList<>::new) est la syntaxe qui déduit les arguments.

Étant donné une classe:

public static class Test<T> {
    public Test() {}
}

cela donne un avertissement:

Test<String> = new Test(); // No <String>

mais cela ne veut pas:

Supplier<Test<String>> = Test::new; // No <String> but no warning

car Test::new infère implicitement les arguments génériques.

Je suppose donc qu'une référence de méthode à un constructeur de tableau fonctionne de la même manière.

10
Willis Blackburn

Il utilise toujours le type brut pour créer le tableau, non?

Les génériques Java ne sont qu'une illusion au moment de la compilation, donc le type brut sera bien sûr utilisé lors de l'exécution pour créer le tableau.

Pourquoi le compilateur n'affiche-t-il pas l'avertissement dans le dernier cas?

Oui, le casting non contrôlé de Test[] à Test<String>[] est toujours en cours; ça se passe juste en coulisses dans un contexte anonyme.

Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10);

Puisque la méthode anonyme fait le sale boulot, le cast non contrôlé disparaît effectivement du code managé.

5
Patrick Parker