创建⼀个Java的不可变对象
⽬录
01、什么是不可变类
02、常见的不可变类
1)常量池的需要
2)hashCode 的需要
3)线程安全
03、⼿撸不可变类
04、总结
前⾔:
为什么String 是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 等也是不可变类。
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("定价:" + Book());
System.out.println("促销价:" + Book());
}
}
程序输出的结果如下所⽰:
定价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=59}
糟糕,Writer 类的不可变性被破坏了,价格发⽣了变化。为了解决这个问题,我们需要为不可变类的定义规则追加⼀条内容:
如果⼀个不可变类中包含了可变类的对象,那么就需要确保返回的是可变对象的副本。也就是说,Writer 类中的getBook() ⽅法应该修改为:
public Book getBook() {
Book clone = new Book();
clone.setPrice(Price());
clone.setName(Name());
return clone;
}
这样的话,构造⽅法初始化后的Book 对象就不会再被修改了。此时,运⾏WriterDemo,就会发现价格
不再发⽣变化了。
定价:Book{name='Web全栈开发进阶之路', price=79}
促销价:Book{name='Web全栈开发进阶之路', price=79}
04、总结
不可变类有很多优点,就像之前提到的String 类那样,尤其是在多线程环境下,它⾮常的安全。尽管每次修改都会创建⼀个新的对象,增加了内存的消耗,但这个缺点相⽐它带来的优点,显然是微不⾜道的——⽆⾮就是捡了西⽠,丢了芝⿇。
到此这篇关于创建⼀个Java的不可变对象的⽂章就介绍到这了,更多相关Java的不可变对象内容请搜索以前的⽂章或继续浏览
下⾯的相关⽂章希望⼤家以后多多⽀持!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论