一  引用拷贝

创建一个指向对象的引用变量的拷贝。

Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);

blog.Teacher@355da254
blog.Teacher@355da254

由输出结果可以看出,它们的地址值是相同的,那么它们肯定是同一个对象。teacher和otherteacher的只是引用而已,他们都指向了一个相同的对象Teacher(“Taylor”,26)。 这就叫做引用拷贝。

 

二 对象拷贝

创建对象本身的一个副本。
Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);

blog.Teacher@355da254
blog.Teacher@4dc63996

结果分析:由输出结果可以看出,它们的地址是不同的,也就是说创建了新的对象, 而不是把原对象的地址赋给了一个新的引用变量,这就叫做对象拷贝。

注:深拷贝和浅拷贝都是对象拷贝

浅拷贝

对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,此为浅拷贝。

 

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。即对象的浅拷贝会对“主”对象进行拷贝,但不会复制主对象里面的对象。"里面的对象“会在原来的对象和它的副本之间共享。

 

简而言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象

深拷贝

对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容,此为深拷贝。

深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。

 

简而言之,深拷贝把要复制的对象所引用的对象都复制了一遍。

三 四种拷贝:

  • for()循环拷贝
  • clone()拷贝
  • System.arraycopy() 拷贝
  • Arrays.copyOf ()拷贝

1 两种类型的运用:

  • 基本类型 (深拷贝):以下例子中,改变拷贝数组元素,源数组不变,则深拷贝
  • 引用类型 (浅拷贝):以下例子中,改变拷贝数组元素,源数组不变,则浅拷贝

2 几种拷贝的介绍:

1.for()循环拷贝

通过循环赋值来将元数组里面的元素拷贝到目标数组

2.clone() 拷贝

protected Object clone()

                throws CloneNotSupportedException

clone的方法Object执行特定的克隆操作。 首先,如果此对象的类不实现接口Cloneable ,则抛出CloneNotSupportedException 。 请注意,所有数组都被认为是实现接口Cloneable ,并且数组类型T[]的clone方法的返回类型是T[] ,其中T是任何引用或原始类型。 否则,该方法将创建该对象的类的新实例,并将其所有字段初始化为完全符合该对象的相应字段的内容,就像通过赋值一样。 这些字段的内容本身不被克隆。 因此,该方法执行该对象的“浅拷贝”,而不是“深度拷贝”操作。

3.System.arraycopy 拷贝

System中提供了一个native静态方法arraycopy(),可以使用这个方法来实现数组之间的复制。

public static void arraycopy(Object src,int srcPos,Object dest,int destPos,int length)

参数

src - 源数组。

srcPos - 源数组中的起始位置。

dest - 目标数组。

destPos - 目的地数据中的起始位置。

length - 要复制的数组元素的数量

4.Arrays.copyOf 拷贝

public static <T> T[] copyOf(T[] original,int newLength)

参数类型

 

T - 数组中对象的类

参数

original - 要复制的数组

newLength - 要返回的副本的长度

 

3 当对象是“基本类型”(代码)

四种拷贝对应方式为“深拷贝”)

1 for()循环拷贝

import java.util.*;
public class SimpleCharcter {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7,8,9};
        int[] brray = new int[array.length];
        for(int i = 0;i < array.length;i++) {
            brray[i] = array[i];
        }
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
        brray[0] = 1000;
        System.out.println("=================");
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
    }
}
/*输出结果:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1000, 2, 3, 4, 5, 6, 7, 8, 9]         (深拷贝)
*/

 2 clone()拷贝

//clone()拷贝 
import java.util.*;
public class Clone {
    public static void main(String[] args) {
            int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9};
            int[] brray = array.clone();

            System.out.println(Arrays.toString(array));
            System.out.println(Arrays.toString(brray));
            brray[0] = 1000;
            System.out.println("=================");
            System.out.println(Arrays.toString(array));
            System.out.println(Arrays.toString(brray));

        }
    }
    
    /*输出结果:
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
    [1, 2, 3, 4, 5, 6, 7, 8, 9]
    [1000, 2, 3, 4, 5, 6, 7, 8, 9]         (深拷贝)
*/

System.arraycopy ()

//System.arraycopy()
import java.util.*;
public class System_Arraycopy {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7,8,9};
        int[] brray = new int[array.length];
        System.arraycopy(array,0,brray,0,array.length);
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
        brray[0] = 1000;
        System.out.println("=================");
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
    }
}

/*输出结果:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
=================
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1000, 2, 3, 4, 5, 6, 7, 8, 9]         (深拷贝)
*/

