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?
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 typeint
, renvoie le ArrayType, et n'a pas de clausethrows
. 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 formeG<T₁,...,Tₙ>
.Il existe une conversion non vérifiée à partir du type de tableau brut
G[]ᵏ
à n'importe quel type de tableau du formulaireG<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'annotationSuppressWarnings
(§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é.
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.
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é.