什么是不可变对象 ?不可变对象指的是在创建一个对象之后 ,不能再改变它的状态 ,那么这个对象就是不可变的 。不能改变状态的意思是 ,不能改变对象内的成员变量 ,包括基本数据类型的值不能改变 ,引用类型的变量不能指向其它的对象 ,引用类型指向的对象状态也不能改变 。

这里插播一下对象和对象的引用之间的区别 ,对象的引用是放在栈中的 ,而对象是放在堆中的 ,看这个例子 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 与不可变对象的更多相关文章

  1. Java String是不可变对象

    基本数据类型和String类型都是值传递,数组,对象等是引用传递 经多方面查找,String很奇特,虽然是引用数据类型,但是采用的却是值传递!!!基本数据类型采用的都是值传递,数组和对象都是引用传递( ...

  2. java String不可变对象,但StringBuffer是可变对象

    什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...

  3. String为什么不可变

    转载:http://www.importnew.com/7440.html https://www.cnblogs.com/leskang/p/6110631.html 什么是不可变对象? 众所周知, ...

  4. Java 不可变对象

    不可变对象(immutable objects):一旦对象被创建,它们的状态就不能被改变(包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变),每次对他 ...

  5. String 的不可变真的是因为 final 吗?

    尽人事,听天命.博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步 本文已收录于 「CS-Wiki」Gitee ...

  6. Java进阶知识点4:不可变对象与并发 - 从String说起

    一.String的不可变特性 熟悉Java的朋友都知道,Java中的String有一个很特别的特性,就是你会发现无论你调用String的什么方法,均无法修改this对象的状态.当确实需要修改Strin ...

  7. 有没有想过String为什么设计为不可变对象

    1.声明为final类的目的: 主要目的就是保证String是不可变(immutable).不可变就是第二次给一个String 变量赋值的时候,不是在原内存地址上修改数据,而是重新指向一个新对象,新地 ...

  8. Scalaz(26)- Lens: 函数式不可变对象数据操作方式

    scala中的case class是一种特殊的对象:由编译器(compiler)自动生成字段的getter和setter.如下面的例子: case class City(name:String, pr ...

  9. 【Python】可变对象和不可变对象

    Python在heap中分配的对象分成两类:可变对象和不可变对象.所谓可变对象是指,对象的内容是可变的,例如list.而不可变的对象则相反,表示其内容不可变. 不可变对象:int,string,flo ...

随机推荐

  1. ASP.NET 应用生命周期19个事件简介

    下面是请求管道中的19个事件. (1)BeginRequest: 开始处理请求 (2)AuthenticateRequest授权验证请求,获取用户授权信息 (3):PostAuthenticateRe ...

  2. JS设计模式——8.桥接模式

    桥接模式的用途 在实现API的时候,桥接模式非常有用. 在设计一个JavaScript API的时候,可以用这个模式来弱化它与使用它的类和对象之间的耦合. 示例:事件监听器 桥接模式最常见和实际的应用 ...

  3. 说说C语言运算符的“优先级”与“结合性”

    论坛和博客上常常看到关于C语言中运算符的迷惑,甚至是错误的解读.这样的迷惑或解读大都发生在表达式中存在着较为复杂的副作用时.但从本质上看,仍然是概念理解上的偏差.本文试图通过对三个典型表达式的分析,集 ...

  4. Android音视频点/直播模块开发实践总结-zz

    随着音视频领域的火热,在很多领域(教育,游戏,娱乐,体育,跑步,餐饮,音乐等)尝试做音视频直播/点播功能.那么作为开发一个小白,如何快速学习音视频基础知识,了解音视频编解码的传输协议,编解码方式,以及 ...

  5. word文档下划线无法显示的解决方法

    在编辑文档的时候经常会遇到下划线无法显示的情况,如图: 如果遇到不能在姓名后面加下划线的情况,我们该怎么做? 请看下面的图解: 1.首先点击左上角的office图标 2.点击右下角“word选项” 3 ...

  6. mysql高可用架构 -> MHA环境准备-02

    环境准备 环境检查(三个测试节点的环境都应该是一样的,只有ip不同) [root@db01 bin]# cat /etc/redhat-release //系统版本 CentOS Linux rele ...

  7. 十一、springboot之web开发之Filter

    我们常常在项目中会使用filters用于录调用日志.排除有XSS威胁的字符.执行权限验证等等.Spring Boot自动添加了OrderedCharacterEncodingFilter和Hidden ...

  8. Python基础:内置常量

    本文根据Python 3.6.5的官文Built-in Constants编写,官文比较短,大家可以直接看原文. 有一些存在于 内置名称空间(the built-in namespace) 的常量,如 ...

  9. Python元组与字典详解

    Python 元组 Python的元组与列表类似,不同之处在于元组的元素不能修改. 元组使用小括号,列表使用方括号. 元组创建很简单,只需要在括号中添加元素,并使用逗号隔开即可. 如下实例: tup ...

  10. js自定义鼠标右键菜单

    document.oncontextmenu = function(e) { return false; } document.onmousedown = function(e) { switch(e ...