操作符“+”的定义,他是否是new了一…

本文通过四个实验探讨了Java中不同场景下字符串拼接的性能表现,包括纯字符串拼接、字符串与常量拼接、字符串与变量拼接等,并分析了内部使用StringBuilder进行优化的原理。

摘录来自优快云论坛:http://bbs.youkuaiyun.com/topics/220015609

为了加深理解,我们可以来做几个小实验。

javac Test         编译文件
javap -c Test   查看虚拟机指令

实验一:纯字符串


Java code:
 public class Test {
    public static void main(String args[]) {
        String str = "a";
    }
}

 

   // 将字符串 a 存入常数池
   0:   ldc     #2; //String a
   // 将引用存放到 1 号局部变量中
   2:   astore_1
   3:   return

实验二:纯字符串相加


Java code:
 public class Test {
    public static void main(String args[]) {
        String str = "a" + "b";
    }
}

 

   // 将字符串 ab 压入常数池
   0:   ldc     #2; //String ab
   2:   astore_1
   3:   return

实验二可以很明显地看出,编译器在编译时产生的字节码已经将 "a" + "b" 优化成了 "ab",
同理多个字符串的相加也会被优化处理,需要注意的是字符串常量相加。

实验三:字符串与自动提升常量相加


Java code:
 public class Test {
    public static void main(String args[]) {
        String str = "a" + (1 + 2);
    }
}

 

   // 将字符串 a3 压入常数池
   0:   ldc     #2; //String a3
   2:   astore_1
   3:   return

通过虚拟机指令可以看出,1 + 2 自动提升后的常量与字符串常量,虚拟机也会对其进行优化。

实验二、实验三结论:常量间的相加并不会引起效率问题

实验四:字符串与变量相加

Java code:
 public class Test {
    public static void main(String args[]) {
        String s = "b";
        String str = "a" + s;
    }
}

 

   // 将字符串 b 压入常数池
   0:   ldc     #2; //String b
   // 将引用存放到 1 号局部变量中
   2:   astore_1
   // 检查到非常量的相加,这时创建 StringBuilder 对象
   3:   new     #3; //class java/lang/StringBuilder
   // 从栈中复制出数据,即把字符串 b 复制出来
   6:   dup
   // 调用 StringBuilder 的初始构造
   7:   invokespecial   #4; //Method java/lang/StringBuilder."":()V
   // 将字符串 a 压入常数池
   10:  ldc     #5; //String a
   // 调用 StringBuilder 的 append 方法,把字符串 a 添加进去
   12:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   // 从 1 号局部变量中加载数据引用
   15:  aload_1
   // 调用 StringBuilder 的 append 方法,把字符串 b 添加进去
   16:  invokevirtual   #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   // 调用 StringBuilder 的 toString 方法
   19:  invokevirtual   #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
   // 将 toString 的结果保存至 2 号局部变量
   22:  astore_2
   23:  return

实验四可以看出,非常量字会串相加时,由于相加的变量中存放的是字符串的地址引用,
因为在编译时无法确切地知道其他具体的值,也就没有办法对其进行优化处理,这时为了
达到连接的效果,其内部采用了 StringBuilder 的机制进行处理(JDK 5 中新增的,我
这里没有 JDK 1.4,估计在 JDK 1.4 下采用的是 StringBuffer),将他们都 append
进去,最后用 toString 输出。

若 s 为其他类型时,比如:int 类型,也是采用同种方式进行处理。

同理,根据实验二的结果,在 String str = "a" + "b" + s; 时,先会优化成 "ab" 再与
s 根据实验四的方式进行处理,这时 StringBuilder 仅调用了两次 append 方法。

如果是 String str = "a" + s + "b"; 这种形式的就没办法优化了,StringBuilder 得调
用三次 append 方法。

实验四的结论表明,字符串与变量相加时在内部产生了 StringBuilder 对象并采取了一定
的操作。

如果只有一句 String str = "a" + s; 这样子的,其效率与
String str = new StringBuilder().append("a").append(s).toString();
是一样的。

一般所说的 String 采用连接运算符(+)效率低下主要产生在以下的情况中:

Java code:
 public class Test {
    public static void main(String args[]) {
        String s = null;
        for(int i = 0; i < 100; i++) {
            s += "a";
        }
    }
}

 

每做一次 + 就产生个 StringBuilder 对象,然后 append 后就扔掉。下次循环再到达时重
新产生个 StringBuilder 对象,然后 append 字符串,如此循环直至结束。

如果我们直接采用 StringBuilder 对象进行 append 的话,我们可以节省 N - 1 次创建和
销毁对象的时间。

在 JavaScript 中,`new` 操作符用于创建个对象实例,并调用构造函数来初始化该对象。为了模拟 `new` 操作符的底层逻辑,我们需要理解其核心行为:创建新对象、绑定作用域、执行构造函数、返回对象。 以下是个实现 `new` 操作符的自定义函数: ```javascript function myNew(constructor, ...args) { // 创建个空对象 const obj = Object.create(null); // 将构造函数的 prototype 赋给新对象的 __proto__ obj.__proto__ = constructor.prototype; // 绑定 this 到新对象并执行构造函数 const result = constructor.apply(obj, args); // 如果构造函数返回的是对象,则返回该对象;否则返回新创建的对象 return typeof result === 'object' && result !== null ? result : obj; } ``` ### 实现原理详解 1. **创建个新对象** 使用 `Object.create(null)` 创建个没有原型链的新对象。也可以使用 `{}`,但为了更清晰地展示原型继承关系,可以使用 `Object.create()` 明确设置原型[^3]。 2. **绑定构造函数的作用域** 使用 `apply` 或 `call` 方法将构造函数的 `this` 指向新创建的对象。这步确保构造函数内部对 `this` 的操作会应用到新对象上[^2]。 3. **执行构造函数中的代码** 在绑定好 `this` 后,构造函数会被调用,从而为新对象添加属性和方法。例如,如果构造函数中有 `this.name = name`,这些属性就会被附加到新对象上[^3]。 4. **返回新对象或构造函数返回的对象** 如果构造函数返回了个对象(非原始值),则返回这个对象;否则返回最初创建的对象。这是 `new` 行为的部分,通常用于增强对象的功能或替代默认的实例化结果[^3]。 --- ### 示例 定义个简单的构造函数并使用自定义的 `myNew` 来创建实例: ```javascript function Person(name, age) { this.name = name; this.age = age; // 返回个对象,覆盖默认的 this return { nickname: 'override' }; } const person1 = myNew(Person, 'Alice', 30); console.log(person1); // 输出: { nickname: 'override' } const person2 = new Person('Bob', 25); console.log(person2); // 输出: { nickname: 'override' } ``` 在这个示例中,尽管 `Person` 构造函数返回了个新的对象 `{ nickname: 'override' }`,但通过 `myNew` 和原生 `new` 都能正确处理这情况,返回构造函数显式指定的对象。 --- ### 常见注意事项 - **构造函数必须是函数** 确保传入的 `constructor` 是个函数,否则应抛出错误。 - **原型链的维护** 新创建的对象应该继承构造函数的原型,因此需要手动设置 `__proto__` 或使用 `Object.create()` 来构建正确的原型链[^4]。 - **处理构造函数返回值** 如果构造函数返回的是个对象,则最终结果应该是该对象而不是默认创建的对象。这点与原生 `new` 的行为致[^3]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值