Java中字符串对象是一种特殊的对象。String类是一个不可变的(final)类,也就说,String对象一旦创建就不允许修改。String类有一个对应的常量池(String pool),每一个内容相同的字符串在常量池里都会有一个对象与之对应。
首先,让我们来看一下下边这两种定义String的区别。
String str1 = "abc";
String str2 = new String("abcd");
那么,它们两个在内存中是怎么分配的呢。
str1和str2是两个对象的引用,存放在Java栈中。第一条语句没有在堆中分配内存,而是将“abc”保存在常量池中。对于第二条语句,同样会在常量池中保存一个“abcd”的字符串,当new时,会拷贝一份该字符串存放到堆中,于是str2指向了堆中的那个“abcd”字符串。同理,如果我们再定义一句String str3 = “abc”;那么,栈中的引用str3会指向跟str1相同的那个常量池中的”abc”。
接下来,让我们通过几段代码来分析一下String。
1、
public static void main(String[] args) {
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2); // true
}
分析: 这里结果为true。当程序加载String str1 = “abc”; 这句代码时,会去常量池中检查有没有”abc”这个对象,如果没有,则在常量池中创建一个”abc”对象。同样,当加载String str2 = “abc”; 这句代码时,因为常量池中已经存在了”abc”这个对象,则str2直接指向常量池中的”abc”对象,它们两个指向的是同一个常量池中的对象,所以输出为true。
2、
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = new String("abc");
System.out.println(str1 == str2); // false
System.out.println(str2 == str3); // false
}
分析: 当程序加载String str1 = “abc”;这句的时候,在常量池里会创建”abc”这个对象,str1指向常量池中的”abc”对象。当程序加载String str2 = new String(“abc”);这句的时候,因为常量池里已经有了”abc”这个对象,所以在常量池中不新建对象,但是在java堆中会新建一个”abc”对象,str2指向Java堆中新建的”abc”对象。当程序加载String str3 = new String(“abc”);这句的时候,同样因为常量池中已经有”abc”这个对象,所以不在常量池中新建”abc”对象,但是也会在Java堆中新建一个”abc”对象,str3指向Java堆中新建的这个”abc”对象,注意,这个”abc”对象跟str2指向的”abc”对象不是同一个,str1、str2和str3指向的都是不同的对象,所以输出结果都为false。
3、
public static void main(String[] args) {
String str1 = "hello";
System.out.println(str1 == "he" + "llo"); // true
}
分析: 这里输出为true。”he” + “llo” 两个双引号形式的字符串常量相加, 在编译的时候直接会被转为一个字符串常量”hello”并保存在常量池中。
4、
public static void main(String[] args) {
String str1 = "hello";
String str2 = "he";
System.out.println(str1 == str2 + "llo"); // false
}
分析: 这里输出为false。str2 + “llo” 字符串变量和字符串常量相加的时候,内部是使用StringBuilder类的append()方法和toString()方法来实现的。而StringBuilder类toString()方法返回的字符串是通过构造函数创建的。所以生成的”hello”对象是在JAVA堆里。
5、
public static void main(String[] args) {
String str1 = "hello";
String str2 = new String("hello");
System.out.println(str1 == str2.intern()); // true
}
分析: 这里输出为true。String的intern()方法是扩充常量池的一个方法,当String实例str2调用intern()方法时,Java会在常量池中查找是否有相同Unicode的字符串常量对象,如果有,则返回其引用,如果没有,则在常量池中增加一个Unicode等于str2的字符串常量对象并返回它的引用。这里str2.intern()方法返回的是指向常量池中”hello”的引用,所以输出为true。
6、
public static void main(String[] args) {
String str1 = "hello";
str1.concat(" world");
System.out.println(str1); // hello
}
分析: 这里输出为”hello”。因为String是不可变(final)的,所以输出为”hello”。如果想可变,可以使用StringBuilder或StringBuffer类,或者定义一个新的变量接收一下方法的返回值String str2 = str1.concat(” world”);。
通过上边几个例子,我们应该能对Java中的字符串String有了一个很好的了解。下边来介绍一些在笔试面试中,经常遇到的问题。
1、String s = new String(“xyz”); 创建了几个String Object?
答案是1个或2个。如果之前在常量池中已经创建过"xyz"对象的话,那么只会在Java堆中创建一个"xyz"对象,
如果常量池中没有的话,也会在常量池中创建一个"xyz"对象,所以说是1个或2个。
2、是否可以继承String类?
String类是final类故不可以被继承。
3、数组有没有length()这个方法? String有没有length()这个方法?
数组没有length()这个方法,有length属性。String有length()这个方法。
4、Java中判断字符串值是否相等是用”==”还是”equals”?
Java中字符串的"equals"方法是判断两个字符串的值是否相等,"=="是判断两个字符串的地址是否相同。
5、Java的String,StringBuffer,StringBuilder有什么区别?
String是不可变(final)类,每次在String对象上的操作都会生成一个新的对象;
StringBuffer和StringBuilder是可变的,它允许在原来对象上进行操作,而不用每次增加对象;
StringBuffer是线程安全的,但效率较低,而StringBuilder则不是线程安全的,效率最高。
6、下面这段代码的运行结果是什么?
代码如下:
public static void main(String[] args) {
String[] strs = new String[10];
System.out.println(strs[1]);
System.out.println(strs[10]);
}
解答:
第一个打印输出为null,因为字符串默认值为null;
第二个打印会报ArrayIndexOutOfBoundsException,因为数组的下标是从0开始的。
7、下面这段代码的运行结果是什么?
代码如下:
public static void main(String[] args) {
String str;
System.out.println(str);
}
解答:
编译器中System.out.println(str);这一行会报错,因为局部变量str必须要初始化才能输出。