什么是不可变对象:
大家都知道java中String对象是不可变的,现在明确一个概念什么叫不可变对象。
一个对象在创建成功之后,对象中的成员变量不可被改变,基本类型的成员值不可改变,引用类型的成员变量不能指向不可改变
区分Java中对象和对象引用
下面用String来举例说明
String a = "abc"
System.out.println(a)
a = "abcABC"
因为a 只是指向String类型变量的一个引用,上面的代码只是将变量a由指向"abc"这个String对象转向"abcABC"这个对象
内存结构图如下:
Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。
为什么Java中的String是不可变的
我们从两个方面来看,源码的设计和这样设计的优点
1:源码分析
上面是JDK8的String成员对象,字符数组,int类型的hash值,long类型的serialVersionUID都是provate类型的,并且没有提供get set方法,并且value[] 和 serialVersionUID是final类型的,对于外界这些字段的值都是没发改变的。在Java中数组也是对象,所以上图中的value是指向字符数组的一个引用。
在执行完 String s = "abc"后内存布局应该是这样的
进一步验证String对象是不可变的
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
上面是String中的subString方法
注意到最后标红的subString返回的新被截取的字符串都是新new出来的,
String s = "abc"
s = s.subString(0,1)
也就是上面代码中"abc"字符串对象并没有被改变,subString方法返回的是一个新的String对象然后让s指向该新对象,大家可以去验证,String类中其他类似的改变字符串的方法都是返回一个新的对象
2:设计String类为不可变的优点
1)出于安全考虑举例说明
- HashSet<StringBuilder> hs = new HashSet<StringBuilder>();
- StringBuilder sb1 = new StringBuilder("aaa");
- StringBuilder sb2 = new StringBuilder("aaabbb");
- hs.add(sb1);
- hs.add(sb2); //这时候HashSet里是{"aaa","aaabbb"}
- StringBuilder sb3 = sb1;
- sb3.append("bbb");//这时候HashSet里是{"aaabbb","aaabbb"}
- System.out.println(hs);
上面的代码中:将sb1,sb2添加到hashset中,然后修改sb1的值跟sb2的值相同,违反了Hashset中Key不能重复的原则
A:还有一点,在多线程的环境中,读是不会出现安全问题的,但竞争写操作会引起线程安全问题,不可变类型的变量不能被写所以线程安全。
B:在java堆内存中有常量池,常用的字符串值是直接存放在常量池中的,如果两个字符串的值相同其实是指向了常量池中的同一个变量,这样设计可以节省内存空间。
String one = "someString";
String two = "someString"
String对象 的不可变性是常量池的基础,不然也就没了意义。