• 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是线程安全的

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

    真的不可变吗

    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/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

  • 相关阅读:
    简单字符串处理应避免使用正则表达式
    提高正则表达式的可读性
    用零宽度断言匹配字符串中的特定位置
    避免不必要的回溯
    预编译正则表达式
    用Text::CSV_XS模块处理csv文件
    Ack 类似grep一样的查找
    Apache压力测试
    仅编译正则表达式一次
    排序上下箭头的是实现
  • 原文地址:https://www.cnblogs.com/-new/p/7763527.html
Copyright © 2020-2023  润新知