我对一段代码的评论风格如下:
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);
从表面上看,这是有道理的。多次实例化一个类必须浪费内存,对吗?
另一方面,人们在代码中间实例化匿名类,就像没有明天一样,对于编译器来说,优化它是微不足道的。
这是一个有效的问题吗?
最佳答案
关于热点JVM优化的有趣事实,如果实例化未传递到当前方法之外的对象,则JVM将在字节码级别执行优化。
通常,堆栈分配与公开内存模型的语言(例如C++)相关联。您无需在C++中使用delete
堆栈变量,因为退出范围时它们会自动释放。这与堆分配相反,堆分配要求您在使用完指针后将其删除。
在Hot JVM中,将分析字节码以确定对象是否可以“转义”线程。有three levels of escape:
这基本上类似于问题:1)是否将其传递给另一个方法或将其返回,以及2)是否将其与附加到GC根目录的内容(如
ClassLoader
或存储在static
字段中的内容)相关联?在您的特定情况下,匿名对象将被标记为“本地转义”,这仅意味着该对象上的所有锁(读取:使用
synchronized
)都将被优化。 (为什么要在另一个线程中永远不会使用的东西上进行同步?)这与“无转义”不同,后者将在堆栈上进行分配。重要的是要注意,此“分配”与堆分配不同。它真正要做的是为非转义对象内的所有变量在堆栈上分配空间。如果在no-escape对象内有3个字段int
,String
和MyObject
,则将分配三个堆栈变量:int
,String
引用和MyObject
引用-MyObject
实例本身仍将存储在堆中,除非它也被分析为“无法逃脱”。然后优化对象分配,并且构造函数/方法将使用本地堆栈变量而不是堆变量运行。话虽这么说,对我来说听起来像是过早的优化。除非后来证明该代码很慢并且会导致性能问题,否则您不应该采取任何措施来降低其可读性。对我来说,这段代码可读性很强,我将不理会它。当然,这完全是主观的,但是“性能”不是更改代码的充分理由,除非它与实际运行时间有关。通常,过早的优化会导致难以维护的代码具有最小的性能优势。
Java 8+和Lambdas
如果分配匿名实例仍然困扰您,建议您将Lambdas转换为单一抽象方法(SAM)类型使用。 Lambda评估是使用
invokedynamic
执行的,该实现最终在第一次调用时仅创建Lambda的单个实例。可以找到更多详细信息in my answer here和this answer here。对于非SAM类型,您仍将需要分配一个匿名实例。在大多数用例中,这里的性能影响可以忽略不计,但是IMO,这种方式更具可读性。引用