6 votes

Génériques - incohérence du compilateur [jdk 1.8.0_162].

J'ai rencontré un problème avec les génériques qui me laisse perplexe sur la façon dont le compilateur traite les types génériques. Prenons l'exemple suivant :

// simple interface to make it a MCVE
static interface A<F, S> {
    public F getF();    
    public S getS();
}

static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
}

Ce qui suit ne compilera pas car les deux types génériques sont réduits à Object lors de l'appel thenComparing :

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R))
    .thenComparing(wrap((L, R) -> Integer.compare(L, R)));

Mais si je les décompose comme dans l'exemple suivant, tout se compile (et s'exécute) correctement :

Comparator<A<String, Integer>> c = wrap((L, R) -> Integer.compare(L, R));
c = c.thenComparing(wrap((L, R) -> Integer.compare(L, R)));

La question est donc la suivante : que se passe-t-il ici ? Je soupçonne que cela est dû à un comportement bizarre du compilateur plutôt qu'à une spécification du langage prévu ? Ou est-ce que je rate quelque chose d'évident ?

5voto

Shadov Points 3094

La deuxième tentative compile correctement, car vous avez spécifié vous-même le type d'une variable, en indiquant au compilateur ce qu'elle est, car le compilateur n'a pas assez d'informations pour le découvrir.

Regardez cet exemple simplifié, il provient de vavr (excellent d'ailleurs). Il existe un Try<T> Classe qui représente le résultat d'une opération. Paramètre générique T est le type de ce résultat. Il existe une fabrique statique pour créer immédiatement un échec, ce qui signifie que nous n'avons pas de résultat ici, mais le paramètre générique est toujours là :

static <T> Try<T> failure(Throwable exception) {
    return new Try.Failure(exception);
}

Où se trouve le T viennent d'ici ? L'usage ressemble à ça :

public Try<WeakHashMap> method() {
  return Try.failure(new IllegalArgumentException("Some message"));
}

El Try<WeakHashMap> c'est mon choix, pas celui des compilateurs, vous pouvez en fait mettre ce que vous voulez là-dedans parce que vous choisissez le type.

La même chose dans votre exemple, le Comparator a un paramètre générique String seulement, parce que vous l'avez spécifié et que le compilateur l'a accepté (comme avec Try<WeakHashMap> ). Quand on ajoutait un appel chaîné, on obligeait le compilateur à déduire le type lui-même, et c'était Object parce qu'il aurait pu être d'un autre type.

Ce que vous pouvez faire d'autre (remarquez le Testing.<String, Integer>wrap ):

public class Testing {
  static interface A<F, S> {
    public F getF();
    public S getS();
  }

  static <V, S> Comparator<A<V, S>> wrap(Comparator<S> c) {
    return (L, R) -> c.compare(L.getS(), R.getS());
  }

  public static void main(String[] args) {
    Comparator<A<String, Integer>> comp = Testing.<String, Integer>wrap((L, R) -> Integer.compare(L, R))
      .thenComparing(wrap((L, R) -> Integer.compare(L, R)));
  }
}

1voto

Stephan Herrmann Points 1860

Juste pour rendre plus visible un élément central d'information, "l'appel chaîné" :

Dans tout ce qui est de la forme T t = m1().m2() tout ce qui se trouve à gauche du dernier point aura une inférence de type limitée, sans type de cible . Le type de cible T ne peut qu'aider à déduire m2() pas m1() .

Il s'agit d'un choix délibéré dans la conception du langage, afin de limiter la complexité. Dans cette approche, l'inférence du type de m1() doivent être complétées avant de commencer à déduire le type de m2() (sinon, où chercheriez-vous la méthode m2 ?) .

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