2015-08-05 5 views
4

Я наткнулся на проблему при работе с каким-то старым кодом, заменив несколько анонимных классов либо лямбда-выражениями, либо ссылками на методы. Проблема немного сложна для объяснения словами, но я сделаю все возможное, и я также добавил короткий пример, иллюстрирующий мою проблему, насколько это возможно из моих возможностей ниже.Ошибка компилятора «несовместимых типов» с привязкой лямбда/метода и генериками

Мой пример состоит из ...

  1. Функциональный интерфейс, GenericListener, который принимает параметр Vтипа и имеет единственный метод "genericCallback (V genericValue)".

  2. Класс, CallbackProducer, который принимает параметр Tтипа. Этот класс также имеет метод добавления GenericListener с типом Integer.

  3. A Основной класс, который создает CallbackProducers и добавляет к ним GenericListeners.

Когда я запускаю метод addIntegerListener CallbackProducer от конструктора Main, я получаю ошибку компилятор: «несовместимые типы» всякий раз, когда я избегать указания типа T. CallbackProducer в

Метод addIntegerListener использует только V GenericListener в Насколько я знаю, он не использует CallbackProducer's T никоим образом.

Я поместил несколько вызовов addIntegerListener + комментариев в конструкторе Main, 3 из которых вызывают ошибки компилятора. Но, насколько я вижу (и, по мнению IntelliJ), все они должны быть законными. Если вы закомментируете 3 первых вызова addIntegerListener, приложение будет компилироваться и работать просто отлично.

Кроме того, если CallbackProducer не использовал дженерики, и мы полностью удалили параметр типа T, будут скомпилированы 3 первых вызова addIntegerListener.

Есть ли причина для такого поведения? Я что-то недопонимаю, или это слабость или ошибка в java-компиляторе? (Я в настоящее время использую java 1.8_51)

Заранее благодарим за любые разъяснения!

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); 
    } 
} 

Вот сокращенная вниз версия без всяких комментариев беспорядка и только два вызовов «addIntegerListener», один из которых вызывает ошибку компилятора.

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); 
    } 
} 

ответ

1

Все три ошибки компилятора из-за того, что вы используете сырые CallbackProducer. Когда вы используете raw CallbackProducer, все аргументы типа подвергаются стиранию типа, так что любой T, такой как ваш, без верхней границы, становится Object.

Из-за этого метод addIntegerListener ожидает в качестве параметра необработанного GenericListener, что-то, что integerReceived больше не подходит. Метод integerReceived принимает Integer, а не Object, так как необработанный GenericListener будет поставляться.

Вы должны предоставить угловые скобки <> по адресу CallbackProducer, чтобы избежать использования сырых типов, как это было сделано на последующих примерах.

+0

Спасибо за ответ. То, что вы говорите, может для всех, кого я знаю очень хорошо, является причиной, по которой компилятор жалуется, но CallbackProducer's никогда не используется. «addIntegerListener» уже принимает в качестве аргумента GenericListener . Кроме того, следующие строки компилируются и выполняются просто отлично: 'CallbackProducer производитель2 = new CallbackProducer <>(); производитель2.addIntegerListener (this :: integerReceived); ' Теперь я говорю, что T - это Double, когда я работаю с целыми слушателями. Он по-прежнему работает, поскольку T не имеет отношения к слушателю. – Serenic

+1

Это не имеет никакого отношения к тому, что такое аргумент типа 'CallProducer'. Он имеет все, что связано с тем, что вы использовали raw 'CallProducer'. – rgettman

+0

Просто, чтобы убедиться, что я понимаю: вы говорите, что если я использую необработанный CallbackProducer (или любой другой класс, который принимает параметр типа), все другие аргументы типа этого производителя будут стираться, даже если они полностью не связаны с «T» CallbackProducer? (как в моем случае) Я не сомневаюсь, что вы правы, но из любопытства, есть ли причина для такого поведения? Я не понимаю, почему это должно быть проблемой. Типы гарантируют соответствие. Знаете ли вы, что это слабость в компиляторе, который может быть рассмотрен в будущем, или если это по дизайну? – Serenic