I stumbled upon an issue when working through some old code, replacing several anonymous classes with either lambda expressions or method references. The problem is a bit hard to explain with words, but I'll do my best, and I've also added a short example illustrating my problem to the best of my abilities below.
My example consists of...
A functional interface, GenericListener, which takes a type parameter V and has a single method "genericCallback(V genericValue)".
A class, CallbackProducer, which takes a type parameter T. This class also has a method to add a GenericListener with type Integer.
A Main class which creates CallbackProducers and adds GenericListeners to them.
When I run CallbackProducer's addIntegerListener method from Main's constructor, I get the compiler error: "incompatible types" whenever i avoid specifying the type of CallbackProducer's T.
The method addIntegerListener only uses GenericListener's V. As far as I know, it doesn't use CallbackProducer's T in any way.
I've put several calls to addIntegerListener + comments in Main's constructor, 3 of which cause compiler errors. But as far as I can see (and according to IntelliJ) all of them should be legal. If you comment out the 3 first calls to addIntegerListener the application will compile and run just fine.
Also, if CallbackProducer didn't use generics, and we removed the type parameter T completely, the 3 first calls to addIntegerListener would compile.
Is there a reason for this behavior? Am I misunderstanding something, or is this a weakness or bug in the java compiler? (I'm currently using java 1.8_51)
Thanks in advance for any 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); } } Here's a shortened down version without all the comment clutter and with only two calls to "addIntegerListener", one of which causes compiler error.
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); } }