String 与不可变对象
什么是不可变对象 ?不可变对象指的是在创建一个对象之后 ,不能再改变它的状态 ,那么这个对象就是不可变的 。不能改变状态的意思是 ,不能改变对象内的成员变量 ,包括基本数据类型的值不能改变 ,引用类型的变量不能指向其它的对象 ,引用类型指向的对象状态也不能改变 。
这里插播一下对象和对象的引用之间的区别 ,对象的引用是放在栈中的 ,而对象是放在堆中的 ,看这个例子 String s = "123" ; s = "456" ; 表面上 s 看是变了 ,但是要搞清楚 ,变的只是 String 对象的引用 s ,而 “123” 这个对象是没有变化的 。看一个图 :

我们都说 String 是不可变对象 ,那我们就分析一下 String 的源码来看看它是怎么保证 String 对象不可变的 。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[]; /** Cache the hash code for the string */
private int hash; // Default to 0 public String(String original) {
this.value = original.value;
this.hash = original.hash;
} public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
} public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
...
我们通过分析源码可知 ,首先 String 类和存放字符串的 char 型数组都是 final 类型的 ,保证了 String 类不能被继承 ,value 这个引用本身不会该变 ,但是这样还不能说明属性是不可变的 ,因为我们可以通过改变 value 数组中具体的值来达到改变 value 的目的 。
public static void main(String[] args) {
char[] value = {'a','b','c','d'};
System.out.println(value);
for (int i = 0; i < value.length; i++) {
if(i == 1){
value[i] = 'B';
}
}
System.out.println(value); //aBcd
}
但是进一步研究会发现 ,对于 value 这个属性 ,被定义为 private ,而且 String 并没有提供相对应的 get set 方法 ,所以我们也不能操作它 。而那些我们常认为改变了 String 对象的方法 ,比方说 subString concat toUpperCase tirm 看了源码之后发现 ,这些方法都是重新创建了一个 char 数组 ,并没有改动之前的对象 。这也就是我们常说 String 是不可变对象的原因 。但是我们还是可以通过反射的方式来改变 value 的值 ,看个例子就好 ,但是我们一般不这么做 。
public static void main(String[] args) throws Exception {
//创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
}
为什么要将 String 设计为不可变对象呢 ?当然是为了提高效率和安全 ,也是由于 String 的超高使用频率 。效率主要体现在当我们复制 String 对象的时候我只需要复制引用即可 ,不需要复制具体的对象 ,而在多线程环境中 ,若是不同的线程同时修改 String 对象 ,相互之间也不会有影响 。因为一旦改变都会创建一个新的字符串 ,保证了线程的安全 。
另外在堆中有个字符串常量池 ,我们创建的字符串都会存在这里 ,当创建相同的字符串的时候 ,其实指向的是同一个地方 。这就节省了大量的空间 ,当然 ,能出现字符串常量池也是因为 String 是不可变对象 。

因为 String 不可变 ,所以在创建对象的时候就已经将 hashCode 的值计算出来缓存在字段 hash 中 。这样在 Map 中 String 就很适合作为主键 ,速度快 。(因为在散列表中我们定位时会计算主键的 hash 值 。)
/** Cache the hash code for the string */
private int hash; // Default to 0
还有在数据库连接的时候我们通过字符串来传递用户名 ,密码 ,连接的库等信息 ,若字符串可变 ,则很可能被黑客篡改 。
PS. 基本类型对应的包装类都是不可变对象 ,这样设计的原因还是因为使用太频繁 ,好处在上面已经说过了 。突然感觉学到了好多之前没注意到的细节 。
源码版本为 JDK 1.7
推荐阅读:https://www.cnblogs.com/YJK923/p/9479805.html
参考资料:https://zhuanlan.zhihu.com/p/38144507
String 与不可变对象的更多相关文章
- Java String是不可变对象
基本数据类型和String类型都是值传递,数组,对象等是引用传递 经多方面查找,String很奇特,虽然是引用数据类型,但是采用的却是值传递!!!基本数据类型采用的都是值传递,数组和对象都是引用传递( ...
- java String不可变对象,但StringBuffer是可变对象
什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...
- String为什么不可变
转载:http://www.importnew.com/7440.html https://www.cnblogs.com/leskang/p/6110631.html 什么是不可变对象? 众所周知, ...
- Java 不可变对象
不可变对象(immutable objects):一旦对象被创建,它们的状态就不能被改变(包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变),每次对他 ...
- String 的不可变真的是因为 final 吗?
尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 「CS-Wiki」Gitee ...
- Java进阶知识点4:不可变对象与并发 - 从String说起
一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...
- 有没有想过String为什么设计为不可变对象
1.声明为final类的目的: 主要目的就是保证String是不可变(immutable).不可变就是第二次给一个String 变量赋值的时候,不是在原内存地址上修改数据,而是重新指向一个新对象,新地 ...
- Scalaz(26)- Lens: 函数式不可变对象数据操作方式
scala中的case class是一种特殊的对象:由编译器(compiler)自动生成字段的getter和setter.如下面的例子: case class City(name:String, pr ...
- 【Python】可变对象和不可变对象
Python在heap中分配的对象分成两类:可变对象和不可变对象.所谓可变对象是指,对象的内容是可变的,例如list.而不可变的对象则相反,表示其内容不可变. 不可变对象:int,string,flo ...
随机推荐
- Linux下压缩文件-1
tar负责打包,gzip负责压缩 tar-c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的 ...
- [转载]function与感叹号
http://swordair.com/function-and-exclamation-mark/ 最近有空可以让我静下心来看看各种代码,function与感叹号的频繁出现,让我回想起2个月前我回杭 ...
- github pages 正确访问方式
❌ http://username.github.io ✅ http://username.github.io. github pages 绑定域名后,由于浏览器dns缓存,需要重启一下
- 2016-2017-2 《Java程序设计》第六周学习总结
20155223 2016-2017-2 <Java程序设计>第六周学习总结 教材学习内容总结 第十章 InputStream.OutputStream:无论数据源或目的地为何,只要设法取 ...
- 基本控件文档-UIButton属性
CHENYILONG Blog 基本控件文档-UIButton属性 Fullscreen UIButton属性技术博客http://www.cnblogs.com/ChenYilong/ 新浪微博ht ...
- 天梯赛 L2-002. (模拟) 链表去重
题目链接 题目描述 给定一个带整数键值的单链表L,本题要求你编写程序,删除那些键值的绝对值有重复的结点.即对任意键值K,只有键值或其绝对值等于K的第一个结点可以被保留.同时,所有被删除的结点必须被保存 ...
- OpenCV LIBTIFF_4.0 link errors
以前用Caffe用的好好的,今天重装后居然报了很多这样的错误 /usr/lib/libopencv_highgui.so.' 1> /usr/lib/libopencv_highgui.so.' ...
- 【Python学习笔记】Jupyter Lab目录插件安装
Jupyter Lab目录插件安装 当然首先你得有python和已经安装了jupyter lab. 1 安装jupyter_contrib_nbextensions 首先先安装jupyter_cont ...
- linq和ef关于group by取最大值的两种写法
LINQ: var temp = from p in db.jj_Credentials group p by p.ProfessionID into g select new { g.Key, Ma ...
- Golang实现mysql读库映射成Map【Easy】
这个类库灵感来源于.net的dbHelper类,因为其简单易用,现在go的driver必须使用对象映射,这让人火大不爽,不能实现灵活的Map,在Key经常变动的业务场景里面非常不爽,我还是喜欢直接写s ...