为什么方法引用不是单例?

2022-09-02 01:41:06

在 Java 中,以下代码在两个查询上都返回 false。为什么?方法引用是单例不是更简单吗?这肯定会使附加和分离侦听器变得更加简单。由于您需要为需要检查等价性的任何方法引用保持常量,因此不能在每个必要的位置仅使用方法引用运算符。

public class Main {

    public Main() {
        // TODO Auto-generated constructor stub
    }

    public void doStuff() {

    }

    public static void main(String[] args) {
        Main main = new Main();
        Runnable thing1 = main::doStuff;
        Runnable thing2 = main::doStuff;
        System.out.println(thing1 == thing2); // false
        System.out.println(thing1.equals(thing2)); // false
    }

}

答案 1

例如,我认为缓存它们没有意义。您必须为每个实例缓存一个方法...这要么意味着与方法关联的类中有一个额外的字段 - 每个公共方法一个,大概是因为可以从类外部引用方法 - 或者缓存在方法引用的用户中,在这种情况下,您需要某种每个实例的缓存。

我认为缓存对静态方法的方法引用更有意义,因为它们将永远保持不变。但是,要缓存实际的 ,您需要针对它所针对的每个类型进行缓存。例如:Runnable

public interface NotRunnable {
    void foo();
}

Runnable thing1 = Main::doStuff; // After making doStuff static
NotRunnable thing2 = Main::doStuff;

这里应该平等吗?如果是这样,编译器如何知道要创建什么类型?它可以在此处创建两个实例并分别缓存它们 - 并且始终在使用点而不是方法声明点进行缓存。(您的示例具有声明该方法并引用该方法的同一类,这是一个非常特殊的情况。您应该考虑它们不同的更一般的情况>)thing1thing2

JLS 允许缓存方法引用。从第15.13.3节开始:

接下来,分配并初始化具有以下属性的类的新实例,或者引用具有以下属性的类的现有实例。

...但即使对于静态方法,目前似乎也不进行任何缓存。javac


答案 2

在这个简单的例子中,你可以按照你的建议,根据需要创建额外的静态或实例字段,如果lambda引用对象,则更复杂,但是目的是将这些实例内联出来,例如。

List<String> list = ....
list.stream().filter(s -> !s.isEmpty()).forEach(System.out::println);

应该同样高效(即使创建的对象不超过)

for (String s : list)
    if(!s.isEmpty())
         System.out.println(s);

它可以通过内联流代码并使用转义分析来消除首先创建对象的需要,从而消除对象。

出于这个原因,很少有人关注实现 equals()、hashCode() 或 toString(),通过反射来访问它们以进行闭包。AFAIK,这是故意避免以非预期的方式使用对象。


推荐