Reputation: 243
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);
}
}
Upvotes: 5
Views: 4855
Reputation: 178333
All 3 compiler errors are due to the fact that you are using a raw CallbackProducer
. When you use a raw CallbackProducer
, all type arguments undergo type erasure, such that any T
, such as yours, without any upper bound, becomes Object
.
Because of this, the addIntegerListener
method expects a raw GenericListener
as a parameter, something that integerReceived
no longer fits. The integerReceived
method takes an Integer
, not an Object
, as a raw GenericListener
would supply.
You must supply the angle brackets <>
on CallbackProducer
to avoid using raw types, as you've done on your subsequent examples.
Upvotes: 1