【问题标题】:"incompatible types" compiler error with lambda / method referencing and genericslambda / 方法引用和泛型的“不兼容类型”编译器错误
【发布时间】:2015-08-05 18:29:36
【问题描述】:

我在处理一些旧代码时偶然发现了一个问题,用 lambda 表达式或方法引用替换了几个匿名类。这个问题有点难以用语言来解释,但我会尽力而为,我还在下面添加了一个简短的例子来说明我的问题。

我的例子包括...

  1. 一个函数接口,GenericListener,它接受一个类型参数V,并且有一个方法“genericCallback(V genericValue)”。

  2. 一个类,CallbackProducer,它接受一个类型参数T。这个类还有一个方法来添加一个 Integer 类型的 GenericListener。

  3. 一个 Main 类,用于创建 CallbackProducers 并向其添加 GenericListener。

当我从 Main 的构造函数运行 CallbackProducer 的 addIntegerListener 方法时,每当我避免指定 CallbackProducer 的 T 的类型时,都会收到 编译器错误:“不兼容的类型”

addIntegerListener 方法只使用了 GenericListener 的 V。据我所知,它并没有以任何方式使用 CallbackProducer 的 T。

我在 Main 的构造函数中对 addIntegerListener + cmets 进行了多次调用,其中 3 次导致编译器错误。但据我所知(根据 IntelliJ),所有这些都应该是合法的。如果您注释掉对 addIntegerListener 的 3 个第一次调用,则应用程序将编译并运行得很好。

另外,如果 CallbackProducer 不使用泛型,并且我们完全删除了类型参数 T,那么对 addIntegerListener 的 3 次第一次调用将编译。

这种行为有原因吗?我是否误解了什么,或者这是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);
    }
}

【问题讨论】:

    标签: java generics lambda compiler-errors method-reference


    【解决方案1】:

    所有 3 个编译器错误都是由于您使用的是原始 CallbackProducer。当您使用原始CallbackProducer 时,所有类型参数都会进行类型擦除,这样任何T(例如您的T)都将变为Object

    因此,addIntegerListener 方法需要一个原始的 GenericListener 作为参数,而 integerReceived 不再适用。 integerReceived 方法采用 Integer,而不是 Object,因为原始 GenericListener 将提供。

    您必须在 CallbackProducer 上提供尖括号 &lt;&gt; 以避免使用原始类型,就像您在后续示例中所做的那样。

    【讨论】:

    • 感谢您的回复。就我所知,您所说的可能是编译器抱怨的原因,但从未使用过 CallbackProducer 的 。 "addIntegerListener" 已经明确将 GenericListener 作为参数。此外,以下行编译并运行得很好:CallbackProducer&lt;Double&gt; producer2 = new CallbackProducer&lt;&gt;(); producer2.addIntegerListener(this::integerReceived); 现在我说 T 是一个 Double,当我使用整数侦听器时。它仍然有效,因为 T 与侦听器无关。
    • 这与CallProducer 的类型参数是什么无关。这与您使用原始CallProducer这一事实有关。
    • 只是为了确保我理解:您的意思是,如果我使用原始 CallbackProducer(或任何其他接受类型参数的类),该生产者中的所有其他类型参数都会被删除,即使它们与 CallbackProducer 的“T”完全无关? (就像我的情况一样)我不怀疑你是对的,但出于好奇,这种行为是否有原因?我不明白为什么这应该是一个问题。类型保证匹配。您知道这是编译器中可能会在未来解决的弱点,还是设计使然?
    • 是的,所有其他类型的参数都被删除,即使是不相关的参数,例如GenericListener。当 Java 5.0 引入泛型时,它是这样创建的,以提供与前泛型代码的向后兼容性。
    • 那么,最后一个例子运行得很好,是不是有点奇怪? GenericListener&lt;Integer&gt; integerListener = this::integerReceived; new CallbackProducer().addIntegerListener(integerListener); 此外,即使监听器的类型参数指定为 Integer,使用匿名类的旧方法也可以正常工作。
    猜你喜欢
    • 2010-11-19
    • 2011-04-18
    • 1970-01-01
    • 2023-03-29
    • 1970-01-01
    • 1970-01-01
    • 2020-12-16
    • 2015-03-27
    相关资源
    最近更新 更多