在学习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. 使用Visual Studio Code Coverage和nunit上传单元测试覆盖率和单元测试结果到SonarQube上

    SonarQube.Scanner.MSBuild.exe begin /k:"OMDCCQuotes" /d:sonar.host.url="http://myip:9 ...

  2. English 邮件

    1.email http://www2.elc.polyu.edu.hk/cill/eiw/e-mail.htm

  3. ZOJ Problem Set - 1002

    参考: 1.http://www.cnblogs.com/phinecos/archive/2008/09/18/1293017.html 基本思想:回溯法 重点在void Solve(int k,i ...

  4. Lua字符串及模式匹配

    字符类基础函数举例介绍: string.len( ‘string’ ) string.lower( ‘string’ ) string.upper( ‘string’ ) string.rep( ‘a ...

  5. android简易跑马灯

    重点:焦点的选择(返回true使得焦点不被选择) MarqueeText.java package com.example.demo02; import android.content.Context ...

  6. windows下安装Sonar

    1.sonar安装: sonar有三部分组成: 1.服务端:显示分析结果和sonar相关配置 2.客户端:对项目运行源代码进行运算和分析 3.数据库:存储sonar配置和代码分析结果的数据库 2.so ...

  7. [Comet OJ - Contest #6 C][48C 2279]一道树题_树

    一道树题 题目大意: 给定一棵树,边的编号为读入顺序.现在规定,区间$[L, R]$的贡献$S(L,R)$为把编号在该区间里的边都连上后,当前形成的森林中点数大于等于$2$的联通块个数. 求$\sum ...

  8. Capacity To Ship Packages Within D Days

    A conveyor belt has packages that must be shipped from one port to another within D days. The i-th p ...

  9. 「java.util.concurrent并发包」之 ThreadPoolExecutor

    一 异步用new Thread? 大写的"low"!! new Thread(new Runnable() { @Override public void run() { // T ...

  10. PAT A1027 Colors in Mars (20)

    AC代码 #include <cstdio> const int max_n = 1000; int ans[max_n]; char result[max_n]; char radix[ ...