在学习Java的过程中,我们会被告知 String 被设计成不可变的类型。为什么 String 会被 Java 开发者有如此特殊的对待?他们的设计意图和设计理念到底是什么?因此,我带着以下三个问题,对String 进行剖析:

String 真的不可变?

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

    //other codes
}

String 的底层实现是依靠 char[] 数组,既然依靠的是基础类型变量,那么他一定是可变的, String 之所以不可变,是因为 Java 的开发者通过技术实现,隔绝了使用者对 String 的底层数据的操作。但是,我们可以同反射的机制,操作 String 的底层,检验其不可变的猜想。

反射的方式操作 String :

       //创建字符串"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
 

通过两次字符串的输出,我们可以看到,String 被改变了,但是在代码里,几乎不会使用反射的机制去操作 String 字符串,所以,我们会认为 String 类型是不可变的。


为什么会将 String 设计为不可变

  • 安全

    • 引发安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞

    • 保证线程安全,在并发场景下,多个线程同时读写资源时,会引竞态条件,由于 String 是不可变的,不会引发线程的问题而保证了线程

    • HashCode,当 String 被创建出来的时候,hashcode也会随之被缓存,hashcode的计算与value有关,若 String 可变,那么 hashcode 也会随之变化,针对于 Map、Set 等容器,他们的键值需要保证唯一性和一致性,因此,String 的不可变性使其比其他对象更适合当容器的键值。

  • 性能

    • 当字符串是不可变时,字符串常量池才有意义。字符串常量池的出现,可以减少创建相同字面量的字符串,让不同的引用指向池中同一个字符串,为运行时节约很多的堆内存。若字符串可变,字符串常量池失去意义,基于常量池的String.intern()方法也失效,每次创建新的 String 将在堆内开辟出新的空间,占据更多的内存

实例代码:

String 的不可变性:

public static String appendStr(String s){
        s+="bbb";
        return s;
    }

    //可变的StringBuilder
    public static StringBuilder appendSb(StringBuilder sb){
        return sb.append("bbb");
    }

    public static void main(String[] args){
        //String做参数
        String s=new String("aaa");
        String ns=Test.appendStr(s);
        System.out.println("String aaa >>> "+s.toString()); // aaa

        //StringBuilder做参数
        StringBuilder sb=new StringBuilder("aaa");
        StringBuilder nsb=Test.appendSb(sb);
        System.out.println("StringBuilder aaa >>> "+sb.toString()); // aaabbb
    }

String 不可变的技术实现

打开JDK的源码:

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

    //other codes
}
  • String 类由关键字 final 修饰,说明该类不可继承

  • char value[] 属性也被 final 所修饰,说明 value 的引用在创建之后,就不能被改变

以上两点并不能完全实现 String 不可变 ,原因在于:

final int[] value={1,2,3}
      int[] another={4,5,6};
value=another;    // 编译器报错,final不可变

value 被 final 修饰,只能保证引用不被改变,但是 value 所指向的堆中的数组,才是真实的数据,只要能够操作堆中的数组,依旧能改变数据。【解释:String实际上是可变的】

final int[] value={1,2,3};
value[2]=100;  //这时候数组里已经是{1,2,100}
  • 所有的成员属性均被 private 关键字所修饰

为了实现 String 不可变,关键在于Java的开发者在设计和开发 String 的过程中,没有暴露任何的内部成员,与此同时 API 的设计是均没有操作 value 的值 , 而是采用 new String() 的方式返回新的字符串,保证了 String 的不可变。

JDK String API 源码:

    public static String valueOf(char c) {
        char data[] = {c};
        return new String(data, true);  //采用 new String() 的方式返回新的字符串
    }

    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);  //采用 new String() 的方式返回新的字符串
    }

整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