Arrays.copyOf ()

//Arrays.copyOf 
import java.util.*;
public class TestArray1 {
    public static void main(String[] args) {
        int[] array = {1,2,3,4,5,6,7,8,9};
        int[] brray = Arrays.copyOf(array,array.length);

        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
        brray[0] = 1000;
        System.out.println("=================");
        System.out.println(Arrays.toString(array));
        System.out.println(Arrays.toString(brray));
    }
 }
 
 /*输出结果:
 [1, 2, 3, 4, 5, 6, 7, 8, 9]
 [1, 2, 3, 4, 5, 6, 7, 8, 9]
 =================
 [1, 2, 3, 4, 5, 6, 7, 8, 9]
 [1000, 2, 3, 4, 5, 6, 7, 8, 9]         (深拷贝)
 */

4 当对象是“引用类型”(代码)

1 for()循环拷贝及打印结果代码

import java.util.*;
public class TestArray {
    private int val = 10;

    public void setVal(int val) {
        this.val = val;
    }
    public int getVal() {
        return this.val;
    }
    public static void print(TestArray[] t1, TestArray[] t2){
        for(int i = 0;i < t1.length;i++) {
            t2[i] = t1[i];//赋值的是引用,最后t1[i] t2[i] 都指向的是引用的地址,一个变都变,浅拷贝
        }
        for(int i = 0;i < t1.length;i++) {
            System.out.print(t1[i].getVal()+" ");
        }
        System.out.println();
        for(int i = 0;i < t2.length;i++) {
            System.out.print(t2[i].getVal()+" ");
        }
        System.out.println();
        System.out.println();
}

//for()
public static void main(String[] args) {
        TestArray[] t1 = new TestArray[4];//TestArray 已经是引用类型
        t1[0] = new TestArray();// t1[0] 链接的是对象的引用
        t1[1] = new TestArray();
        t1[2] = new TestArray();
        t1[3] = new TestArray();
        TestArray[] t2 = new TestArray[4];//t2[0]
        print(t1, t2);
        t2[0].setVal(100000);
        print(t1, t2);
        }
        
        /*输出结果:
        10 10 10 10
        10 10 10 10
       
        100000 10 10 10
        100000 10 10 10         (浅拷贝)
        */

clone()

//clone()
public static void main(String[] args) {
    TestArray[] t1 = new TestArray[4];
    t1[0] = new TestArray();
    t1[1] = new TestArray();
    t1[2] = new TestArray();
    t1[3] = new TestArray();
    TestArray[] t2 = t1.clone();//t2[0]
    print(t1, t2);
    t2[0].setVal(99999);
    print(t1, t2);
    }
    
    /*输出结果:
    10 10 10 10 
    10 10 10 10 

    99999 10 10 10 
    99999 10 10 10          (浅拷贝)
*/

System.arraycopy ()

//System.arraycopy() 
public static void main(String[] args) {
   TestArray[] t1 = new TestArray[4];
   t1[0] = new TestArray();
   t1[1] = new TestArray();
   t1[2] = new TestArray();
   t1[3] = new TestArray();
   TestArray[] t2 = new TestArray[4];//t2[0]
   System.arraycopy(t1,0,t2,0,t1.length);
   print(t1, t2);
   t2[0].setVal(88888);
   print(t1, t2);
   }
   
   /*输出结果:
   10 10 10 10 
   10 10 10 10 

   88888 10 10 10 
   88888 10 10 10         (浅拷贝)         
   */

4 Arrays.copyOf ()

//Arrays.copyOf ()
public static void main(String[] args) {
    TestArray[] t1 = new TestArray[4];
    t1[0] = new TestArray();
    t1[1] = new TestArray();
    t1[2] = new TestArray();
    t1[3] = new TestArray();
    TestArray[] t2 = Arrays.copyOf(t1,t1.length);
    print(t1, t2);
    t2[0].setVal(666666);
    print(t1, t2);
    
    /*输出结果:
    10 10 10 10 
    10 10 10 10 

    666666 10 10 10 
    666666 10 10 10         (浅拷贝) 
*/

5 Java对象-深拷贝两种方式

实现对象克隆两种方式:

 

1). 实现Cloneable接口并重写Object类中的clone()方法;

2). 实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深度克隆。

1继承Cloneable重写clone方法

public class Attachment  implements Serializable{

    private String name;

    public String getName() {
        return name;
    }

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


public class YuelyLog implements Serializable,Cloneable {
    private Attachment attachment;
    private String name;
    private String date;

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

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public String getName() {
        return name;
    }

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

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }
}

