Java基础提升10

2019-04-09  本文已影响0人  努力的土豆

写一个简短的开场白吧,最近在深入一些框架的学习,但是感觉有些吃力,甚至怀疑自己到底适不适合当程序员。总而言之,过程很痛苦,用别人造好的轮子感觉爽的飞起,一旦研究别人的轮子,瞬间被秒。既然想成为大神,想成为一个设计轮子的人,这些是必须经历的吧。在获得什么之前,总的付出些什么吧。一句话,死磕到底。

今天第一个关于Java的问题是,对象克隆。

对象克隆

绘图1.jpg
/**
 * @ClassName: Student
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-04-07 01:39
 * @Version: 1.0
 **/
public class Student implements Cloneable {

    private String name;
    private Integer age;
    private StringBuilder birthday;

    public Student(String name, Integer age, StringBuilder birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public StringBuilder getBirthday() {
        return birthday;
    }

    public void setBirthday(StringBuilder birthday) {
        this.birthday = birthday;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}

/**
 * @ClassName: CloneTest
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-04-07 01:41
 * @Version: 1.0
 **/
public class CloneTest {
    public static void main(String[] args) {
        Student S1 = new Student("A", 18, new Date());
        Student S2 = S1;
        System.out.println(S1);
        System.out.println(S2);
    }
}

首先,看完上述内容,可能第一反应就是,这是毛线的克隆。其实这的确不是克隆,这仅仅是一个简单的对象的引用,S1 和 S2 都指向同一个 Student 对象。因此,这两个实例对象中的任何一个修改 Student 中的内容,那么另一个的实例所引用的属性也会发生变化。因为,在内存中本就只有一个 new 出来的Student。

接下来,该克隆了。clone() 方法是Object中的一个protected方法,因此,想要调用 clone() 方法是有前提条件的(这是由protected决定的,因为clone() 方法比较复杂,在设计之初,就为了防止误用,因此加上protected),protected对本包的所有子类可见,很明显,上述中的 Student 类和 clone() 方法不在一同个包中,因此,想要使用它,必须实现 Cloneable 接口,并重写clone()方法。

Cloneable 接口的出现与接口的正常使用没有关系。该接口没有指定clone()方法,这个接口只是作为一个标记,指示类设计者了解克隆过程。(《Java核心技术 卷1》)

/**
 * @ClassName: CloneTest
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-04-07 01:41
 * @Version: 1.0
 **/
public class CloneTest {
    public static void main(String[] args) {
        Student student1 = new Student("A", 18, new StringBuilder("1995"));
        try {
            Student student2 = (Student) student1.clone();

            System.out.println(student1);
            System.out.println(student2);

            student2.setName("B");
            student2.setBirthday(student2.getBirthday().append("09"));

            System.out.println(student1);
            System.out.println(student2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
================
结果
================
Student{name='A', age=18, birthday=1995}
Student{name='A', age=18, birthday=1995}
Student{name='A', age=18, birthday=199509}
Student{name='B', age=18, birthday=199509}

问题来了,当执行 Student student2 = (Student) student1.clone(); 时,的确 new出新的 Student 对象实例(新开辟了一块区域),并且实例对象的初始状态和之前的保持一致。但是在更改 student2 时,发现连同 student1 的生日一起变了。

绘图2.jpg

图中 Date 类应该为 StringBuilder 类,这两个类都是可变的类。但是,为了方便操作使用 StringBuilder 类

上图就是产生问题的原因,因为 name 和 birthday 共用了一个存储区域(也就是浅拷贝),导致 student2 修改的同时,将 student1 的生日一起修改了。但是这里有个问题,为什么名字没有被一起修改?

  • 浅拷贝,如果原对象和克隆对象共享的子对象是不可变的,那么这种克隆就是安全的。
  • 本例中,共享子对象有String,StringBuilder,这两个类,其中 String 是被 final 修饰的类,也就是不可变的类,而 StringBuilder 类不是不可变的类,因此在克隆 birthday 时就出现了问题。
  • 总结,基本数据类型,不可变类,在浅拷贝时,不会出现任何问题。

原因已经知晓,该怎么解决呢,代码如下

/**
 * @ClassName: Student
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-04-07 01:39
 * @Version: 1.0
 **/
public class StudentA implements Cloneable {

    private String name;
    private Integer age;
    private StringBuilder birthday;;

    public StudentA(String name, Integer age, StringBuilder birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public StringBuilder getBirthday() {
        return birthday;
    }

    public void setBirthday(StringBuilder birthday) {
        this.birthday = birthday;
    }

    @Override
    protected StudentA clone() throws CloneNotSupportedException {
        StudentA studentA = (StudentA) super.clone();
        studentA.birthday = new StringBuilder(birthday);
        return studentA;
    }

    @Override
    public String toString() {
        return "StudentA{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", birthday=" + birthday +
                '}';
    }
}

注意,在重写 clone() 方法时,把可变类 StringBulider 也进行了一次新的创建,并且初始值仍为克隆前的值,这样就能解决浅拷贝的问题。

实验对比

/**
 * @ClassName: CloneTest
 * @Description: TODO
 * @Author: kevin
 * @Date: 2019-04-07 01:41
 * @Version: 1.0
 **/
public class CloneTest {
    public static void main(String[] args) {

        Student student1 = new Student("A", 18, new StringBuilder("1995"));
        try {
            Student student2 = (Student) student1.clone();

            System.out.println(student1);
            System.out.println(student2);

            student2.setName("B");
            student2.setBirthday(student2.getBirthday().append("09"));

            System.out.println(student1);
            System.out.println(student2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        System.out.println("--------------------");

        StudentA studentA1 = new StudentA("A", 18, new StringBuilder("1995"));
        try {
            StudentA studentA2 = studentA1.clone();

            System.out.println(studentA1);
            System.out.println(studentA2);

            studentA2.setName("B");
            studentA2.setBirthday(studentA2.getBirthday().append("09"));

            System.out.println(studentA1);
            System.out.println(studentA2);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}
================
结果
================
Student{name='A', age=18, birthday=1995}
Student{name='A', age=18, birthday=1995}
Student{name='A', age=18, birthday=199509}
Student{name='B', age=18, birthday=199509}
--------------------
StudentA{name='A', age=18, birthday=1995}
StudentA{name='A', age=18, birthday=1995}
StudentA{name='A', age=18, birthday=1995}
StudentA{name='B', age=18, birthday=199509}

参考资料

《Java核心技术 卷1》

上一篇下一篇

猜你喜欢

热点阅读