String类型为什么不可变的更多相关文章

  1. C++标准库string类型

    string类型支持长度可变的字符串,C++标准库将负责管理与存储字符相关的内存,以及提供各种有用的操作.标准库string类型的目的就是满足对字符串的一般应用. 本文地址:http://www.cn ...

  2. java中关于String 类型数据 的存储方式

    Constant Pool常量池的概念: 在讲到String的一些特殊情况时,总会提到String Pool或者Constant Pool,但是我想很多人都不太 明白Constant Pool到底是个 ...

  3. 聊聊JAVA中 String类为什么不可变

    前言 "我的风格比较偏传统和经典" 小明说,"我们在打扮自己的问题上还是蛮冒险的...我觉得当你是只狗的时候,穿什么都hold的住!" 哈哈哈,脱离单身狗快两年 ...

  4. C++ Primer 学习笔记_6_标准库类型 -- 命名空间using与string类型

     标准库类型(一) --命名空间using与string类型 引: 标准库类型是语言组成部分中更主要的哪些数据类型(如:数组.指针)的抽象! C++标准库定义的是高级的抽象数据类型: 1.高级:由 ...

  5. 【C++ Primer每日刷】之三 标准库 string 类型

    标准库 string 类型 string 类型支持长度可变的字符串.C++ 标准库将负责管理与存储字符相关的内存,以及提供各种实用的操作.标准库string 类型的目的就是满足对字符串的一般应用. 与 ...

  6. JAVA 没有重载运算符,那么 String 类型的加法是怎么实现的,以及String类型不可变的原因和好处

    1, JAVA 不具备 C++ 和 C# 一样的重载运算符 来实现类与类之间相互计算 的功能    这其实一定程度上让编程失去了代码的灵活性, 但是个人认为,这在一定程度上减少了代码异常的概率     ...

  7. 对String值不可变的理解以及String类型的引用传递问题

    今天复习java时,突然注意到了一句以前没有注意过的一句话,String 是final修饰的,其值是不可变的.当时看的一脸懵逼,String str = "abc"; str = ...

  8. String类型的属性和方法

    × 目录 [1]属性 [2]对象通用方法 [3]访问字符方法[4]字符串拼接[5]创建子串方法[6]大小写转换[7]查找子串位置[8]正则匹配方法[9]去除首尾空格[10]字符串比较 前面的话 前面已 ...

  9. java内存分配和String类型的深度解析

    [尊重原创文章出自:http://my.oschina.net/xiaohui249/blog/170013] 摘要 从整体上介绍java内存的概念.构成以及分配机制,在此基础上深度解析java中的S ...

随机推荐

  1. php支持多个地址跨域访问

    //跨域访问的时候才会存在此字段 $origin = isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : ''; $allow_ori ...

  2. Markdown 入门基础

    MarkDown基础语法 标题 通过 # 来实现标题效果.在markdown当中,标题与html相似,一共有六级标题. # 一级标题 ## 二级标题 ### 三级标题 #### 四级标题 ##### ...

  3. vue中如何编写可复用的组件?

    原文地址 Vue.js 是一套构建用户界面的渐进式框架.我们可以使用简单的 API 来实现响应式的数据绑定和组合的视图组件. 从维护视图到维护数据,Vue.js 让我们快速地开发应用.但随着业务代码日 ...

  4. Java学习笔记-GUI

    Java也提供图像化编程 图形化 GUI(图形用户界面) GUI Graphical User Interface(图形用户接口) 用图形的方式,来显示计算机操作的界面,这样更方便更直观 CLI Co ...

  5. OS X更新Catalina 10.15.2后虚拟机黑屏(已解决)

    简述 问题:更新OS X 10.15.2后VM Ware进unbuntu 16.0黑屏,但是VM Ware 有显示,情况类似如下: 解决办法   重启系统,command+r 进入恢复模式,打开bas ...

  6. 简单的GCC语法: 弄清gcc test.c 与 gcc -c test.c 的差别

    转载于:http://cache.baiducontent.com/c?m=9d78d513d99610fe4fede5690d60c067690597634dc0d06368d5e31587231b ...

  7. getBoundingClientRect()方法

    是在<javascript高级程序设计>中看到了这个方法.getBoundingClientRect在IE5中就有,但似乎不怎么引起我们注意. 返回值:它返回一个clientRect对象, ...

  8. 数据排序 sort

    排序命令: 常和管道进行协作的命令  -sort  (默认使用字符的第一个字符进行排序) -n  按数字排序 -r  反序排序 -o  结果 输出到文件 -t  分隔符 (sort -n -t &qu ...

  9. MGR复制

    CentOS7 配置如下 5.7.22 Group ReplicationMySQL5.7.22安装略   在三台db服务器上面设置/etc/hosts映射,如下:192.168.1.101 mydb ...

  10. jenkins转换显示语言为中文简体(jenkins汉化)

    jenkins版本2.117 单位使用的jenkins一直是英文版本,有同事建议切换为中文版. 以下过程完成转换. 一.安装插件 主界面-->系统管理-->插件管理-->可选插件 图 ...