二哥,你能给我说说为什么 String 是 immutable 类(不能变工具)吗?我想研究它,想知道为什么它就不能变了,这种强烈的愿望就像想研究众多的星空一样。但无奈自身功力有限,始终以为雾里看花终隔一层。二哥你的文章总是充满趣味性,我想一定能够说明了,我也一定能够看明了,能在接下来写一写吗?
收到读者小 R 的私信后,我就总感受自己有一种义不容辞的责任,非要把 immutable 类说明了,否则我就怎么地——你说了算!
01、什么是不能变类
一个类的工具在通过组织方式建立后若是状态不会再被改变,那么它就是一个不能变(immutable)类。它的所有成员变量的赋值仅在组织方式中完成,不会提供任何 setter 方式供外部类去修改。
还记得《神雕侠侣》中小龙女的古墓吗?随着那一声巨响,仅有的通道就被无情地关闭了。别较真谁人密道,我这么说只是为了打开你的想象力,让你对不能变类有一个更直观的印象。
自从有了多线程,生产力就被无限地放大了,所有的程序员都爱它,由于壮大的硬件能力被充实地利用了。但与此同时,所有的程序员都对它心生忌惮,由于一不小心,多线程就会把工具的状态变得杂乱不堪。
为了珍爱状态的原子性、可见性、有序性,我们程序员可以说是竭尽所能。其中,synchronized(同步)关键字是最简朴最入门的一种解决方案。
如果说类是不能变的,那么工具的状态就也是不能变的。这样的话,每次修改工具的状态,就会发生一个新的工具供差别的线程使用,我们程序员就不必再忧郁并发问题了。
02、常见的不能变类
提到不能变类,险些所有的程序员第一个想到的,就是 String 类。那为什么 String 类要被设计成不能变的呢?
1)常量池的需要
字符串常量池是 Java 堆内存中一个特殊的存储区域,当建立一个 String 工具时,假云云字符串在常量池中不存在,那么就建立一个;如果已经存,就不会再建立了,而是直接引用已经存在的工具。这样做能够削减 JVM 的内存开销,提高效率。
2)hashCode 的需要
由于字符串是不能变的,所以在它建立的时刻,其 hashCode 就被缓存了,因此异常适合作为哈希值(比如说作为 HashMap 的键),多次挪用只返回同一个值,来提高效率。
3)线程平安
就像之前说的那样,若是工具的状态是可变的,那么在多线程环境下,就很容易造成不能预期的效果。而 String 是不能变的,就可以在多个线程之间共享,不需要同步处置。
因此,当我们挪用 String 类的任何方式(比如说 trim()
、substring()
、toLowerCase()
)时,总会返回一个新的工具,而不影响之前的值。
String cmower = "缄默王二,一枚有趣的程序员";
cmower.substring(0,4);
System.out.println(cmower);// 缄默王二,一枚有趣的程序员
虽然挪用 substring()
方式对 cmower 进行了截取,但 cmower 的值没有改变。
除了 String 类,包装器类 Integer、Long 等也是不能变类。
【Android】Retrofit源码学习
03、自界说不能变类
看懂一个不能变类也许容易,但要建立一个自界说的不能变类生怕就有点难了。但知难而进是我们作为一名优异的程序员不能或缺的品质,正由于不容易,我们才气真正地掌握它。
接下来,就请和我一起,来自界说一个不能变类吧。一个不能变诶,必须要知足以下 4 个条件:
1)确保类是 final 的,不允许被其他类继续。
2)确保所有的成员变量(字段)是 final 的,这样的话,它们就只能在组织方式中初始化值,而且不会在随后被修改。
3)不要提供任何 setter 方式。
4)若是要修改类的状态,必须返回一个新的工具。
根据以上条件,我们来自界说一个简朴的不能变类 Writer。
public final class Writer {
private final String name;
private final int age;
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}
Writer 类是 final 的,name 和 age 也是 final 的,没有 setter 方式。
OK,听说这个作者分享了许多博客,广受读者的喜好,因此某某出版社找他写了一本书(Book)。Book 类是这样界说的:
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}
2 个字段,分别是 name 和 price,以及 getter 和 setter,重写后的 toString()
方式。然后,在 Writer 类中追加一个可变工具字段 book。
public final class Writer {
private final String name;
private final int age;
private final Book book;
public Writer(String name, int age, Book book) {
this.name = name;
this.age = age;
this.book = book;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public Book getBook() {
return book;
}
}
并在组织方式中追加了 Book 参数,以及 Book 的 getter 方式。
完成以上事情后,我们来新建一个测试类,看看 Writer 类的状态是否真的不能变。
public class WriterDemo {
public static void main(String[] args) {
Book book = new Book();
book.setName("Web全栈开发进阶之路");
book.setPrice(79);
Writer writer = new Writer("缄默王二",18, book);
System.out.println("订价:" + writer.getBook());
writer.getBook().setPrice(59);
System.out.println("促销价:" + writer.getBook());
}
}
程序输出的效果如下所示:
订价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=59}
糟糕,Writer 类的不能变性被破坏了,价钱发生了转变。为了解决这个问题,我们需要为不能变类的界说规则追加一条内容:
若是一个不能变类中包含了可变类的工具,那么就需要确保返回的是可变工具的副本。也就是说,Writer 类中的 getBook()
方式应该修改为:
public Book getBook() {
Book clone = new Book();
clone.setPrice(this.book.getPrice());
clone.setName(this.book.getName());
return clone;
}
这样的话,组织方式初始化后的 Book 工具就不会再被修改了。此时,运行 WriterDemo,就会发现价钱不再发生转变了。
订价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=79}
04、总结
不能变类有许多优点,就像之前提到的 String 类那样,尤其是在多线程环境下,它异常的平安。只管每次修改都市建立一个新的工具,增加了内存的消耗,但这个瑕玷相比它带来的优点,显然是微不足道的——无非就是捡了西瓜,丢了芝麻。
若是以为文章对你有点辅助,请微信搜索「 缄默王二 」第一时间阅读,回复【666】【1024】更有我为你经心准备的 500G 高清教学视频(已分门别类),以及大厂手艺牛人整理的面经一份。
原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/1868.html