反复实例化一个匿名类是浪费吗?

我对以下风格的代码进行了评论:

Iterable upperCaseNames = Iterables.transform( lowerCaseNames, new Function() { public String apply(String input) { return input.toUpperCase(); } }); 

该人说,每次我通过这段代码,我都会实例化这个匿名的Function类,而我宁愿在一个静态变量中有一个实例:

 static Function toUpperCaseFn = new Function() { public String apply(String input) { return input.toUpperCase(); } }; ... Iterable upperCaseNames = Iterables.transform(lowerCaseNames, toUpperCaseFn); 

在一个非常肤浅的层面上,这在某种程度上是有道理的; 多次实例化一个类必须浪费内存或其他东西,对吧?

另一方面,人们在代码中间实例化匿名类,就像没有明天一样,编译器优化它将是微不足道的。

这是一个有效的问题吗?

有关Sun / Oracle JVM优化的有趣事实,如果您实例化一个未在线程外部传递的对象,JVM将在堆栈而不是堆上创建对象。

通常,堆栈分配与暴露内存模型的语言相关联,如C ++。 您不必在C ++中delete堆栈变量,因为在退出作用域时它们会自动释放。 这与堆分配相反,堆分配要求您在完成后删除指针。

在Sun / Oracle JVM中,分析字节码以确定对象是否可以“逃避”该线程。 有三个级别的逃脱 :

  1. 无转义 – 该对象仅在其创建的方法/范围内使用,并且无法在该线程外部访问该对象。
  2. Local / Arg转义 – 该对象由创建它的方法返回或传递给它调用的方法,但当前堆栈跟踪中的任何方法都不会将该对象放在可以在线程外部访问的位置。
  3. 全局转义 – 将对象放在可以在另一个线程中访问的位置。

这基本上类似于问题,1)我传递/返回它,2)我是否将它与GC根目录相关联? 在您的特定情况下,匿名对象将被标记为“ 本地转义” 并将被分配到堆栈,然后通过简单地在for循环的每次迭代中弹出堆栈来清理,因此清理它将是超快的。 对不起,当我写完答案时,我并没有太多关注。 它实际上是本地转义,这意味着对象上的任何锁(读取:使用synchronized )都将被优化掉。 (为什么要同步一些不会在另一个线程中使用过的东西?)这与“no escape”不同,后者在堆栈上进行分配。 值得注意的是,这种“分配”与堆分配不同。 它真正做的是为堆栈中的非转义对象内的所有变量分配空间。 如果在no-escape对象中有3个字段intStringMyObject ,则将分配三个堆栈变量: intString引用和MyObject引用。 然后优化对象分配,并使用本地堆栈变量而不是堆变量运行构造函数/方法。

话虽如此,这对我来说听起来像是过早的优化。 除非后来certificate代码很慢并且导致性能问题,否则您不应该做任何事情来降低其可读性。 对我来说,这段代码非常易读,我会不管它。 当然,这是完全主观的,但“性能”并不是改变代码的好理由,除非它与算法运行时间有关。 通常,过早优化是中级编码器的指标。 “专家”(如果有这样的事情)只是编写更容易维护的代码。

简答:不 – 别担心。

答案很长:这取决于你实例化它的频率。 如果在一个经常被称为紧密循环的情况下,可能 – 虽然注意到当应用该函数时,它会为Iterable每个项调用一次String.toUpperCase() – 每个调用可能会创建一个新的String ,这将创建更多的GC流失。

“过早的优化是所有邪恶的根源” – 克努特

发现这个post: Java匿名类效率的含义 ,你可能会发现它很有趣

做了一些微观基准测试。 微基准测试是:每个循环迭代实例化一个(静态内部)类,一次实例化一个(静态内部)类并在循环中使用它,以及两个类似的但具有匿名类的比较。 对于微基准测试,编译器似乎从循环中提取匿名类,并且如预测的那样,将匿名类提升为调用者的内部类。 这意味着所有四种方法在速度上都难以区分。 我也把它与外面的课程进行了比较,并且速度相同。 具有匿名类的那个可能需要大约128位的空间

您可以在http://jdmaguire.ca/Code/Comparing.java和http://jdmaguire.ca/Code/OutsideComp.java上查看我的微基准测试。 我在wordLen,sortTimes和listLen的各种值上运行它。 同样,JVM的预热很慢,所以我改变了方法调用。 请不要因为糟糕的非注释代码来判断我。 我编程比RL更好。 而微小的标记几乎和过早优化一样邪恶无用。