编程的艺术 ----Ctrl + C , Ctrl + V 在代码中的体现
前言 : 你是否在写代码的时候遇见过一个对象要new多个一样的或者改动不大的 复制粘贴使得代码很难看不雅观而身有体会呢?
那么这篇文章绝对适合你 它还存在一个设计模式–原型模式
浅复制
先介绍一下浅复制
浅复制是指当对象的字段值被复制时,字段引用的对象不会被复制 只会得到其引用 例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅复制,那么两个对象将引用同一个字符串·
那么浅复制怎么实现呢?
其实在Object类中有一个方法 Object clone()
这个方法就可以完成浅复制 只需要子类重写这个方法 并且实现了Cloneable接口即可
例如一个Teacher类 和 Student类
public class Teacher {
private String name;
public Teacher(String name) {
this.name = name;
}
public Teacher() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
public class Student implements Cloneable {
private int age;
private Teacher teacher;
public Student(int age, Teacher teacher) {
this.age = age;
this.teacher = teacher;
}
public Student() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", teacher=" + teacher +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object object = super.clone();
return object;
}
}
可能有人就想问了 clone方法既然是Object 的 那么 你实现了Cloneable接口难道不用重写其方法
那么让我们看一下Cloneable的源码
public interface Cloneable {
}
对的 这个接口的源码就是这个样子 其实他只是一个标识符
你实现了这个接口表示你支持复制 不实现代表不复制
接下来看一下测试类
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.clone();
System.out.println(stu1);
System.out.println(stu2);
}
}
这样做的好处是什么呢?
比如一个类 有很多成员变量 你再给他set值的时候会set很多行
然后你又需要很多个一样的对象 这个时候你是不是就想到了复制粘贴
导致 代码几百行 而且都是重复的 很难看
用这种方法是不是就不用那么麻烦了
还有一个好处就是不用去初始化 万一你的构造器很复杂 很消耗时间
如果用我们的方法就
但是! 这里却有个缺陷 还记得我们之前说的如果对象类型只会得到其引用
接下来让我们看看这个事情 修改其测试类代码
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.clone();
System.out.println(stu1);
System.out.println(stu2);
stu2.getTeacher().setName("唐嫣");
System.out.println(stu1);
System.out.println(stu2);
}
}
然后看一下输出结果
我们只是修改了stu2的老师的名字 stu1的也跟着变了
所以说浅复制只是获得了其引用可能有人还是不相信 看一下这个测试代码
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.clone();
System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
}
}
输出结果
两个对象是同一个 这就造成了很多复制上的弊端
让我们来看一下深复制
深复制
深复制会赋值所有的属性,并复制属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深赋值贝。深复制相比于浅复制速度较慢并且花销较大。
我这里讲几种深复制的方法
1.构造深复制法
@Override
protected Object clone() throws CloneNotSupportedException {
Teacher teacher = new Teacher(this.teacher.getName());
Student student = new Student(this.age, teacher);
return student;
}
修改复制的方法可能比较笨
然后再看测试类
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.clone();
System.out.println(stu1);
System.out.println(stu2);
stu2.getTeacher().setName("唐嫣");
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
}
}
这次就不是同一个对象了.还有一种方法就是实现序列化接口
序列化接口: Serializable接口
序列化: 就是把一个对象的状态全部用字节码保存起来 存到硬盘 数据库等等…
反序列化:就是序列化的逆过程 把你存起来的字节码恢复成对象 其底层依赖反射
在Teacher 类 和Student类中实现Serializable接口
并尝试着把Student对象写到文件里
在Student里实现一个方法
public void write() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
objectOutputStream.writeObject(this);
}
当然文件路径你可以自定义 也可以不写到文件里 随便你
然后测试类
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
Student student = new Student(18, new Teacher("刘亦菲"));
student.write();
}
}
然后再用同样的方法读出来
public static Object read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
return objectInputStream.readObject();
}
修改测试类
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(18,new Teacher("刘亦菲"));
System.out.println(student);
student.write();
Student student1 = read();
System.out.println(student1);
}
public static Student read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
return (Student) objectInputStream.readObject();
}
}
输出结果 让我们看一下两个对象是不是同一个
修改测试类代码
import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
public class Demo2 {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Student student = new Student(18,new Teacher("刘亦菲"));
System.out.println(student);
student.write();
Student student1 = read();
System.out.println(student1);
System.out.println(student.equals(student1));
System.out.println(student1.getTeacher().equals(student.getTeacher()));
}
public static Student read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
return (Student) objectInputStream.readObject();
}
}
这里其实就是两个不同的对象了
这里还需要强调一个字段
serialVersionUID
serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高哦啊serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:
如果你需要在两个不同的进程一个写 一个读的话就需要这个东西的显示定义 也就是你自己加一个字段
于是修改了Student类的定义
import java.io.*;
public class Student implements Cloneable,Serializable {
private int age;
private Teacher teacher;
private static final long serialVersionUid = 1L;
public Student(int age, Teacher teacher) {
this.age = age;
this.teacher = teacher;
}
public Student() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", teacher=" + teacher +
'}';
}
//浅复制
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void write() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
objectOutputStream.writeObject(this);
}
}
代码的第六行 定义了 private static final long serialVersionUid = 1L;
好了 然后怎么进行深复制你们应该明白了 其实最好的方法不要写到硬盘中 而是写到流中
比如
//深复制
public Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
学生类代码
import java.io.*;
public class Student implements Cloneable,Serializable {
private int age;
private Teacher teacher;
private static final long serialVersionUid = 1L;
public Student(int age, Teacher teacher) {
this.age = age;
this.teacher = teacher;
}
public Student() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", teacher=" + teacher +
'}';
}
//浅复制
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
//深复制
public Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}
我们修改测试类
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.deepClone();
System.out.println(stu1);
System.out.println(stu2);
stu2.getTeacher().setName("唐嫣");
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
}
}
输出结果
好了 贴一下详细的代码 首先 Teacher类
import java.io.Serializable;
public class Teacher implements Serializable {
private String name;
public Teacher(String name) {
this.name = name;
}
public Teacher() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
'}';
}
}
然后Student类
import java.io.*;
public class Student implements Cloneable,Serializable {
private int age;
private Teacher teacher;
private static final long serialVersionUid = 1L;
public Student(int age, Teacher teacher) {
this.age = age;
this.teacher = teacher;
}
public Student() {
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", teacher=" + teacher +
'}';
}
//浅复制
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void write() throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("D://12345.txt"));
objectOutputStream.writeObject(this);
}
public Object read() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("D://12345.txt"));
return objectInputStream.readObject();
}
//深复制
public Object deepClone() throws IOException, ClassNotFoundException {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(this);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
return objectInputStream.readObject();
}
}
测试类
import java.io.IOException;
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
Teacher teacher = new Teacher("刘亦菲");
Student stu1 = new Student(18,teacher);
Student stu2 = (Student) stu1.deepClone();
System.out.println(stu1);
System.out.println(stu2);
stu2.getTeacher().setName("唐嫣");
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu1.getTeacher().equals(stu2.getTeacher()));
}
}