77 votes

Pourquoi ce programme Java 8 ne compile-t-il pas ?

Ce programme se compile très bien en Java 7 (ou en Java 8 avec la fonction -source 7 ), mais ne parvient pas à compiler avec Java 8 :

interface Iface<T> {}
class Impl implements Iface<Impl> {}

class Acceptor<T extends Iface<T>> {
    public Acceptor(T obj) {}
}

public class Main {
    public static void main(String[] args) {
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
    }
}

Résultat :

Main.java:10: error: incompatible types: cannot infer type arguments for Acceptor<>
        Acceptor<?> acceptor = new Acceptor<>(new Impl());
                                           ^
    reason: inference variable T has incompatible bounds
      equality constraints: Impl
      upper bounds: Iface<CAP#1>,Iface<T>
  where T is a type-variable:
    T extends Iface<T> declared in class Acceptor
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Iface<CAP#1> from capture of ?
1 error

En d'autres termes, il s'agit d'une à l'envers incompatibilité de source entre Java 7 et 8. J'ai parcouru Incompatibilités entre Java SE 8 et Java SE 7 mais je n'ai rien trouvé qui corresponde à mon problème.

Alors, c'est un bug ?

Environnement :

$ /usr/lib/jvm/java-8-oracle/bin/java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)

40voto

nosid Points 20267

La spécification du langage Java a changé de manière significative concernant inférence de type . Dans JLS7, inférence de type a été décrit dans §15.12.2.7 et §15.12.2.8 alors que dans JLS8, il y a un chapitre entier consacré à Chapitre 18. Inférence de type .

Les règles sont assez complexes, tant en JLS7 qu'en JLS8. Il est difficile de faire la part des choses, mais il y a manifestement des différences, comme le montre la section §18.5.2 :

Cette stratégie d'inférence est différente de celle de l'édition Java SE 7 de la spécification du langage Java [ ].

Cependant, l'intention de ce changement était d'être rétrocompatible. Voir le dernier paragraphe de la section §18.5.2 :

[La stratégie permet d'obtenir des résultats raisonnables dans des cas d'utilisation typiques et est rétrocompatible avec l'algorithme de l'édition Java SE 7 de la spécification du langage Java.

Je ne peux pas dire si c'est vrai ou non. Cependant, il y a quelques variations intéressantes de votre code, qui ne montrent pas le problème. Par exemple, l'instruction suivante compile sans erreur :

new Acceptor<>(new Impl());

Dans ce cas, il n'y a pas type de cible . Cela signifie que le Instance de classe Créer une expression n'est pas un expressions poly et les règles pour inférence de type sont plus simples. Voir §18.5.2 :

Si l'invocation n'est pas une expression poly, laissons l'ensemble lié B 3 être identique à B 2 .

C'est aussi la raison pour laquelle la déclaration suivante fonctionne.

Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());

Bien qu'il y ait un type dans le contexte de l'expression, il ne compte pas comme type de cible . Si un expression de création d'une instance de classe ne se produit ni dans un expression de mission ou un expression d'invocation alors il ne peut pas être un poly-expression . Voir §15.9 :

Une expression de création d'instance de classe est une expression poly (§15.2) si elle utilise la forme diamant pour les arguments de type de la classe, et si elle apparaît dans un contexte d'affectation ou un contexte d'invocation (§5.2, §5.3). Sinon, c'est une expression autonome.

Je reviens sur votre déclaration. La partie pertinente du JLS8 est encore une fois §18.5.2 . Cependant, je ne peux pas vous dire si la déclaration suivante est correcte selon le JLS8, ou si le compilateur a raison avec le message d'erreur. Mais au moins, vous avez obtenu quelques alternatives et des pointeurs pour des informations supplémentaires.

Acceptor<?> acceptor = new Acceptor<>(new Impl());

20voto

Vicente Romero Points 295

Merci pour le rapport. Cela ressemble à un bug. Je vais m'en occuper et probablement ajouter une meilleure réponse une fois que nous aurons plus d'informations sur la raison pour laquelle cela se produit. J'ai classé cette entrée de bogue JDK-8043926 pour le suivre.

7voto

Aleksandr Dubinsky Points 2488

L'inférence de type a été modifiée dans Java 8. Désormais, l'inférence de type examine à la fois le type cible et les types de paramètres, tant pour les constructeurs que pour les méthodes. Considérez ce qui suit :

interface Iface {}
class Impl implements Iface {}
class Impl2 extends Impl {}

class Acceptor<T> {
    public Acceptor(T obj) {}
}

<T> T foo(T a) { return a; }

Ce qui suit est maintenant correct en Java 8 (mais pas en Java 7) :

Acceptor<Impl> a = new Acceptor<>(new Impl2());

// Java 8 cleverly infers Acceptor<Impl>
// While Java 7 infers Acceptor<Impl2> (causing an error)

Ceci, bien sûr, donne une erreur dans les deux :

Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());

C'est également possible en Java 8 :

Acceptor<Impl> a = foo (new Acceptor<>(new Impl2())); 

// Java 8 infers Acceptor<Impl> even in this case
// While Java 7, again, infers Acceptor<Impl2>
//   and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>

L'exemple suivant donne une erreur dans les deux cas, mais l'erreur est différente :

Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2()));

// Java 7:
// incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>

// Java 8:
// incompatible types: inferred type does not conform to upper bound(s)
//     inferred: Acceptor<Impl2>
//     upper bound(s): Acceptor<Impl>,java.lang.Object

Il est clair que Java 8 a rendu le système d'inférence de type plus intelligent. Cela provoque-t-il des incompatibilités ? En général, non. En raison de l'effacement des types, les types déduits n'ont pas d'importance, tant que le programme se compile. Java 8 compile-t-il tous les programmes Java 7 ? Il devrait, mais vous avez soulevé un cas où il ne le fait pas.

Ce qui semble se passer, c'est que Java 8 ne gère pas bien les caractères génériques. Au lieu de les considérer comme une absence de contrainte, il semble les traiter comme une contrainte restrictive qu'il ne peut pas satisfaire. Je ne suis pas sûr qu'il respecte la lettre du JLS, mais j'appellerais cela un bogue, au moins dans l'esprit.

Pour info, cela fonctionne (notez que ma Acceptor n'a pas les mêmes contraintes de type que la vôtre) :

Acceptor<?> a = new Acceptor<>(new Impl2());

Notez que votre exemple utilise un type joker en dehors d'un paramètre de méthode (ce qui est déconseillé), je me demande si le même problème se produira dans un code plus typique qui utilise l'opérateur diamant dans les appels de méthode. (Probablement.)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X