我对以下风格的代码进行了评论:
Iterable<String> upperCaseNames = Iterables.transform(
lowerCaseNames, new Function<String, String>() {
public String apply(String input) {
return input.toUpperCase();
}
});
这个人说每次我通过这段代码,我都会实例化这个匿名的Function类,而我宁愿在一个静态变量中有一个实例:
static Function<String, String> toUpperCaseFn =
new Function<String, String>() {
public String apply(String input) {
return input.toUpperCase();
}
};
...
Iterable<String> upperCaseNames =
Iterables.transform(lowerCaseNames, toUpperCaseFn);
在一个非常肤浅的层面上,这在某种程度上是有道理的;多次实例化一个类必须浪费内存或其他东西,对吗?
另一方面,人们在代码中间实例化匿名类,就像没有明天一样,编译器优化它是微不足道的。
这是一个有效的问题吗?
答案 0 :(得分:7)
有关Sun / Oracle JVM优化的有趣事实,如果您实例化一个未在线程外部传递的对象,JVM将在堆栈而不是堆上创建对象。
通常,堆栈分配与暴露内存模型的语言相关联,如C ++。您不必在C ++中使用delete
堆栈变量,因为在退出作用域时它们会自动释放。这与堆分配相反,堆分配要求您在完成后删除指针。
在Sun / Oracle JVM中,分析字节码以确定对象是否可以“逃避”该线程。有three levels of escape:
这基本上类似于问题,1)我传递/返回它,2)我是否将它与附加到GC根的内容相关联?在您的特定情况下,匿名对象将被标记为“没有本地转义”并将被分配到堆栈,然后通过在{{的每次迭代时弹出堆栈来清除1}}循环,所以清理它会非常快。抱歉,当我写完答案时,我并没有太多关注。它实际上是本地转义,这意味着对象的任何锁定(读取:使用for
)都将被优化掉。 (为什么要同步一些不会在另一个线程中使用的东西?)这与“无法逃避”不同,将在堆栈上进行分配。重要的是要注意这个“分配”与堆分配不同。它真正做的是在堆栈上为非转义对象内的所有变量分配空间。如果在no-escape对象中有3个字段synchronized
,int
和String
,则将分配三个堆栈变量:MyObject
,一个{{1引用和int
引用。然后优化对象分配,并使用本地堆栈变量而不是堆变量来运行构造函数/方法。
话虽如此,这对我来说听起来不成熟。除非后来证明代码很慢并且导致性能问题,否则您不应该做任何事情来降低其可读性。对我来说,这段代码非常易读,我会不管它。当然,这是完全主观的,但“性能”不是改变代码的好理由,除非它与算法运行时间有关。通常,过早优化是中级编码器的指标。 “专家”(如果有这样的事情)只是编写更容易维护的代码。
答案 1 :(得分:1)
简答:不 - 别担心。
答案很长:这取决于你实例化它的频率。如果在频繁调用的紧密循环中,可能 - 尽管注意到在应用函数时它会为String.toUpperCase()
中的每个项调用Iterable
一次 - 每个调用可能会创建一个新的String
,这将产生更多的GC流失。
“过早优化是所有邪恶的根源” - Knuth
答案 2 :(得分:1)
找到这个帖子:Java anonymous class efficiency implications,你可能会发现它很有趣
做了一些微观基准测试。微基准测试是:每个循环迭代实例化一个(静态内部)类,一次实例化(静态内部)类并在循环中使用它,以及两个类似但具有匿名类的类。对于微基准测试,编译器似乎从循环中提取匿名类,并且如预测的那样,将匿名类提升为调用者的内部类。这意味着所有四种方法在速度上都难以区分。我也将它与外部课程进行了比较,并将其与同样的速度相提并论具有匿名类的那个可能需要大约128位的空间
您可以在http://jdmaguire.ca/Code/Comparing.java&amp ;;查看我的微观基准。 http://jdmaguire.ca/Code/OutsideComp.java。我在wordLen,sortTimes和listLen的各种值上运行它。同样,JVM的预热很慢,所以我改变了方法调用。请不要因为糟糕的非评论代码来判断我。我编程比RL更好。而微小的标记几乎和过早优化一样邪恶无用。