什么是不可变对象?
众所周知, 在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 String implements 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 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</string> }
由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。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", 并赋给引用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 }
在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。
转自:http://www.2cto.com/kf/201401/272974.html
相关推荐
通过上面的分析,我们可以看到,String类的字符串是不可变的,而StringBuffer和StringBuilder类的字符串可以被修改。StringBuffer类是线程安全的,而StringBuilder类不是线程安全的。 在选择字符串类时,我们需要...
在Java编程语言中,`String`类是使用最频繁的类之一,它代表不可变的字符序列。本文将深入解析`String`类的一些常用方法,帮助开发者更好地理解和使用这个核心类。 1. **构造方法** - `String()`:创建一个空字符...
众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象是不可变的。不能改变状态的意思是,不能改变对象...
从给定的文件片段中,我们可以提取出关于Java中Object类和String类的源码分析的知识点。 首先,Object类是Java中所有类的根类。它包含了所有对象共有的方法,是所有类继承结构的最顶层。从文件片段中我们可以看到...
1. `String` 类:Java中的字符串是不可变对象,由`String`类表示。源码中可以看到`String`是如何实现字符串拼接、比较和查找等操作的。例如,`substring()`方法用于提取子字符串,`indexOf()`方法查找字符或子串的...
在`String`的源码中,可以学习到如何设计一个高效且安全的不可变类。 总的来说,`String`类是Java开发中不可或缺的一部分,理解和掌握其特性和方法对于编写高质量的代码至关重要。通过深入研究源码,我们能更深入地...
在Java编程语言中,`String`类是使用最频繁的类之一,它代表不可变的字符序列。这个“String_raw-源码.rar”文件很可能包含了Java标准库中`String`类的原始源代码,这对于深入理解`String`类的工作原理非常有帮助。...
String 类是 Java 语言中一个典型的不可变对象,下面是 JDK 1.8 中 String 类的部分源码: ```java public final class String implements java.io.Serializable, Comparable<String>, CharSequence { / The value...
在这个名为"Java可变参数demo"的示例中,开发者通过分析Android蓝牙框架层的源码,对Java的可变参数有了更深入的理解,并编写了一个小的演示程序以备后用。下面我们将详细探讨Java可变参数的相关知识点。 1. 可变...
String 在 Java 中是一个不可变类,这意味着一旦创建了一个 String 对象,就不能改变它的值。这种设计带来了许多优点,包括线程安全、缓存哈希码以及能够被用作哈希表键等。接下来,我们将详细分析 Java 6 String ...
1. **字符串(String)**:在Java中,String是一个不可变的对象,它用于处理文本数据。String类提供了大量方法,如`concat()`, `substring()`, `indexOf()`, `replace()`等,用于字符串的连接、截取、查找和替换。深入...
2. `String`类:不可变的字符序列,用于处理文本数据。`String`类提供了许多实用的方法,如`substring()`、`concat()`、`indexOf()`等,学习其源代码有助于掌握字符串操作的内部机制。 3. `Integer`类:作为`int`的...
《Java String 可变性的分析》 在Java编程语言中,String对象的不可变性是一个核心概念,也是面试中常见的问题。通常我们认为,一旦创建了一个String对象,其内容就不能更改。然而,通过深入源码,我们可以揭示...
Java中的`String`类是编程中非常基础且重要的部分,它代表不可变的字符序列。在Java中,字符串被广泛用于各种操作,如拼接、比较、搜索、格式化等。`String`类位于`java.lang`包中,是所有Java程序的默认组成部分,...
`String` 是Java中最常用的类,表示不可变的字符序列。一旦创建,其内容就不能改变。字符串对象在内存中通常由连续的字符数组表示,且`String`类实现了`CharSequence`接口。由于`String`是不可变的,每次对字符串...
`String`类是不可变字符串的实现,其源码解析可以帮助理解字符串的拼接、比较等操作。 2. **集合框架**:Java 1.6的集合框架在`java.util`包中,包括`List`、`Set`、`Map`接口及其实现类如`ArrayList`、`LinkedList...
`String`是Java中的一个类,用于表示不可变的字符序列,通常用来存储文本信息。而`Image`是Java AWT(Abstract Window Toolkit)库中的一个接口,它代表了图像数据,可以是位图或矢量图。 1. **字符串转图像** 要...
6. **字符串操作**:Java的String类是不可变的,源码可能包含大量关于字符串拼接、查找、替换等操作的例子。 7. **IO流**:Java的IO流用于读写数据,包括文件操作、网络通信等。源码可能涉及BufferedReader、...
再者,`java.lang.String`类是不可变的,它的源码展示了如何高效地处理字符串操作,如拼接、查找和替换。`StringBuilder`和`StringBuffer`作为可变字符串,它们在多线程环境下提供了线程安全的字符串操作。 `java....