Java 字符串链接运算符干了什么?

和其余多数程序设计语言同样,Java 语言容许使用 + 链接两个字符串。html

String name = "stephen";
String foo = "Hey, " + name;

当咱们将一个字符串和一个非字符串的值进行拼接时,并不会报错:java

String name = "Stephen";
int age = 25;
String foo = name + age; // 结果为 Stephen25

其缘由是当 + 运算符左右两边有一个值是字符串时,会将另外一个值尝试转化为字符串。oracle

字符串转换机制

咱们在了解字符串链接运算符前,先了解一下字符串转换机制(String Conversion)。app

Any type may be converted to type String by string conversion.

若是值 x 是基本数据类型 T,那么在字符串转换前,首先会将其转换成一个引用值,举几个例子:ide

• 若是 T 是 boolean 类型的,那么就会用 new Boolean(x) 封装一下;工具

• 若是 T 是 char 类型的,那么就会用 new Character(x) 封装一下;性能

• 若是 T 是 byte、short、int 类型的,那么就会用 new Integer(x) 封装一下;ui

咱们知道,对于基本数据类型,Java 都对应有一个包装类(好比 int 类型对应有 Integer 对象),这样操做之后,每一个基础数据类型的值 x 都变成了一个对象的引用。设计

为何这么作?为了统一对待,当咱们把基础数据类型转换成对应的包装类的一个实例后,全部的值都是统一的对象引用。code

此时才开始真正进行字符串转换。咱们须要考虑两种状况:空值和非空值。

若是此时的值 x 是 null,那么最终的字符串转换结果就是一个字符串 null

不然就会调用这个对象的 toString() 的无参方法

前者很好理解,后者咱们一块儿来看看:

在 Java 全部的父类 Object 中,有一个重要的方法就是 toString 方法,它返回表示对象值的一个字符串。在 Object 类中对 toString 的定义以下:

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

该方法返回对象的类名和散列码。若是类没有重写 toString 方法,默认就会调用它的父类的 toString 方法,而此时咱们的值 x 统一都是对象值,因此必定有 toString 方法能够调用并打印出值(也有个特殊,若是调用 toString 返回的值是一个 null 值,那么就会用字符串 null 代替)。

字符串链接符

+ 运算符左右两边参与运算的表达式的值有一个为字符串时,那么在程序运行时会对另外一个值进行字符串转换

这里须要注意的是 + 运算符同时做为算术运算符,在含有多个值参与运算的时候,要留意优先级,好比下面这个例子:

String a = 1 + 2 + " equals 3";
String b = "12 eqauls " + 1 + 2;

变量 a 的结果是 3 equals 3,变量 b 的结果是 12 equals 12

有些人这里可能会有疑问,解释一下,第一种状况根据运算优先级是先计算 1+2 那么此时的 + 运算符是算术运算符,因此结果是 3,而后再和 " equals 3" 运算,又由于 3 + " equals 3" 有一个值为字符串,因此 + 运算符是字符串链接运算符。

在运行时,Java 编译器通常会使用相似 StringBuffer/StringBuilder 这样带缓冲区的方式来减小经过执行表达式时建立的中间 String 对象的数量,从而提升程序性能。

咱们能够用 Java 自带的反汇编工具 javap 简单的看一下:

假设有以下这段代码:

public class Demo {
    public static void main(String[] args) {
        int i = 10;
        String words = "stephen" + i;
    }
}

而后编译,再反汇编一下:

javac Demo.java
javap -c Demo

能够获得以下内容:

Compiled from "Demo.java"
public class Demo {
  public Demo();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: bipush        10
       2: istore_1
       3: new           #2                  // class java/lang/StringBuilder
       6: dup
       7: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
      10: ldc           #4                  // String stephen
      12: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      15: iload_1
      16: invokevirtual #6                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      22: astore_2
      23: return
}

咱们能够发现,Java 编译器在执行字符串链接运算符所在表达式的时候,会先建立一个 StringBuilder 对象,而后将运算符左边的字符串 stephen 拼接(append)上去,接着在拼接右边的整型 10,而后调用 StringBuilder 的 toString 方法返回结果。

若是咱们拼接的是一个对象呢?

public class Demo {
    public static void main(String[] args) {
        Demo obj = new Demo();
        String words = obj + "stephen";
    }

    @Override
    public String toString() {
        return "App{}";
    }
}

同样的作法,咱们会发现此时 Method java/lang/StringBuilder.append:(Ljava/lang/Object;) 也就是 StringBuilder 调用的是 append(Object obj) 这个方法,咱们查看 StringBuilder 类的 append 方法:

public StringBuilder append(Object obj) {
    return append(String.valueOf(obj));
}

String.valueOf(obj) 的实现代码以下:

public static String valueOf(Object obj) {
    return (obj == null) ? "null" : obj.toString();
}

也就是会调用对象的 toString() 方法。

可能到这里你们会有一个疑问:上面不是说字符串转换对于基本类型是先转换成对应的包装类,而后调用它的 toString 方法吗,这边怎么都是调用 StringBuilder 的 append 方法了呢?

实现方式不一样,实际上是本质上是同样的,只不过为了提升性能(减小建立中间字符串等的损耗),Java 编译器采用 StringBuilder 来作。感兴趣的能够本身去追踪下 Integer 包装类的 toString 方法,其实和 StringBuilder 的 append(int i) 方法的代码是几乎同样的。

参考连接

String Concatenation Operator +

String Conversion

相关文章
相关标签/搜索