深入了解Java中的String对象

在平时的代码开发过程中必不可少的需要使用字符串,那必然是要用到String类型,一般来说创建String对象常用就这几种方式:

String s1 = "china";
String s2 = new String("hello");
String s3 = "ch" + "i" + "na";
String s4 = "love " + "china";
String s5 = "love china";
String s6 = "love " + s1;

它们之间有什么区别,你知道吗?如果不清楚,那现在我们一条一条的来解读。


String s1 = "china";

这条代码在编译成 .class 文件时,将会把  china 字符串直接编译进常量表中,这个可以通过 javap 来反编译 .class 文件来查看到。QQ截图20160409003142

这样当加载 .class 文件时,就已经生成了该String对象。以后要是再直接使用 china 的String 对象时,就会直接从常量表中拿到对应 china 字符串的 String 对象。

如果定义了 String t1 = “china”; 再进行  t1 == s1 比较时,返回的结果为 true ,表示为相同的内存地址引用。


String s2 = new String("hello");

这条语句,一样是从常量表中拿到 hello的String对象,再做为String构造方法的参数,再创建一个新的String对象。

看到没,虽然这里是用 hello 字符来创建一个新的 String 对象,但你要知道实际上在常量表中已经存在为 hello 字符的String对象。

说到这里顺便来看一下String这个类。

public final class String {

    private final char value[];

    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }
}

内部有一个只能初始化一次的 char 类型的 value[] ,这也就很明显示表示String对象的内容是初始化后不可变的。

这时定义一个 String t2 = “hello”; 再进行 t2 == s2 内存地址比较时,自然结果为 false 。

使用 t2.equals(s2) 比较时结果为 true,那是因为使用 equals 比较时,实际是对内部的 char 字符集合逐一比较,通过观察 String.equals 源码,便很清楚的了解比较过程。


String s4 = "love " + "china";
String s5 = "love china";

要了解这两行代码,需要比较着来看。String s4 = “love ” + “china”; 这行代码实际会被编译器优化为 String s4 = “love china”; 如果进行地址比较  s4 == s5 返回的结果必然是 true  的。

dfadsafsa

同样通过反编译就可以很楚清的知道,倒底发生了什么事情。这两个变量均引用了 #16 的那个String实例。


String s1 = "china";
String s5 = "love china";
String s6 = "love " + s1;

那么 s6 呢?如果拿 s6 == s5 进行比较,那结果会是 true 还是 false ?要是不清楚,那么继续来看一下反编译后的代码吧。

QQ截图20160409130236

要是看不太懂的话,那就看下面还原成 java 源码的代码段吧。

String s1 = "china";
String s5 = "love china";
String s6 = (new StringBuilder("love ")).append(s1).toString();

可以很清楚的看到编译器对于String字符集的添加其实会转换为 StringBuilder 进行连接。那它也自然会产生新的String对象。

那既然java编译器会使用 StringBuilder 进行字符连接,那是不是就不需要手工创建StringBuilder 而直接使用 + 来完成字符串的连接呢?

要想知道这个答案我们还需要看一段代码来了解java字符连接的规则。

String s1 = "love ";
for (int i = 0; i < 10; i++) {
  s1 = s1 + "china,";
}
//---- 以下是 java 编译后的代码-----
String s1 = "love ";
for(int i = 0; i < 10; i++)
  s1 = (new StringBuilder(String.valueOf(s1))).append("china,").toString();

这下就该清楚了,java 会把每一行都创建一个StringBuilder实例,这样当有N多行字符相加时,对于性能损耗还是很大的。所以对于大量字符相加还是需要手工创建StringBuilder 进行添加,以获取最优的性能。