在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
}
}
答案 0 :(得分:16)
对于实例方法,我不认为对它们进行缓存是有意义的。你必须为每个实例缓存一个方法...这或者意味着与该方法相关联的类中的额外字段 - 每个公共方法一个,大概是因为这些方法可以从类外部引用 - 或者缓存在方法引用的 user ,在这种情况下,您需要某种每实例缓存。
我认为对缓存 static 方法的方法引用更有意义,因为它们将永远是相同的。但是,要缓存实际的Runnable
,您需要为其定位的每种类型的缓存。例如:
public interface NotRunnable {
void foo();
}
Runnable thing1 = Main::doStuff; // After making doStuff static
NotRunnable thing2 = Main::doStuff;
这里thing1
和thing2
应该相等吗?如果是这样,编译器将如何知道要创建的类型?它可以在这里创建两个实例并单独缓存它们 - 并始终在使用点缓存而不是方法声明点。 (您的示例具有相同的类,声明方法并引用它,这是一个非常特殊的情况。您应该考虑更一般的情况,它们是不同的>)
JLS 允许缓存方法引用。来自section 15.13.3:
接下来,分配并初始化具有以下属性的类的新实例,或者引用具有以下属性的类的现有实例。
...但即使对于静态方法,似乎javac
目前也没有进行任何缓存。
答案 1 :(得分:4)
在这个简单的例子中,您可以按照您的建议通过适当创建额外的静态或实例字段来做,如果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,这是故意避免使用非预期的对象。
答案 2 :(得分:2)
制作方法单例将需要同步获取对它们的引用。这给这种简单的操作带来了很大的开销,并且结果不可预测。另一种解决方案是在类加载时为每个方法创建对象,但这会产生许多冗余对象,因为只有很少的方法需要引用。我认为同步是主要问题。