不可变类
在日常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等)的实现方式,就能设计出自己的高效的不可变类。
相关推荐
《深入解析JDK11源码:以jdk-11.0.4-src为例》 在Java编程领域,深入理解JDK源码是提升技术能力的重要途径。JDK11作为Java开发工具包的一个重要版本,引入了许多新特性和改进,为开发者提供了更高效、更安全的编程...
【描述】"jdk-7u80-windows-x64.exe" 描述的是该文件的格式和平台兼容性,它是一个可执行文件,用于在Windows 64位操作系统上安装JDK 7的更新80版本。这个版本的JDK包含了Java编译器(javac)、Java虚拟机(JVM)、...
- **对象和类**:Java是面向对象的语言,类是其核心,JDK源码中包含了大量内置类,如`Object`、`String`、`Exception`等。 - **接口与多态**:`interface`定义了类的行为规范,多态性是Java的一大特性,体现在方法...
《深入解析JDK8源码:探索lang、io、nio、util包的奥秘》 在Java编程领域,深入理解JDK源码是提升技术能力的重要途径。...对于每一个Java开发者来说,掌握JDK8的新特性和源码细节都是必不可少的技能。
- **String类**:Java中不可变的字符序列,广泛用于字符串操作,如`substring()`、`indexOf()`、`concat()`等。 - **Arrays类**:提供了对数组进行排序、复制、填充的操作,如`sort()`、`equals()`。 - **...
Java 1.7,也被称为Java 7,是Oracle公司发布的一个重要的Java开发版本,它在2011年发布,引入了许多新特性和改进,为开发者提供了更高效...同时,这对于Java平台的维护者和扩展开发者来说,是一个不可或缺的参考资料。
这个"jdk1.8源码jar包"包含了上述所有特性的源代码,开发者可以通过阅读这些源代码,深入了解Java 8的实现细节,从而提高编程技巧和问题解决能力。在IDE中导入源码后,可以轻松地跳转到相关类和方法,进行深度学习。
- `java.lang.String`类是不可变的,了解它的内部实现,如字符数组、intern()方法和字符串池,有助于优化字符串操作。 9. **多线程(Multithreading)**: - `java.lang.Thread`类和`java.lang.Runnable`接口定义...
对于想要提升技术水平、进行底层优化或参与开源项目的人来说,研究JDK源码是不可或缺的环节。 **JDK 7 新特性** 1. **Try-with-resources**:这个特性使得资源管理更加便捷,自动关闭在try语句块中打开的资源,如...
Java JDK源码是Java开发工具包的原始代码集合,它为开发者提供了深入理解Java平台工作原理的机会。JDK源码包含了许多核心类库,如`javax`、`com`、`org`、`java`以及`launcher`和`sunw`等包下的类和接口。这些源文件...
《深入解析JDK1.6源码》 JDK(Java Development Kit)是Java开发工具集,其中包含了Java运行环境、编译器以及各种API。...尽管JDK已更新至更高版本,但JDK1.6源码的学习仍然是开发者不可忽视的一环。
**Java Development Kit (JDK) 1.8详解与源码分析** JDK 1.8,也称为Java 8,是Oracle公司发布的Java平台标准版(Java SE)的一个重大更新,于2014年3月18日正式发布。这个版本引入了许多新特性和改进,对Java编程...
在Java编程语言中,`String`类是使用最频繁的类之一,它代表不可变的字符序列。这个“String_raw-源码.rar”文件很可能包含了Java标准库中`String`类的原始源代码,这对于深入理解`String`类的工作原理非常有帮助。...
8. **字符串处理**:Java中的String类是不可变的,了解其内部构造、字符串连接的效率以及如何优化字符串操作,有助于编写高效的代码。 9. **模块系统**:Java 9引入了模块系统,它使得大型项目的组织和打包更加有序...
JDK 1.7,全称Java Development Kit 1.7...对于想要成为Java专家的开发者而言,这是一个必不可少的学习资源。通过阅读源码,可以提高代码质量,解决实际开发中遇到的问题,甚至参与到开源社区,为Java的发展做出贡献。
`String`类的源码揭示了字符串的不可变性以及如何进行拼接、查找、替换等操作。`Integer`类则展示了基本类型`int`与对象之间的转换。 其次,`java.util`包提供了大量实用工具类,如集合框架。`ArrayList`和`HashMap...
【标题】"JDK源码解析 - Java编程基础" 【正文】 JDK(Java Development Kit)是Java开发的核心工具集,包含了编译器、运行时环境、类库以及各种工具,使得开发者能够编写、测试和部署Java应用程序。深入研究JDK...
Java 语言中,String 类型是不可变的,这一点毫无疑问。那么,为什么 Java 语言的设计者要把 String 类型设计成不可变对象呢?下面,我们将深入探讨字符串不可变性的原因和优点。 不可变对象的定义 不可变对象指的...
通过阅读JDK 1.8的中文版源码和文档,开发者可以深入理解这些特性的实现细节,从而在实际项目中更好地利用它们。CHM文件中的详细注释将帮助我们更清晰地了解每个类和方法的作用,提高代码的可维护性和可读性。在学习...
通过`--module-path`和`--add-modules`等命令行选项,开发者可以更精确地控制依赖关系,减少类路径冲突,提高应用的可维护性和安全性。 ### 二、JShell(REPL) JShell是JDK9引入的交互式命令行工具,也称为Read-...