开场白
老铁 :所谓“不可变对象”就是当对象创建以后其成员变量不再能发生变化。今天我们以昨天所提的问题——“为什么在特定场景下需要clone操作?”作为引子来说明在定义不可变对象时的注意事项,详细说说我们需要避开哪些坑,同时把前期系列文章的一些知识点进行串联复习,起到融会贯通、举一反三的“温故而知新”效果。本文需要用到的知识点可参看文末的【延伸阅读】。
穿越构造函数的陷阱
代码1自定义了一个文章类Article;代码2自定义了一个学生类Student,本意是意欲定义一个不可变对象类。老铁们想想代码2的实现方式正确吗?并请事先给出代码2的输出。
代码1:
//定义一篇文章
public class Article{
//文章标题
private String title;
//文章作者
private String author;
public Article(String n,String a){
setTitle(n);
setAuthor(a) ;
}
public String getTitle() {
return title;
}
public void setTitle(String t) {
this.title = t;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
代码2:
final public class MutableStudent {
private String name;//学生姓名
private Article article;//has-a关系;委托
//构造函数
//所有赋值操作均在构造函数中完成
public MutableStudent(String n,Article a) {
name = n;
article = a;
}
//只有取值getter方法,没有赋值setter方法
public String getName(){
return this.name;
}
//获取学生数据
public Article getArticle(){
return this.article;
}
//打印信息
public void print(){
System.out.println("Student:" + name +
";Article:" + article.getTitle() +
";" + article.getAuthor());
}
//测试客户端
public static void main(String[] args){
Article article = new
Article("Java敲黑板","chatbook");//1
MutableStudent student = new
MutableStudent("程序员",article);//2
student.print();
article.setTitle("Java划重点");
student.print();
}
}
Student:程序员;Article:Java敲黑板;chatbook
Student:程序员;Article:Java划重点;chatbook
通过程序的运行结果,可以肯定的是:MutableStudent不是不可变对象类。//1定义了一个article对象,并以参数的形式传到了MutableStudent的构造函数中,根据//2执行语句的定义方式,我们发现构造函数中执行了对象引用之间的赋值操作,也就是说传入的article对象引用与MutableStudent类对象中的article对象引用其实是指向了同一个对象,当客户端中的article对象引用成员发生变化时,MutableStudent类对象中的article对象引用所指向的对象内容也发生了变化。这显然不符合不可变对象类的定义。
如果要将MutableStudent类修改为不可变对象类,如何破?
穿越getter函数的陷阱
如果我们继续将测试客户端修改为代码3的内容,老铁们也想想该测试的结果输出内容会是什么?
代码3:
//测试客户端
public static void main(String[] args){
Article article = new
Article("Java敲黑板","chatbook");
MutableStudent student = new
MutableStudent("程序员",article);
student.print();
student.getArticle().setTitle("Java划重点");
student.print();
}
是的,您没有想错:输出结果和上述完全一致。
通过上面的代码,其实,分析的过程也与上述一致;老铁们可以自行分析。
如果要将MutableStudent类修改为不可变对象类,如何破?
正确的做法
看过NO.3 厉害了,clone哥 与 NO.4 我是Java大厦的一块砖:何谓对象与对象引用 的老铁们其实不难想出上述解决方案:就是分别在构造函数传入不可变对象时、在getter操作返回不可变对象时,一定要先进行clone操作,切断(放开握紧的双手)与原对象引用直接的关系。
其中,需要对Article类对象进行改造,使其支持clone操作,详见代码4;另外,需要在构造函数和getter操作返回不可变对象前,先支持clone操作,详见代码5。
代码4:
//定义一篇文章
public class Article implements Cloneable{
//......
public Object clone(){
try{
return super.clone();
}catch(CloneNotSupportedException e){
throw new InternalError();
}
}
}
代码5:
final public class MutableStudent {
//......
//所有赋值操作均在构造函数中完成
public MutableStudent(String n,Article a) {
name = n;
article = (Article)a.clone();
}
//获取学生数据
public Article getArticle(){
return (Article)this.article.clone();
}
}
敲黑板
- 定义不可变对象类,当构造函数传入可变对象引用时、当getter函数返回可变对象引用时,容易掉坑。
- 在不可变对象类的构造函数中,如果传入值包括了可变对象,则clone先。
- 从不可变对象类的getter函数返回前,如果返回值为可变对象,则clone先。
转载自公众号:代码荣耀