public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
        Attachment attachment = new Attachment();
        attachment.setName("wj");
        YuelyLog yuelyLog = new YuelyLog();
        yuelyLog.setAttachment(attachment);
        yuelyLog.setDate(new Date().toString());
        yuelyLog.setName("dn");

        YuelyLog clone = yuelyLog.clone();
        clone.getAttachment().setName("clone");
        System.out.println("yelyLog:name="+(yuelyLog.getAttachment().getName()));
    }

2 下面通过序列化实现深拷贝

public class YuelyLog implements Serializable,Cloneable {
    private Attachment attachment;
    private String name;
    private String date;

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

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public String getName() {
        return name;
    }

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

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    /**
     * 使用序列化技术实现深拷贝
     * @return
     */
    public YuelyLog deepClone() throws IOException,ClassNotFoundException{
        //将对象写入流中
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
        objectOutputStream.writeObject(this);
        //从流中取出
        ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        return (YuelyLog)objectInputStream.readObject();

    }
}

深拷贝浅拷贝示例

假如说你想复制一个简单变量。很简单:

int apples = 5;
int pears = apples;

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

class Student {
    private int number;
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
    
}
public class Test {
    
    public static void main(String args[]) {
        
        Student stu1 = new Student();
        stu1.setNumber(12345);
        Student stu2 = stu1;
        
        System.out.println("学生1:" + stu1.getNumber());
        System.out.println("学生2:" + stu2.getNumber());
    }
}

打印结果:

学生1:12345
学生2:12345

这里我们自定义了一个学生类,该类只有一个number字段。

我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:

stu2.setNumber(54321);

System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());

打印结果:

学生1:54321
学生2:54321

这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,这样,stu1和stu2指向内存堆中同一个对象。

那么,怎样才能达到复制一个对象呢?

是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

该方法的签名是:

protected native Object clone() throws CloneNotSupportedException;

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

要想对一个对象进行复制,就需要对clone方法覆盖。

一般步骤是(浅复制):

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方***抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)

2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)


下面对上面那个方法进行改造:

一般步骤是(浅复制):

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方***抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)

2. 覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象,(native为本地方法)


下面对上面那个方法进行改造:

打印结果:

学生1:12345
学生2:12345
学生1:12345
学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

System.out.println(stu1 == stu2); // false

上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):

我们在学生类里再加一个Address类。

class Address  {
    private String add;
 
    public String getAdd() {
        return add;
    }
 
    public void setAdd(String add) {
        this.add = add;
    }
    
}
 
class Student implements Cloneable{
    private int number;
 
    private Address addr;
    
    public Address getAddr() {
        return addr;
    }
 
    public void setAddr(Address addr) {
        this.addr = addr;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
    
    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return stu;
    }
}
public class Test {
    
    public static void main(String args[]) {
        
        Address addr = new Address();
        addr.setAdd("杭州市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        
        Student stu2 = (Student)stu1.clone();
        
        System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    }
}

打印结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

addr.setAdd("西湖区");
        
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());

打印结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区

这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

package abc;
 
class Address implements Cloneable {
    private String add;
 
    public String getAdd() {
        return add;
    }
 
    public void setAdd(String add) {
        this.add = add;
    }
    
    @Override
    public Object clone() {
        Address addr = null;
        try{
            addr = (Address)super.clone();
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return addr;
    }
}
 
class Student implements Cloneable{
    private int number;
 
    private Address addr;
    
    public Address getAddr() {
        return addr;
    }
 
    public void setAddr(Address addr) {
        this.addr = addr;
    }
 
    public int getNumber() {
        return number;
    }
 
    public void setNumber(int number) {
        this.number = number;
    }
    
    @Override
    public Object clone() {
        Student stu = null;
        try{
            stu = (Student)super.clone();    //浅复制
        }catch(CloneNotSupportedException e) {
            e.printStackTrace();
        }
        stu.addr = (Address)addr.clone();    //深度复制
        return stu;
    }
}
public class Test {
    
    public static void main(String args[]) {
        
        Address addr = new Address();
        addr.setAdd("杭州市");
        Student stu1 = new Student();
        stu1.setNumber(123);
        stu1.setAddr(addr);
        
        Student stu2 = (Student)stu1.clone();
        
        System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
        
        addr.setAdd("西湖区");
        
        System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    }
}

打印结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市

这样结果就符合我们的想法了。

最后我们可以看看API里其中一个实现了clone方法的类:

java.util.Date:

这样结果就符合我们的想法了。

最后我们可以看看API里其中一个实现了clone方法的类:

java.util.Date:

该类其实也属于深度复制。