2015-08-05 1 views
4

Je suis tombé sur un problème lorsque je travaillais sur un ancien code, en remplaçant plusieurs classes anonymes par des expressions lambda ou des références de méthode. Le problème est un peu difficile à expliquer avec des mots, mais je ferai de mon mieux, et j'ai également ajouté un court exemple illustrant mon problème au mieux de mes capacités ci-dessous.Erreur de compilateur "incompatible types" avec références et génériques lambda/method

Mon exemple consiste en ...

  1. une interface fonctionnelle, GenericListener, ce qui prend un paramètre de type V et a une seule méthode "genericCallback (V genericValue)".

  2. Une classe, CallbackProducer, qui prend un paramètre de type T . Cette classe a également une méthode pour ajouter un GenericListener avec le type Integer.

  3. A Classe principale qui crée CallbackProducers et leur ajoute GenericListeners.

Quand je lance la méthode addIntegerListener de CallbackProducer du constructeur de Main, je reçois l'erreur du compilateur : « types incompatibles » chaque fois que j'éviter de spécifier le type de T.

La méthode addIntegerListener de CallbackProducer n'utilise V de GenericListener Pour autant que je sache, il n'utilise en aucun cas le T de CallbackProducer.

J'ai mis plusieurs appels à addIntegerListener + commentaires dans le constructeur Main, dont 3 causent des erreurs de compilation. Mais d'après ce que je peux voir (et selon IntelliJ), tous devraient être légaux. Si vous commentez les 3 premiers appels à addIntegerListener, l'application compilera et fonctionnera très bien.

De même, si CallbackProducer n'utilisait pas de génériques et que nous supprimions complètement le paramètre de type T, les 3 premiers appels à addIntegerListener se compileraient.

Y a-t-il une raison à ce comportement? Ai-je mal compris quelque chose, ou est-ce une faiblesse ou un bug dans le compilateur Java? (J'utilise actuellement java 1.8_51)

Merci d'avance pour toute clarification!

import javax.swing.*; 

public class Main { 

    public static void main(final String[] args) { 
     SwingUtilities.invokeLater(Main::new); 
    } 

    public Main() { 

     // Compiler error, type of CallbackProducer's "T" not specified 
     CallbackProducer producer1 = new CallbackProducer(); 
     producer1.addIntegerListener(this::integerReceived); 

     // Compiler error, no diamond brackets for CallbackProducer 
     new CallbackProducer().addIntegerListener(this::integerReceived); 

     // Also compiler error for lambdas with no diamond brackets on CallbackProducer 
     new CallbackProducer().addIntegerListener(intValue -> integerReceived(intValue)); 

     // Works because a (any) type for CallbackProducer's "T" is specified 
     CallbackProducer<Object> producer2 = new CallbackProducer<>(); 
     producer2.addIntegerListener(this::integerReceived); 

     // Works because of the diamond brackets 
     new CallbackProducer<>().addIntegerListener(this::integerReceived); 

     // Lambda also works with diamond brackets 
     new CallbackProducer<>().addIntegerListener(intValue -> integerReceived(intValue)); 

     // This variant also works without specifying CallbackProducer's "T" 
     // ... but it is a workaround I'd prefer to avoid if possible :-P 
     GenericListener<Integer> integerListener = this::integerReceived; 
     new CallbackProducer().addIntegerListener(integerListener); 
    } 

    private void integerReceived(Integer intValue) { 
     System.out.println("Integer callback received: " + intValue); 
    } 

    // A callback producer taking generic listeners 
    // Has a type parameter "T" which is completely unrelated to 
    // GenericListener's "V" and not used for anything in this 
    // example really, except help provoking the compiler error 
    public class CallbackProducer<T> { 
     // Adds a listener which specifically takes an Integer type as argument 
     public void addIntegerListener(GenericListener<Integer> integerListener) { 
      // Just a dummy callback to receive some output 
      integerListener.genericCallback(100); 
     } 
    } 

    // A simple, generic listener interface that can take a value of any type 
    // Has a type parameter "V" which is used to specify the value type of the callback 
    // "V" is completely unrelated to CallbackProducer's "T" 
    @FunctionalInterface 
    public interface GenericListener<V> { 
     void genericCallback(V genericValue); 
    } 
} 

Voici une version raccourcie vers le bas sans tous les échos de commentaires et avec seulement deux appels à « addIntegerListener », l'une qui provoque l'erreur du compilateur.

import javax.swing.*; 

public class Main { 

    public static void main(final String[] args) { 
     SwingUtilities.invokeLater(Main::new); 
    } 

    public Main() { 

     CallbackProducer producer1 = new CallbackProducer(); 
     producer1.addIntegerListener(this::integerReceived); // Compiler error 

     CallbackProducer<Object> producer2 = new CallbackProducer<>(); 
     producer2.addIntegerListener(this::integerReceived); // Compiles OK  
    } 

    private void integerReceived(Integer intValue) { 
     System.out.println("Integer callback received: " + intValue); 
    } 

    public class CallbackProducer<T> { 
     public void addIntegerListener(GenericListener<Integer> integerListener) { 
      integerListener.genericCallback(100); 
     } 
    } 

    @FunctionalInterface 
    public interface GenericListener<V> { 
     void genericCallback(V genericValue); 
    } 
} 

Répondre

1

Toutes les 3 erreurs du compilateur sont dues au fait que vous utilisez un CallbackProducer brut. Lorsque vous utilisez un CallbackProducer brut, tous les arguments de type subissent un effacement de type, de sorte que tout T, tel que le vôtre, sans limite supérieure, devient Object. Pour cette raison, la méthode addIntegerListener attend un GenericListener brut comme paramètre, ce que integerReceived ne correspond plus. La méthode integerReceived prend un Integer, pas un Object, comme un GenericListener fournirait.

Vous devez fournir les chevrons <> sur CallbackProducer pour éviter d'utiliser des types bruts, comme vous l'avez fait dans les exemples suivants.

+0

Merci pour la réponse. Ce que vous dites peut très bien être la raison pour laquelle le compilateur se plaint, mais le de CallbackProducer n'est jamais utilisé. "addIntegerListener" prend déjà spécifiquement un GenericListener comme argument. En outre, les lignes suivantes se compilent et s'exécutent correctement: 'CallbackProducer producer2 = new CallbackProducer <>(); producer2.addIntegerListener (this :: integerReceived); ' Maintenant, je dis que T est un double, quand je travaille avec des auditeurs entiers. Cela fonctionne toujours, puisque T n'est pas lié à l'écouteur. – Serenic

+1

Cela n'a rien à voir avec l'argument de type 'CallProducer'. Cela a tout à voir avec le fait que vous avez utilisé un CallProducer brut. – rgettman

+0

Juste pour m'assurer que je comprends: Ce que vous dites est que si j'utilise un CallbackProducer brut (ou toute autre classe qui prend un paramètre de type), tous les autres arguments de type dans ce producteur obtiennent le type effacé, même s'ils sont complètement indépendants le "T" de CallbackProducer? (comme dans mon cas) Je ne doute pas que tu aies raison, mais par curiosité, y a-t-il une raison à ce comportement? Je ne vois pas pourquoi cela devrait poser problème. Les types sont garantis pour correspondre. Savez-vous s'il s'agit d'une faiblesse dans le compilateur qui pourrait être traitée à l'avenir ou si c'est par conception? – Serenic