java基础解析系列(九)---String不可变性分析

java基础解析系列(九)---String不可变性分析

目录

什么是不可变

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

先看一个例子

public static void main(String[] args) throws Exception {
        String s=new String("jia");
        String s2=s.concat("jun");
        System.out.println(s);
        StringBuffer sb=new StringBuffer("jia");
        sb.append("jun");
        System.out.println(sb);
    }
输出jia和jiajun
  • 对字符串s的操做并无改变他,而对StringBuffer sb进行apped,输出的时候却改变了,这就说明了String一个不可变性。

也许你会说这是可变的

public static void main(String[] args) {
        String s1="jiajun";
        String s2=s1;
        s1="666";
        System.out.println(s1);
    }
输出:666
  • 实际上,"jiajun"字符串并无改变,能够经过一个例子来证实
String s3="jiajun";
        System.out.println(s2==s3);
        输出:true
  • 为何会这样,由于实际上"jiajun"字符串存放在了常量池,此时s2和s3都指向了这个这个字符串,因此能够证实这个字符串是不改变的并存在的
  • 之因此会输出666,是由于此时s1指向的字符串是另外一个了
  • 其实最本质的是这个改变是改变s1的引用

也许你会说这是可变的

public static void main(String[] args) {
        String s1="jiajun";
        s1=s1.replace("j","J");
        System.out.println(s1);
        s1=s1.toLowerCase();
        System.out.println(s1);
    }
    JiaJun
    jiajun
  • 实际上jiajun字符串仍是没有改变的,看一下方法的源码
2047    public String More ...replace(char oldChar, char newChar) {
2048        if (oldChar != newChar) {
                ...
2069                return new String(0, len, buf);
2070            }
2071        }
2072        return this;
2073    }
  • 能够看到返回的时候是建立一个新的字符串
  • 实际上String的一些方法substring, replace, replaceAll, toLowerCase,返回的时候是建立一个新的String

分析源码

111 public final class String
112     implements java.io.Serializable, Comparable<String>, CharSequence {
    
The value is used for character storage.
113 
114     private final char value[];

    
Cache the hash code for the string
116 
117     private int hash; // Default to 0
118 
    
  private static final long serialVersionUID = -6849794470754667710L;

136 
137     public String() {
138         this.value = new char[0];
139     }
151     public String(String original) {
152         this.value = original.value;
153         this.hash = original.hash;
154     }
1913    public String substring(int beginIndex) {
1914        if (beginIndex < 0) {
1915            throw new StringIndexOutOfBoundsException(beginIndex);
1916        }
1917        int subLen = value.length - beginIndex;
1918        if (subLen < 0) {
1919            throw new StringIndexOutOfBoundsException(subLen);
1920        }
1921        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
1922    }
  • 111行能够看到,String类是用final修饰的,说明这个类是没法被继承的
  • 114行能够String类里面维护一个value的char数组,这个数组是用final修饰的,说明这个value不能指向别的数组,可是并不说明这个value数组的内容不可变,而这个value是用private修饰的,说明只有在类里面能够修改访问他,在外部不能改变他,这是关键
  • 从1913行能够看到substring方法实际上返回的数组是新建立的数组

怎么实现不可变

  • String里面维护的value数组是用private final修饰的,没法改变引用,也没法访问这个数组修改数组的值,最关键的是private
  • 对Sting的操做,并无修改数组的值,而是建立新的String
  • 类用final修饰,方法没法被子类重写,避免被其余人破坏

不可变的好处

  • 节省空间,大量使用相同的字符串,同时指向常量池的字符串就行,若是字符串是可变的话,那么常量池就没意义了
String s1="jiajun";
        String s2="jiajun";
        System.out.println(s1==s2);
  • 线程安全,出现线程安全的是在对共享变量写的时候,而由于不可变,因此Strig是线程安全的html

  • 最重要的是安全,若是当一个String已经传给别人了,这个时候若是是可变,那么能够在后面进行修改,那么这是麻烦并不安全的。并且在hashmap中,若是做为key的String s1是可变的,那么这样是很危险的,好比说可能出现两个一样的键。java

真的不可变吗

public static void main(String[] args) throws Exception {
        String s1="jiajun";
        Field field=String.class.getDeclaredField("value");
        field.setAccessible(true);
        char [] value=(char[])field.get(s1);
        value[0]='Jiajun';
  • 实际上,经过反射能够修改value数组

为何设置为不可变

  • 调用其余方法,好比调用一些系统级操做以前,可能会有一系列校验,若是是可变类的话,可能在你校验事后,其内部的值被改变了,可能引发严重的系统崩溃问题
  • 当你在传参的时候,使用不可变类不须要去考虑谁可能会修改其内部的值

我以为分享是一种精神,分享是个人乐趣所在,不是说我以为我讲得必定是对的,我讲得可能不少是不对的,可是我但愿我讲的东西是我人生的体验和思考,是给不少人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引起本身心里的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

做者:jiajun 出处: http://www.cnblogs.com/-new/
本文版权归做者和博客园共有,欢迎转载,但未经做者赞成必须保留此段声明,且在文章页面明显位置给出原文链接,不然保留追究法律责任的权利。若是以为还有帮助的话,能够点一下右下角的【推荐】,但愿可以持续的为你们带来好的技术文章!想跟我一块儿进步么?那就【关注】我吧。算法

相关文章
相关标签/搜索