相信大家看了它,以后对Java字符串问题总是信心百倍
总结了下关于那些总是说不尽的String问题,目的是让大家彻底告别String的噩梦,彻底理解String的本质。package stringtest;
public class StringDeep {
public static void main(String[] args) {
/**
* 测试一
*
* */
//1.下面三行代码的运行分别创建了几个对象?
String s1 = new String("abc"); //(1)
String s2 = "abc";//(2)
String s3 = new String("abc");//(3)
//2.你认为下面的输出结果应该是什么?
System.out.println(s1 == s2);//(4)
System.out.println(s1 == s3);//(5)
System.out.println(s3 == s2);//(6)
/**
* 测试二
*
* */
String s4 = "hello";
String s5 = "he";
String s6 = "llo";
//3.你认为下面的输出结果应该是什么?
System.out.println(s4 == "he" + "llo");//(7)
System.out.println(s4 == "he" + s6);//(8)
System.out.println(s4 == s5 + "llo");//(9)
System.out.println(s4 == s5 + s6);//(10)
/**
* 测试三
*
* */
//4.你认为下面的输出结果应该是什么?
System.out.println(s4 == (s5 + s6).intern());//(11)
}
}
上面我写了一小段代码,分成三个部分,其中夹杂了四个问题,如果你可以轻松回答,我想你对String的理解应该是到位的,不能的话,请耐心一起深入学习下String。为了方便,我给上面每一行程序都打上了粉色的行尾标记。
下面我们先回答第一个问题:行(1)生成了两个对象;行(2)生成了0个对象;(3)行生成了一个对象。其对象的内容都是abc。估计很多人已经想反驳这个结论了,那么先看看下面我给出的证据:
1) API doc关于String的构造方法之一,String(String original)的说明,如下: 初始化一个新创建的 String 对象,表示一个与该参数相同的字符序列;换句话说,新创建的字符串是该参数字符串的一个副本。由于 String 是不可变的,不必使用该构造方法,除非需要 original 的显式副本。大家仔细读一下API的说明就可以清晰的窥见,我用粉色标注的那句,虽然是一个副本,但是的确新创建了一个String对象。
2) String池的概念:一个初始时为空的字符串池,它由类 String 私有地维护。这句话在intern方法的说明中,这个方法随后说明。可以看出除了在堆中创建一个字符串对象外,还会在字符串池中创建一个字符串对象,它是一个直接量。可以看看下图画出了(1)(2)(3)执行完后的内存结构示意图:
如上图, 图片在附件中
当(1)执行时候,String类先会查找字符串池,是否有abc,发现没有,那么它创建abc,并放入字符串池;并且new关键字的使用,虚拟机又在堆中生成了一个String对象,内容为abc,这样就创建了两个对象,至于引用是s1,是存放在栈中的临时变量,并不是对象。
当(2)执行时,虚拟机只是在String池中生成一个String对象,它的过程是:先查找字符串池中是否有abc,发现已经有了,所以它直接把这个地址返回给引用s2.注意这里由于(1)的执行,在字符串池中已经有abc了,所以生成0个对象。
当执行(3)时,首先检查字符串池,发现已经有abc,不用再创建,只是在堆中生成一个新的String对象,所以生成1个对象。
接着回答第二个问题,第一个问题清楚了,这个问题就很好回答,用==比较的是引用,也就是内存地址,那么从上面的分析,从内存结构示意图清晰的看出,三个引用分别指向不同的对象,即有不同的值,所以三个输出都应该是false。
第三个问题中有一个java的约定:
当使用+这个重载的运算符时候,如果两边有任意一个不是直接量,而是一个引用量,那么虚拟机将新生成一个String对象,并把这个对象作为运算结果。
当两边都是直接量时候,两者就返回字符串池中的同内容的String对象的引用,作为计算结果。
根据这个约定很显然只有(7)为true,(8)(9)(10)均为false。
最后一个问题,这里牵扯到intern这个方法,看看API的说明:返回字符串对象的规范化表示形式。 一个初始时为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并且返回此 String 对象的引用。 它遵循对于任何两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。
看看上面的说名,很清晰,通俗点讲:池中如果有和这个String对象内容相同的字符串时候,就返回这个字符串的引用。否则不存在,那么就将此String对象放入池中,并返回该引用。
很显然,在(7)执行完后,字符串池中就有了hello了,而s5+s6的内容就是hello,所以调用intern方法,就是返回和s4值相同的引用了,所以应为true。
总结:
1. 使用String(String original)这个构造函数创建字符串对象实际上生成了两个字符创对象,一个在字符串池中(这里假设池中还没有此字符串),一个在堆中,返回的引用指向堆中那个对象。
2. 使用直接量创建字符串,只会生成一个对象,在字符串池中(这里假设池中还没有此字符串)。如 String str = “just one“;
3. intern方法返回的是与其内容相同的在字符串池中的那个String对象的引用。