美文网首页
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

    写一个简短的开场白吧,最近在深入一些框架的学习,但是感觉有些吃力,甚至怀疑自己到底适不适合当程序员。总而言之,过程...

  • java接口及其新特性——Java基础回炉(三)

    前言 本文是Java基础回炉文集的第三篇,关于文集可通过《Java基础回炉和提升暨文集开篇》了解。 今天我们将从接...

  • java基础提升篇

    此篇幅是个人整理的一点心得,针对的广大的普通大众人员,还有此篇文章的出发点是从先就业后择业的角度去分析java程序...

  • Java基础提升2

    今天是第二天,感觉有很多东西需要加强,每次看到那些技术大神洋洋洒洒的博客,内心除了崇拜,更多的是鄙视自己,感慨差距...

  • Java基础提升1

    从今天开始进行基本功的提升,(本人很菜,写的可能不好,欢迎大家提出意见,此外,引用别人的东西,我会全部标识出),我...

  • Java基础提升5

    今天的内容是关于Java字符串的。字符串,每一个Java开发人员都会用到,但是真的对它熟悉吗? Java中的Str...

  • Java基础提升3

    昨天太困了,就没有写东西,因此今天会补两篇。 今天的内容将围绕Java的变量,目的是对变量有更深刻的认识。 Jav...

  • Java基础提升4

    本文将阐述关于Java语言中变量是如何存储的。 Java中数据的存储位置 寄存器 最快的存储区,位于处理器的内部,...

  • Java基础提升8

    大概有两天没有更新了,是自己懒了点,所以今天更新一篇,把看过的东西记录下来。今天的内容是关于封装的概念。之前的文章...

  • Java基础提升7

    今天的内容是关于String,StringBuffer,StringBuilder的一些知识。 基本概念 Stri...

网友评论

      本文标题:Java基础提升10

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