NO.18 放开握紧的双手将得到全世界:不可变对象进阶 | Java敲黑板系列

开场白

老铁 :所谓“不可变对象”就是当对象创建以后其成员变量不再能发生变化。今天我们以昨天所提的问题——“为什么在特定场景下需要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();
    }
}

敲黑板

  1. 定义不可变对象类,当构造函数传入可变对象引用时、当getter函数返回可变对象引用时,容易掉坑。
  2. 在不可变对象类的构造函数中,如果传入值包括了可变对象,则clone先。
  3. 从不可变对象类的getter函数返回前,如果返回值为可变对象,则clone先。

转载自公众号:代码荣耀
图1

猜你喜欢

转载自blog.csdn.net/maijia0754/article/details/80584093