上一节咱们讲解到字符串本质上就是字符数组,同时详细讲解了字符串判断相等须要注意的地方,本节咱们来深刻探讨字符串特性,下面咱们一块儿来看看。java
咱们依然借助初始化字符串的方式来探讨字符串的不可变性,以下:数组
String str = "Jeffcky";
System.out.println(str);
上述咱们经过字面量的方式来建立字符串,接下来咱们对字符串str进行以下操做:缓存
String str = "Jeffcky";
str.substring(0,3).concat("wang").toLowerCase().trim();
System.out.println(str);
咱们看到针对str字符串进行截取、链接、小写等操做后,字符串的值依然未发生改变,这就是字符串的不可变性,咱们经过查看任意一个对字符串操做的方法,好比concat方法源码:安全
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); }
经过查看concat源码得出:原始的str值永远都没有发生改变,它的值只是被复制,而后将咱们链接的文本添加到复制的副本里,最后返回一个新的String。因此到这里咱们知道针对字符串的操做都是复制一份字符串,而后对复制后的字符串进行操做,最终返回一个新的String对象。多线程
咱们再来看看上一节所给出的代码示例,以下:优化
public class Main { public static void main(String[] args) { String str1 = "Jeffcky"; String str2 = "Jeffcky"; System.out.println(str1 == str2); System.out.println(str1.equals(str2)); } }
当咱们实例化一个String时(在本例中为Jeffcky)保存在Java堆内存(用于全部Java对象的动态内存分配)中。虽然在这个例子中咱们有两个不一样的引用变量,但它们都只是指Java Heap Memory中的同一内存位置,虽然看起来有两个不一样的String对象,但实际上只有一个,而str2永远不会被实例化为对象,而是在内存中分配对应于str1的对象,这是由于Java针对字符串进行了优化处理,每次要实例化此类String对象时,都会将要添加到堆内存的值与先前添加的值进行比较,若是值已存在,则不初始化对象,并将值分配给引用变量,这些值保存在名叫“字符串池”中,该字符串池包含全部文字字符串值,固然咱们能够经过new运算符绕过这种状况。this
上述咱们经过例子说明了字符串的不可变性特性,那么为何字符串是不可变的呢?能够参考知乎回答:《https://www.zhihu.com/question/31345592》。我认为主要在于有效共享对象,节省内存空间。当程序运行时,建立的String实例的数量也会增加,若是不缓存String常量,堆空间中会有大量的String,占用内存空间,因此String对象被建立后缓存在字符串池中,若缓存的字符串被多个客户端共享,此时一个客户端的操做修改了字符串则影响到其余客户端,所以经过字符串的不可变性来规避这种风险,同时经过缓存和共享字符串常量,JVM为Java应用程序节省内存。spa
有了字符串不可变性,能够很安全的被多线程所共享,咱们不用担忧线程同步问题,确保线程安全。线程
有了字符串不可变性,能够很好的使用好比HashMap,咱们能正确检索到存储到HashMap中的对象,若字符串可变且在插入到HashMap后并修改了字符串内容,此时将会出现丢失对应字符串所映射的对象。设计
有了字符串不可变性,此时会缓存字符串哈希码,因此每次调用字符串的hashcode方法时都不用计算,使得在HashMap中使用键很是快。
其余等等......
接下来咱们一块儿来经过源码的方式来看看String的实现,以下:
public class Main { public static void main(String[] args) { char a[] = {'j', 'e', 'f', 'f', 'c', 'k', 'y'}; String str = new String(a); System.out.println(str); } }
咱们经过字符数组建立字符串的方式去查看源码,以下:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** * Initializes a newly created {@code String} object so that it represents * an empty character sequence. Note that use of this constructor is * unnecessary since Strings are immutable. */ public String() { this.value = new char[0]; } /** * Allocates a new {@code String} so that it represents the sequence of * characters currently contained in the character array argument. The * contents of the character array are copied; subsequent modification of * the character array does not affect the newly created string. * * @param value * The initial value of the string */ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } ... }
咱们看到字符串对象定义为final(当前咱们还未学到final,咱们只须要知道经过final关键字修饰说明该类不可继承),网上有不少例子说字符串对象经过final关键字修饰,说明字符串不可变,其实这种说法是不严谨且错误的。String经过final关键字修饰的缘由在于:确保不能经过扩展和覆盖行为来破坏String类的不可变性而非说明字符串不可变。好比,以下例子:
public class Main { public static void main(String[] args) { String str1 = "Jeffcky"; String str2 = "Jeffcky".toUpperCase(); System.out.println(str1); System.out.println(str2); } }
如今字符串str2为"Jeffcky".toUpperCase(),咱们将同一个对象修改成“JEFFCKY”,若是修改了字符串变量,其余字符串变量也将自动受到影响,好比str1也将是"JEFFCKY",很显然是不可取的。
本文咱们详细介绍了字符串的不可变、字符串池特性,同时解释了字符串为什么不可变,以及说明字符串类定义为final,并非说明其不可变,只是为了避免容许经过扩展或覆盖来破坏字符串的不可变性。