一、面试经常会碰到一个问题,就是String不可变
大部分答的时候会讲因为String的源码里面,它是这样的
/** The value is used for character storage. */
private final char value[];
它是被final修饰的,被final修饰的真正含义是什么呢?一定能讲出是不可变,那么到底是什么不可变啊?我们可以来试一试
final int[] value = new int[]{1,2,3};
value = new int[]{1,2,4};
会报错
Error:(33, 9) java: 无法为最终变量value分配值
这说明引用不可变,value不能再指向另一个变量,但是这能说明value的值不可变吗?我们再来试试看
final int[] value = new int[]{1,2,3};
value[1]=4;
System.out.println(value[0]+" "+value[1]+" "+value[2]);
输出就是
1 4 3
因此,final不可变指的是引用对象不可变,而不是对象的值不可变,那么到底是什么让String对象不可变呢?再去源码看看,是不是还有个private,这个让value的值在外部是不可变的。再来看一个是如何表现线程安全的不可变的呢:
//现有一个get方法,在方法内改变string
public static String get(String str){
str += "aaa";
System.out.println("get方法中str的hashcode"+str.hashCode());
return str;
}
//测试
String str = "123";
System.out.println("str的hashcode"+str.hashCode());
System.out.println(get(str));
System.out.println(str);
System.out.println("最后的str的hashcode"+str.hashCode());
//输出的结果
str的hashcode48690
get方法中str的hashcode1450620111
123aaa
123
最后的str的hashcode48690
可以发现get方法其实形成了一个新的str,而真正的str并没有被改变
*这里有个小tip:String只能通过hashcode的方式获得相对的jvm中的地址,但并不是真实的地址。
二、java中还有个字符串常量值的概念
先来看看神奇的地方:
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
String s4 = s3.intern();
System.out.println(s1==s2);
System.out.println( s2==s3);
System.out.println(s3==s4);
System.out.println(s4==s2);
//输出
true
false
false
true
s1和s2竟然是一样的,这就是常量池的作用,你创建一个常量,它会先去常量池中寻找是否已经存在,如果存在那就引用同一个,如果不存在那就放进去,通过new操作符创建的字符串对象不指向字符串池中的任何对象,但是可以通过使用字符串的intern()方法来指向其中的某一个。java.lang.String.intern()返回一个保留池字符串,就是一个在全局字符串池中有了一个入口。如果以前没有在全局字符串池中,那么它就会被添加到里面。
因此,使用new创建对象的时候是会创建两个对象或者一个对象,它的过程是先去常量池中寻找是否有,如果有,则再堆中(常量池在方法区中)创建一个新的对象并指向它,如果没有,则在常量池中先创建一个,再在堆中创建一个,还是指向堆中的,不信咱们来看看
String s1 = new String("hello");
String s2 = "hello";
System.out.println(s1==s2);
输出是false,因此使用new创建出来的必定是指向堆里面的。
三、字符串拼接
最经典就是+号问题了,那么String s = s1 + s2到底是个啥,和String s = "hello"+"world"有啥区别吗?其实他们打印输出的结果是一样的,只不过底层的方式是不一样的
String s = s1 + s2;
这种方式,是在底层先创建一个StringBuilder对象,然后进行两次append操作,最后再toString一下输出,而new一个StringBuilder对象是在堆中,所以操作都是在堆中完成的
String s = "hello" + "world";
这种方式,编译的时候会认为+号是没用的,所以实际上等同于
String s ="helloworld";
因此当问到String,StringBuilder,StringBuffer的问题时,为啥String做字符串拼接的效率是最低的就有答案了,它会创建新的对象放在堆里面,如果循环太多而gc来不及收回,那么堆中会被占用大量的空间。
想起来还有String的内容再接着补充吧