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 ...
随机推荐
- CSS中的选择器(笔记)
1.通配符选择器(*):通配符选择器是用来选择所有元素,也可以选择某个元素下的所有元素.所有浏览器都支持通配符选择器. ;;} .dome *{padding: 2px;} 2.元素选择器(Ele): ...
- java后台调用http请求
1:代码 @Value("${sms.username}") 可以将sms.properties配置文件中的值注入到username //这种方式是将sms.properti ...
- ASP.NET mvc下在Controller下action的跳转方式
在ASP.NET mvc下,action有多种挑战方式: return RedirectToAction("Index");//一个参数时在本Controller下 如果Redir ...
- httpd.conf详解,因为php始终报fileinfo扩展无法加载的错
# # This is the main Apache HTTP server configuration file. It contains the # configuration directiv ...
- Virut样本取证特征
1.网络特征 ant.trenz.pl ilo.brenz.pl 2.文件特征 通过对文件的定位,使用PEID查看文件区段,如果条件符合增加了7个随机字符区段的文件,则判定为受感染文件. 3.受感染特 ...
- linux下定时器介绍2--timer_create等函数集的使用示例
程序1:采用新线程派驻的通知方式 程序2:通知方式为信号的处理方式 #include <stdio.h>#include <time.h>#include <stdlib ...
- aarch64_p2
perl-Crypt-URandom-0.36-6.fc26.noarch.rpm 2017-02-12 07:17 16K fedora Mirroring Project perl-Crypt-X ...
- RabbitMQ--Publish/Subscribe(四)
先前例子中,我们创建了一个简单的日志系统,广播messages到consumer接收方. 但如果有日志错误级别的,不同的consumer接收不同错误级别的信息.比如consumer1接收info和wa ...
- nfs挂载出错:mount.nfs: access denied by server while mounting
这个问题就是服务器不允许客户端去挂载,那么修改服务端的权限 $ sudo vi /etc/hosts.deny 文本末添加 ### NFS DAEMONS portmap: ALL lockd: AL ...
- jexus linux x64[标准版] - 未集成mono 配置https
一.找到mono安装位置 sudo find / -name mono 二.首先查看“/lib”或“/usr/lib”等系统库文件夹中是否有SSL库文件的名字,该文件名应该是“libssl.so.版本 ...