String字符串是我们日常工作中常用的一个类,在面试中也是高频考点,这里Hydra精心总结了一波常见但也有点烧脑的String面试题,一共5道题,难度从简到难,来一起来看看你能做对几道吧。
本文基于jdk8版本中的String进行讨论,文章例子中的代码运行结果基于
Java 1.8.0_261-b12
第1题,奇怪的 nullnull
下面这段代码最终会打印什么?
public class Test1 {
private static String s1;
private static String s2;
public static void main(String[] args) {
String s= s1+s2;
System.out.println(s);
}
}
揭晓答案,看一下运行结果,打印了nullnull
:
在分析这个结果之前,先扯点别的,说一下为空null
的字符串的打印原理。查看一下PrintStream
类的源码,print
方法在打印null
前进行了处理:
public void print(String s) {
if (s == null) {
s = "null";
}
write(s);
}
因此,一个为null
的字符串就可以被打印在我们的控制台上了。
再回头看上面这道题,s1
和s2
没有经过初始化所以都是空对象null
,需要注意这里不是字符串的"null"
,打印结果的产生我们可以看一下字节码文件:
编译器会对String
字符串相加的操作进行优化,会把这一过程转化为StringBuilder
的append
方法。那么,让我们再看看append
方法的源码:
public AbstractStringBuilder append(String str) {
if (str == null)
return appendNull();
//...
}
如果append
方法的参数字符串为null
,那么这里会调用其父类AbstractStringBuilder
的appendNull
方法:
private AbstractStringBuilder appendNull() {
int c = count;
ensureCapacityInternal(c + 4);
final char[] value = this.value;
value[c++] = 'n';
value[c++] = 'u';
value[c++] = 'l';
value[c++] = 'l';
count = c;
return this;
}
这里的value
就是底层用来存储字符的char
类型数组,到这里我们就可以明白了,其实StringBuilder
也对null
的字符串进行了特殊处理,在append
的过程中如果碰到是null
的字符串,那么就会以"null"
的形式被添加进字符数组,这也就导致了两个为空null
的字符串相加后会打印为"nullnull"
。
第2题,改变String的值
如何改变一个String字符串的值,这道题可能看上去有点太简单了,像下面这样直接赋值不就可以了吗?
String s="Hydra";
s="Trunks";
恭喜你,成功掉进了坑里!在回答这道题之前,我们需要知道String是不可变的,打开String的源码在开头就可以看到:
private final char value[];
可以看到,String的本质其实是一个char
类型的数组,然后我们再看两个关键字。先看final
,我们知道final
在修饰引用数据类型时,就像这里的数组时,能够保证指向该数组地址的引用不能修改,但是数组本身内的值可以被修改。
是不是有点晕,没关系,我们看一个例子:
final char[] one={'a','b','c'};
char[] two={'d','e','f'};
one=two;
如果你这样写,那么编译器是会报错提示Cannot assign a value to final variable 'one'
,说明被final
修饰的数组的引用地址是不可改变的。但是下面这段代码却能够正常的运行:
final char[] one={'a','b','c'};
one[1]='z';
也就是说,即使被final
修饰,但是我直接操作数组里的元素还是可以的,所以这里还加了另一个关键字private
,防止从外部进行修改。此外,String类本身也被添加了final
关键字修饰,防止被继承后对属性进行修改。
到这里,我们就可以理解为什么String是不可变的了,那么在上面的代码进行二次赋值的过程中,发生了什么呢?答案很简单,前面的变量s
只是一个String对象的引用,这里的重新赋值时将变量s
指向了新的对象。
上面白话了一大顿,其实是我们可以通过比