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

我的示例包括...

  • 功能接口(interface)GenericListener,它具有类型参数V,并且具有单个方法“genericCallback(VgenericValue)”。
  • 一个名为CallbackProducer的类,它使用类型参数T。该类还具有添加类型为Integer的GenericListener的方法。
  • 类,它创建CallbackProducers并将GenericListeners添加到它们。

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

    方法addIntegerListener仅使用GenericListener的V。据我所知,它不以任何方式使用CallbackProducer的T。

    我在Main的构造函数中对addIntegerListener +注释进行了多次调用,其中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);
        }
    }
    

    最佳答案

    所有3个编译器错误均归因于您使用的是原始CallbackProducer。当您使用原始的CallbackProducer时,所有类型参数都会进行类型擦除,以使任何T(例如您的Object)(没有任何上限)都变为addIntegerListener

    因此,GenericListener方法期望将原始的integerReceived作为参数,但integerReceived不再适合。 Integer方法采用Object而不是GenericListener,就像原始<>会提供的那样。

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

    08-07 06:28