美文网首页
Java基础提升10

Java基础提升10

作者: 努力的土豆 | 来源:发表于2019-04-09 01:20 被阅读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》

    相关文章

      网友评论

          本文标题:Java基础提升10

          本文链接:https://www.haomeiwen.com/subject/yinmiqtx.html