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

use*_*413 10 java

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

Iterable<String> upperCaseNames = Iterables.transform(
    lowerCaseNames, new Function<String, String>() {
        public String apply(String input) {
            return input.toUpperCase();
        }
    });
Run Code Online (Sandbox Code Playgroud)

该人说,每次我通过这段代码,我都会实例化这个匿名的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);
Run Code Online (Sandbox Code Playgroud)

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

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

这是一个有效的问题吗?

Bri*_*ian 7

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

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

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

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

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

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

  • 实际上,在内联接收该函数的代码之后,函数对象很可能变成"No Escape"状态(取决于该方法的作用).但这并不意味着会有堆栈分配 - 因为对象没有状态,根本就没有分配.相反,函数的逻辑将被内联到调用方法的代码中(在此上下文中).仍然,当用lambda表达式替换它时,即使对象逃脱,你也会得到一个单例.所以Java 8让我们无需考虑这些事情. (2认同)