考虑这种方法:
private void iterate(List<Worker> workers) {
SortedSet<Worker> set = new TreeSet<>(new Comparator<Worker>() {
@Override
public int compare(Worker w0, Worker w1) {
return Double.compare(w0.average, w1.average);
}
});
// ...
}
正如您所看到的,该集正在创建一个带有自定义比较器的新TreeSet
。
我想知道它是否与性能/内存/垃圾收集/任何观点有任何区别,如果我这样做而是污染了外层空间:
static final Comparator<Worker> COMPARATOR = new Comparator<Worker>() {
@Override
public int compare(Worker w0, Worker w1) {
return Double.compare(w0.average, w1.average);
}
};
private void iterate(List<Worker> workers) {
SortedSet<Worker> set = new TreeSet<>(COMPARATOR);
// ...
}
我问的原因是,我觉得编译器应该已经解决了这个并为我优化它,所以我不应该提取它,对吧?
对于在方法中声明的字符串或其他临时的,不可变的对象,情况也是如此。
将final
变量提取出来会有什么不同吗?
注意:我知道这可能给性能提升带来的影响很小。问题是是否存在任何差异,无论如何都可以忽略不计。
答案 0 :(得分:2)
肯定会有所不同。
Hotspot非常擅长内联,但不太可能认识到比较器可以在堆上分配或移动到常量。但这取决于TreeSet的内容。如果TreeSet的实现非常简单(和小),那么它可以内联,但是我们都知道它不是。 TreeSet也被编码为通用的,如果它只用于一种类型的对象(Worker),那么JVM可以应用一些优化但是我们应该假设TreeSet也会被其他类型使用,所以TreeSet不会能够对正在过去的比较器做出任何假设。
因此,两个版本之间的差异主要是对象分配。使用final关键字不太可能提高性能,因为Hotspot无论如何都会忽略最终关键字。
使用lambdas时,Java 8在这里有一个非常有趣的行为。请考虑以下示例的变体:
import java.util.*;
public class T {
public void iterate(List<String> workers) {
SortedSet<Double> set = new TreeSet<>( Double::compare );
}
}
运行&#39; javap -c T.class&#39;,您将看到以下jvm代码:
public void iterate(java.util.List<java.lang.String>);
Code:
0: new #2 // class java/util/TreeSet
3: dup
4: invokedynamic #3, 0 // InvokeDynamic #0:compare:()Ljava/util/Comparator;
9: invokespecial #4 // Method java/util/TreeSet."<init>":(Ljava/util/Comparator;)V
12: astore_2
13: return
这里要注意的很酷的事情是lambda没有对象构造。 invokedynamic在第一次调用它时会有更高的成本,然后它会被有效地缓存。
答案 1 :(得分:1)
一个很大的区别是由于“隐藏”暗示匿名类持有对包含类的隐式引用,因此如果将该TreeSet传递给另一个进程,则通过TreeSet通过匿名方式保存对类实例的引用比较器由另一段代码组成,因此您的实例不会被垃圾收集。
这可能会导致内存泄漏。
然而,选项2并没有遇到这个问题。
否则,这是一种风格问题。
在java 8中,您可以使用lambda表达式,这是两个世界中最好的。