`

不可变类String--阅读源码从jdk开始

阅读更多

不可变类

 

在日常java开发中,String是用得最多的类之一。对于jdk的String类的设计方式值得我们去思考和学习。

 

String类是一个不可变类,java平台的类库中包含的不可变类,如:String、基本类型的包装类(Integer等)、BigInteger和BigDecimal。为什么要设计不可变类呢?它们不容易出错,更加安全(比如作为HashMap的key),而且更加易于设计、实现和使用。

 

我们阅读String的源码在理解String的源码之前,先看下不可变类设计的5条原则:

1、不要提供任何可以修改对象状态的方法。

2、保证类不会被扩展(不能被继承)。

3、使所有的域都成为私有的。

4、使所有的域都是final的。

5、确保对任何可变组件的互斥访问。

 

根据这5点原则来看String类的源码(基于jdk1.8)。

 

成员变量

 

String的两个主要成员变量

private final char value[];//

private int hash;//首次调用String的hashcode方法后,会被缓存起来,防止后面再重新计算。

 

可以看到都是私有的满足“原则3”,String的主要成员变量 value(char类型的数组)是final的满足“原则4”。即:成员变量value在首次赋值之后,就不能被再次赋值(一般是在构造方法中赋值,或在静态实例化工程方法中赋值)。

 

有人会说成员变量hash不是final的,其实它只是对象首次调用hashcode方法后,用来缓存该对象的hash值,避免下次使用时重新计算(关于hashcode方法的重写规则可以参考这里)。看下String的hashcode实现:

    

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {//如果hash不为0,且String不为空,直接使用以前计算好的hash值。否则重新计算
            char val[] = value;
 
            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;//只需要赋值一次
        }
        return h;
}

 

 

构造方法

 

前面已经说了,由于成员变量value是final的,所以String的构造方法的主要作用就是给value 赋值。

默认构造方法:

 

public String() {
        this.value = "".value;//让value指向””字符串的value的引用。
    }

 

参数为String的构造方法: 

 

public String(String original) {
        this.value = original.value; //本身就是不可变的
        this.hash = original.hash;
    }

 

参数为char型的数组的构造方法: 

 

public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);//copy一个新的数组,防止直接应用外部传入的可变对象
    }

public String(char value[], int offset, int count){
    //类似 省略
}

 

这里采用的是Arrays.copyOf来生成一个新的数组,为成员变量value赋值。为什么不能直接赋值呢(采用 this.value =value),因为参数char value[]是可变的,如果直接赋值,当参数数组发生变化时,就会影响到新生成的String对象,着就破坏的String的“不可变性”。这一点满足不可变类设计原则5。

 

包级私有的构造方法:

 

String(char[] value, boolean share) {//该构造方法会破坏“不可变型”,因此是包级私有的,我们无法使用
        // assert share : "unshared not supported"; 
        this.value = value;
    }

 如果这个构造方法是公有的,就破坏了不可变性。说白了这个构造方法是,给写jdk的大神使用的。

 

 

 

参数为StringBuffer的构造方法:

 

public String(StringBuffer buffer) {
        synchronized(buffer) {
            this.value = Arrays.copyOf(buffer.getValue(), buffer.length());//copy一个新数组对象
        }
    }

 

 

参数为StringBulder的构造方法

 

public String(StringBuilder builder) {
        this.value = Arrays.copyOf(builder.getValue(), builder.length());//copy一个新的数组对象
    }

 

 

还有其他几个参数为byte数组的构造方法,以及用得较少的基于ascii码和代码点构造方法。这里就不再一一列举。

 

小结:不可变类的构造方法设计,不要直接引用参数传入的“不可变”对象,而是采用copy的方式,重新生成一个新的对象。

 

修改String的方法

 

其实jdk没有暴露能直接修改String内部成员变的方法,这里所谓的修改String的方法 其实是通过生成一个新的String来实现,而不是真正意义生的修改。比如:

substring方法,实际上是生成一个新的String

 

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);//创建一个新的string
    }

 

 

concat字符串连接方法,通过copy生成一个新的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); //创建一个新的string
    }

 

 

replace方法,其实是先创建一个新的char数组,在这个基础上进行替换,再根据这个新char数组生成一个新的String对象:

 

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {//找到替换位置
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];//创建一个新的char数组
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];//把老字符串中的所有字符 copy到新的char数组
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;//在新的char数组中进行替换
                    i++;
                }
                return new String(buf, true);//使用新的char数组创建一个新的字符串
            }
        }
        return this;
    }

 

 

这些方法,都是设计不可变类 规则5的体现

 

总结

 

阅读完整个类的3000多行代码,没有任何其他可供修改的String内容的公有方法(public),因此String类的设计满足规则1。

 

最后看下String类的定义public final class String,该类是不能被继承的,因此String类的设计满足规则2。

按照不可变类的5个设计原则,再参考jdk的不可变类(String、Integer等)的实现方式,就能设计出自己的高效的不可变类。

 

 

 

0
1
分享到:
评论

相关推荐

    JDK11源码 jdk-11.0.4 src源码

    《深入解析JDK11源码:以jdk-11.0.4-src为例》 在Java编程领域,深入理解JDK源码是提升技术能力的重要途径。JDK11作为Java开发工具包的一个重要版本,引入了许多新特性和改进,为开发者提供了更高效、更安全的编程...

    jdk-7u80-windows-x64.exe

    【描述】"jdk-7u80-windows-x64.exe" 描述的是该文件的格式和平台兼容性,它是一个可执行文件,用于在Windows 64位操作系统上安装JDK 7的更新80版本。这个版本的JDK包含了Java编译器(javac)、Java虚拟机(JVM)、...

    JDK源码,整合所有内容

    - **对象和类**:Java是面向对象的语言,类是其核心,JDK源码中包含了大量内置类,如`Object`、`String`、`Exception`等。 - **接口与多态**:`interface`定义了类的行为规范,多态性是Java的一大特性,体现在方法...

    jdk8源码(jdk-687fd7c7986d)

    《深入解析JDK8源码:探索lang、io、nio、util包的奥秘》 在Java编程领域,深入理解JDK源码是提升技术能力的重要途径。...对于每一个Java开发者来说,掌握JDK8的新特性和源码细节都是必不可少的技能。

    java jdk 实例宝典源码

    - **String类**:Java中不可变的字符序列,广泛用于字符串操作,如`substring()`、`indexOf()`、`concat()`等。 - **Arrays类**:提供了对数组进行排序、复制、填充的操作,如`sort()`、`equals()`。 - **...

    java1.7源码-jdk7-sourcecode-read:jdk1.7的java源代码

    Java 1.7,也被称为Java 7,是Oracle公司发布的一个重要的Java开发版本,它在2011年发布,引入了许多新特性和改进,为开发者提供了更高效...同时,这对于Java平台的维护者和扩展开发者来说,是一个不可或缺的参考资料。

    jdk1.8源码jar包

    这个"jdk1.8源码jar包"包含了上述所有特性的源代码,开发者可以通过阅读这些源代码,深入了解Java 8的实现细节,从而提高编程技巧和问题解决能力。在IDE中导入源码后,可以轻松地跳转到相关类和方法,进行深度学习。

    java源码之jdk源码

    - `java.lang.String`类是不可变的,了解它的内部实现,如字符数组、intern()方法和字符串池,有助于优化字符串操作。 9. **多线程(Multithreading)**: - `java.lang.Thread`类和`java.lang.Runnable`接口定义...

    jdk源码和jdk7开发帮助文档(api)

    对于想要提升技术水平、进行底层优化或参与开源项目的人来说,研究JDK源码是不可或缺的环节。 **JDK 7 新特性** 1. **Try-with-resources**:这个特性使得资源管理更加便捷,自动关闭在try语句块中打开的资源,如...

    java JDK 源码

    Java JDK源码是Java开发工具包的原始代码集合,它为开发者提供了深入理解Java平台工作原理的机会。JDK源码包含了许多核心类库,如`javax`、`com`、`org`、`java`以及`launcher`和`sunw`等包下的类和接口。这些源文件...

    jdk1.6源码

    《深入解析JDK1.6源码》 JDK(Java Development Kit)是Java开发工具集,其中包含了Java运行环境、编译器以及各种API。...尽管JDK已更新至更高版本,但JDK1.6源码的学习仍然是开发者不可忽视的一环。

    jdk1.8及jdk1.8源码

    **Java Development Kit (JDK) 1.8详解与源码分析** JDK 1.8,也称为Java 8,是Oracle公司发布的Java平台标准版(Java SE)的一个重大更新,于2014年3月18日正式发布。这个版本引入了许多新特性和改进,对Java编程...

    String_raw-源码.rar

    在Java编程语言中,`String`类是使用最频繁的类之一,它代表不可变的字符序列。这个“String_raw-源码.rar”文件很可能包含了Java标准库中`String`类的原始源代码,这对于深入理解`String`类的工作原理非常有帮助。...

    深入浅出JDK源码

    8. **字符串处理**:Java中的String类是不可变的,了解其内部构造、字符串连接的效率以及如何优化字符串操作,有助于编写高效的代码。 9. **模块系统**:Java 9引入了模块系统,它使得大型项目的组织和打包更加有序...

    JDK1.7源码

    JDK 1.7,全称Java Development Kit 1.7...对于想要成为Java专家的开发者而言,这是一个必不可少的学习资源。通过阅读源码,可以提高代码质量,解决实际开发中遇到的问题,甚至参与到开源社区,为Java的发展做出贡献。

    jdk src 源码 压缩包提取

    `String`类的源码揭示了字符串的不可变性以及如何进行拼接、查找、替换等操作。`Integer`类则展示了基本类型`int`与对象之间的转换。 其次,`java.util`包提供了大量实用工具类,如集合框架。`ArrayList`和`HashMap...

    JDK 源码 java

    【标题】"JDK源码解析 - Java编程基础" 【正文】 JDK(Java Development Kit)是Java开发的核心工具集,包含了编译器、运行时环境、类库以及各种工具,使得开发者能够编写、测试和部署Java应用程序。深入研究JDK...

    浅谈为什么Java里面String类是不可变的

    Java 语言中,String 类型是不可变的,这一点毫无疑问。那么,为什么 Java 语言的设计者要把 String 类型设计成不可变对象呢?下面,我们将深入探讨字符串不可变性的原因和优点。 不可变对象的定义 不可变对象指的...

    jdk1.8中文版源码+文档

    通过阅读JDK 1.8的中文版源码和文档,开发者可以深入理解这些特性的实现细节,从而在实际项目中更好地利用它们。CHM文件中的详细注释将帮助我们更清晰地了解每个类和方法的作用,提高代码的可维护性和可读性。在学习...

    JDK9 中文 chm

    通过`--module-path`和`--add-modules`等命令行选项,开发者可以更精确地控制依赖关系,减少类路径冲突,提高应用的可维护性和安全性。 ### 二、JShell(REPL) JShell是JDK9引入的交互式命令行工具,也称为Read-...

Global site tag (gtag.js) - Google Analytics