什么是不可变对象?

众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

区分对象和对象的引用

对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:

String s = "ABCabc";
System.out.println("s = " + s);
s = "123456";
System.out.println("s = " + s);

打印结果为: s = ABCabc

s = 123456

首先创建一个String对象s,然后让s的值为“ABCabc”, 然后又让s的值为“123456”。 从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。 也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个心的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:

Java和C++的一个不同点是, 在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在C++中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如Java中的引用不能像C++中的指针那样进行加减运算。

为什么String对象是不可变的?

要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.6中,String的成员变量有以下几个:

public final class Stringimplements java.io.Serializable, Comparable<string>, CharSequence{/** The value is used for character storage. */private final char value[];/** The offset is the first index of the storage that is used. */private final int offset;/** The count is the number of characters in the String. */private final int count;/** Cache the hash code for the string */private int hash; // Default to 0</string>

在JDK1.7中,String类做了一些改动,主要是改变了substring方法执行时的行为,这和本文的主题不相关。JDK1.7中String类的主要成员变量就剩下了两个:

public final class Stringimplements 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</string>

由以上的代码可以看出, 在Java中String类其实就是对字符数组(value)的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

value,offset和count这三个变量都是private的,并且没有提供setValue, setOffset和setCount等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这三个成员。此外,value,offset和count这三个变量都是final的, 也就是说在String类内部,一旦这三个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。 
那么在String中,明明存在一些方法,调用他们可以得到改变后的值。这些方法包括substring, replace, replaceAll, toLowerCase等。例如如下代码:

String a = "ABCabc";
System.out.println("a = " + a);
a = a.replace('A', 'a');
System.out.println("a = " + a);

打印结果为: a = ABCabc

a = aBCabc

那么a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个新的对象重新赋给了引用a。String中replace方法的源码可以说明问题

读者可以自己查看其他方法,都是在方法内部重新创建新的String对象,并且返回这个新的对象,原来的对象是不会被改变的。这也是为什么像replace, substring,toLowerCase等方法都存在返回值的原因。也是为什么像下面这样调用不会改变对象的值:

String ss = "123456";
System.out.println("ss = " + ss);
ss.replace('1', '0');
System.out.println("ss = " + ss);

打印结果: ss = 123456

ss = 123456


String对象真的不可变吗?

从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。 那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

public static void testReflection() throws Exception {//创建字符串"Hello World", 并赋给引用sString 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
}

打印结果为: s = Hello World

s = Hello_World

在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

String为什么是不可变类型?相关推荐

  1. Python:python中的可变类型和不可变类型

    Python的基本数据类型大致可分为6类: 1.Number(数字)(bool布尔类型.int整型.float浮点型.complex复数等都归为Number数字类型) 2. String(字符串) 3 ...

  2. 如何给女朋友解释为什么Java里面的String对象是不可变的?

    点击关注公众号,Java干货及时送达 String的不变性 String在Java中特别常用,相信很多人都看过他的源码,在JDK中,关于String的类声明是这样的: public final cla ...

  3. python不可变的列表被称为_【Python学习】可变类型和不可变类型

    一.可变类型与不可变类型的特点 1.不可变数据类型 不可变数据类型在第一次声明赋值声明的时候, 会在内存中开辟一块空间, 用来存放这个变量被赋的值,  而这个变量实际上存储的, 并不是被赋予的这个值, ...

  4. python 可变 与 不可变类型

    一.Python对象 见<Python 核心编程> 二.可变与不可变 python的数据类型和c不太一样,有一种分类标准,可变(mutable)/不可变(immutable). 我理解的可 ...

  5. python四种可变类型_SICP Python 描述 2.4 可变数据

    2.4 可变数据 我们已经看到了抽象在帮助我们应对大型系统的复杂性时如何至关重要.有效的程序整合也需要一些组织原则,指导我们构思程序的概要设计.特别地,我们需要一些策略来帮助我们构建大型系统,使之模块 ...

  6. Python中可变类型和不可变类型的数据?

    可变类型有list,dict.不可变类型有string,number,tuple. 当进行修改操作时,可变类型传递的是内存中的地址,也就是说,直接修改内存中的值,并没有开辟新的内存. 不可变类型被改变 ...

  7. Java 中的 String 真的是不可变的吗

    转载自   Java 中的 String 真的是不可变的吗 我们都知道 Java 中的 String 类的设计是不可变的,来看下 String 类的源码. public final class Str ...

  8. python可变类型和不可变深浅拷贝类型_python3笔记十四:python可变与不可变数据类型+深浅拷贝...

    一:学习内容 python3中六种数据类型 python赋值 python浅拷贝 python深拷贝 二:python3六种数据类型 1.六种数据类型 Number(数字) string(字符串) L ...

  9. 漫话:如何给女朋友解释String对象是不可变的?

    String的不变性 String在Java中特别常用,相信很多人都看过他的源码,在JDK中,关于String的类声明是这样的: public final class String implement ...

最新文章

  1. rhel6.3挂载HP-EVA6400磁阵--linux端操作流程
  2. mxnet基础到提高(5)-- 卷积神经网络基础(1)
  3. 使用malloc创建头结点的坑
  4. java内存回收机制
  5. 使用 Carla 和 Python 的自动驾驶汽车第 4 部分 —— 强化学习代理
  6. 强制将IE,Chrome设置为指定兼容模式来解析
  7. C++:类的成员函数
  8. Python 揭秘斐波那契定律,如何帮助码农分析股票?| 技术头条
  9. Windows phone8 基础篇(二) xaml介绍 一
  10. 2021美赛总结(假)。预祝大家获得满意的成绩!
  11. 烽火携手中航信斩获“十佳上云”优秀案例大奖
  12. 阿里巴巴Java面试题
  13. 【Linux】Linux中755权限是什么意思
  14. 使用javaSwing搭建一个简单的聊天室
  15. Angular之生命周期函数
  16. python象棋博弈算法_python做中国国粹——象棋
  17. 阅读笔记--计算机网络 自顶向下方法
  18. 业务痛点、个人成长以及未来发展的一些思考
  19. 小型便携式AIS接收机dAI01
  20. idea字体变成繁体

热门文章

  1. 选择列表中的列……无效,因为该列没有包含在聚合函数或 GROUP BY 子句中
  2. 动态链表增删改查及排序功能
  3. Unable to execute dex: Multiple dex files define异常的解决办法
  4. C++用库 jsoncpp 解析 JSON
  5. Linux 安装 Elasticsearch-rtf
  6. conda设置Python虚拟环境 其他配置
  7. Debian 9/10快速开启Google BBR的方法,实现TCP高效单边加速
  8. 4kyu Sum by Factors
  9. lua 区间比较_Lua(模糊查找):判断两个字符串(含中文)是否存在至少一个相同
  10. html 页面过度效果,HTML页面过渡效果大全