web-dev-qa-db-fra.com

Pourquoi ce programme Java 8 ne se compile pas?

Ce programme se compile très bien en Java 7 (ou en Java 8 avec -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 rétrocompatibilité incompatibilité de source entre Java 7 et 8. J'ai parcouru - Incompatibilités entre Java SE 8 et Java SE 7 liste, mais je n'ai rien trouvé qui conviendrait à 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)
77
ghik

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

20
Vicente Romero

La spécification de langage Java Java a changé de manière significative en ce qui concerne l'inférence de type . Dans JLS7, le type l'inférence a été décrite dans §15.12.2.7 et §15.12.2.8 , alors qu'en JLS8, il y a tout un chapitre dédié à - Chapitre 18. Tapez Inférence .

Les règles sont assez complexes, à la fois dans JLS7 et JLS8. Il est difficile de distinguer les différences, mais il y a évidemment des différences, comme le montre la section §18.5.2 :

Cette stratégie d'inférence est différente de la Java SE 7 Edition of The Java Language Specification [..]).

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

[..] La stratégie permet des résultats raisonnables dans des cas d'utilisation typiques, et est rétrocompatible avec l'algorithme dans l'édition Java SE 7 de The Java Language Spécification.

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

new Acceptor<>(new Impl());

Dans ce cas, il n'y a pas de type cible . Cela signifie que Expression de création d'instance de classe n'est pas une expressions poly , et les règles pour l'inférence de type sont plus simples. Voir §18.5.2 :

Si l'invocation n'est pas une expression poly, laissez l'ensemble lié B3 être le même que B2.

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

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

Bien qu'il existe un type dans le contexte de l'expression, il ne compte pas comme type cible . Si une expression de création d'instance de classe ne se produit pas dans une expression d'affectation ou une expression d'invocation , il ne peut pas s'agir d'une expression poly . Voir §15.9 :

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

Revenons à votre déclaration. La partie pertinente du JLS8 est à nouveau §18.5.2 . Cependant, je ne peux pas vous dire si la déclaration suivante est correcte selon le JLS8, si le compilateur a raison avec le message d'erreur. Mais au moins, vous avez des alternatives et des pointeurs pour plus d'informations.

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

L'inférence de type a été modifiée dans Java 8. Maintenant, l'inférence de type examine à la fois le type cible et les types de paramètres, pour les constructeurs et les méthodes. Tenez compte des éléments suivants:

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 dans Java 8 (mais pas dans Java 7):

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

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

Bien sûr, cela donne une erreur dans les deux:

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

Ceci est également correct dans 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>

Ce qui suit donne une erreur dans les deux, 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

De toute évidence, Java 8 a rendu le système d'inférence de type plus intelligent. Cela provoque-t-il des incompatibilités? Généralement, non. En raison de l'effacement des types, peu importe quels types ont été déduits, tant que le programme Java 8 compile-t-il tous les programmes Java 7? Cela devrait, mais vous avez évoqué un cas où ce n'est pas le cas).

Ce qui semble se produire, c'est que Java 8 ne gère pas bien les caractères génériques. Au lieu de les considérer comme un manque de contrainte, il semble les traiter comme une contrainte restrictive qu'il ne peut pas Je ne sais pas si cela suit la lettre du JLS, mais j'appellerais cela un bug au moins dans l'esprit.

Pour info, cela fonctionne (notez que mon Acceptor n'a pas les contraintes de type que le vôtre fait):

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

Notez que votre exemple utilise un type générique 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.)

7
Aleksandr Dubinsky