Java基础篇--拷贝

目录

前言

Java拷贝的概念

***:拷贝有什么作用,日常哪些场合需要用到拷贝?

对象拷贝

***:实现对象拷贝的三种方式

***:cloneable接口有什么作用,是否可以不实现该接口?

***:浅拷贝和深拷贝是什么?有什么区别?

***:如何实现深拷贝?

***:使用工具类BeanUtils(org.apache.commons.beanutils)实现拷贝


前言

带着问题学java系列博文之java基础篇。从问题出发,学习java知识。


Java拷贝的概念

拷贝或者叫复制,是指复制一个目标A,生成一个新的目标B。根据拷贝目标的不同,java分为引用拷贝和对象拷贝。而Java实现对象拷贝主要有三种方式,根据实现方式的不同,又分为浅拷贝和深拷贝。

***:拷贝有什么作用,日常哪些场合需要用到拷贝?

引用拷贝:比如定义一个变量,指向的是一个对象;此时我们需要再定义一个同样指向该对象的拷贝,就可以用到引用拷贝。如下例:

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("sanzhang",23);
        Person p2 = p1;
        p2.setAge(25);
        System.out.println(p2.getAge());
        System.out.println(p1.getAge());
    }
}

如代码,直接将p1赋值给了p2,这里就是引用拷贝,此时p1和p2都是指向person对象实例的引用,不管是通过p2还是p1去修改person对象的值,都是修改堆内存空间中的同一对象,所以可以看到打印出来的age都是25.

对象拷贝:可以看到引用拷贝仅仅只是复制了一份引用,通过任一引用的修改都会导致对象的改变。而当我们需要完整复制一个对象,两个对象是完全独立的,此时就需要用到对象拷贝。

public class Test {
    public static void main(String[] args) {
        Person p1 = new Person("sanzhang",23);
        Person p2 = p1.clone();
        p2.setAge(25);
        System.out.println(p2.getAge());
        System.out.println(p1.getAge());
    }
}

如代码,p2是p1.clone()产生的一个新对象,p2和p1在堆内存中拥有各自独有的地址空间,通过p2修改age,仅能修改p2对象的值,而p1对象的值不受影响,所以打印出了25和23.

 

对象拷贝

***:实现对象拷贝的三种方式

  • new对象组合set方法赋值的方式拷贝对象
public class Test {
    public static void main(String[] args) {
        Person p1 = new Person(23,"lisi",new Address("hefei"));
        Person p2 = new Person(p1.getAge(),p1.getName(),p1.getAddress());
        p2.setAge(25);
        System.out.println(p2.getAge());
        System.out.println(p1.getAge());
    }
}

如上代码,通过new关键字创建一个新的对象,然后将目标对象的值通过set的方式赋值给新的对象,如此组合,实现了对象的拷贝。这里修改p2的age不会影响到p1的age,所以打印出了25和23.

  • 利用object的clone方法实现对象拷贝
public class Test {

    public static void main(String[] args) {
        Person p1 = new Person(23,"lisi",new Address("hefei"));
        Person p2 = p1.clone();
        p2.setAge(25);
        System.out.println(p2.getAge());
        System.out.println(p1.getAge());
    }

    static class Person implements Cloneable{
        int age;
        String name;
        Address address;

        @Override
        public Person clone(){
            try {
                return (Person) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }

        public Person() {
        }

        public Person(int age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        public int getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }

    static class Address{
        String addr;

        public Address() {
        }

        public Address(String addr) {
            this.addr = addr;
        }

        public String getAddr() {
            return addr;
        }

        public void setAddr(String addr) {
            this.addr = addr;
        }
    }
}

由于object的clone方法是protected修饰的,外部调用受限制;所以这里person类实现cloneable接口,重写object的clone方法。

  • 利用序列化实现对象拷贝
    //---------------------序列化流-----------------------------
    private static void  testObjectOS(){
        try {
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Object.txt"));
            Person person = new Person("张三",19);
            oos.writeObject(person);
            oos.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void testObjectIS(){
        try {
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Object.txt"));
            Person person = (Person) ois.readObject();
            System.out.println(person.toString());
            ois.close();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

如上代码,使用序列化输出流将对象保存到文件中,然后再通过序列化输入流读取文件内容,生成一个新的对象,实现对象的拷贝。

***:cloneable接口有什么作用,是否可以不实现该接口?

实现对象拷贝的第二种方式是采用clone方法,要求对象类实现cloneable接口,然后重写object类的clone方法。我们看下cloneable接口的源码:

 * @see     java.lang.CloneNotSupportedException
 * @see     java.lang.Object#clone()
 * @since   JDK1.0
 */
public interface Cloneable {
}

可以看到,源码中Cloneable就是一个空接口,没有任何抽象方法。那为什么还要在对象类上去实现该接口呢?再看一下Object类的clone方法源码:

    protected native Object clone() throws CloneNotSupportedException;

clone方法是一个native方法,它的实现细节其实是先检查该类型是否支持拷贝,不支持则抛出CloneNotSupportException异常,支持则执行对象拷贝逻辑。这里的检查其实就是看该类型是否实现了Cloneable接口,所以Cloneable接口其实只是一个标识接口,一个类型实现了该接口,相当于告诉jvm,这个类型支持clone。通常再重写clone方法(主要是改修饰符为public,返回类型为当前类),满足外部调用,实现对象拷贝。如果不实现该接口,即使重写了clone方法,在调用clone方法的时候,也会抛出CloneNotSupportException异常。

***:浅拷贝和深拷贝是什么?有什么区别?

在分析之前,我们先看下第二种方式(clone方法)实现对象拷贝的一个现象:

p2 = p1.clone()实现了对象拷贝,生成一个新的对象p2;此时修改p2的age不会影响到p1.但是当修改p2的address

(p2.getAddress().setAddr("anqing"))时,会发现p1的address内容也被改成了“anqing”。我们不是实现了对象拷贝吗,p2是一个新的对象呀,为什么修改p2的值,p1也会被修改呢?

这是因为address属性是一个自定义的类型,一个引用类型,我们调用clone方法只是拷贝基本类型,而引用类型是直接拷贝引用;所以这里address属性只是引用拷贝,p2和p1都是指向同一个address对象,通过p2修改address的值,直接修改了address对象,之后p1去获取对象的值时,当然获取到的是修改后的值。这种仅复制对象本身以及其值类型的成员变量,而引用类型的成员变量是引用拷贝的对象拷贝就是浅拷贝。可以实现对象的真正复制,包括对象的引用类型成员变量,以及引用类型成员变量里面的引用类型变量的对象拷贝就是深拷贝,深拷贝生成的新对象与原来的对象不共享任何内存空间,修改新对象的任何属性都不会影响原来的对象。

***:如何实现深拷贝?

首先序列化的方式实现对象拷贝本身就是深拷贝;clone方式实现对象拷贝,可以通过改写代码实现深拷贝;set方式对于引用类型其实就是引用赋值,所以无法修改成深拷贝。

public class Test {

    public static void main(String[] args) {
        Person p1 = new Person(23,"lisi",new Address("hefei"));
        Person p2 = p1.clone();
        p2.getAddress().setAddr("anqing");
        System.out.println(p2.getAddress().getAddr());
        System.out.println(p1.getAddress().getAddr());
    }

    static class Person implements Cloneable{
        int age;
        String name;
        Address address;

        @Override
        public Person clone(){
            Person person = null;
            try {
                person = (Person) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            person.address = address.clone();
            return person;
        }

        public Person() {

        }

        public Person(int age, String name, Address address) {
            this.age = age;
            this.name = name;
            this.address = address;
        }

        public int getAge() {
            return age;
        }

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

        public String getName() {
            return name;
        }

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

        public Address getAddress() {
            return address;
        }

        public void setAddress(Address address) {
            this.address = address;
        }
    }

    static class Address implements Cloneable{
        String addr;

        @Override
        public Address clone() {
            try {
                return (Address) super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }

        public Address() {
        }

        public Address(String addr) {
            this.addr = addr;
        }

        public String getAddr() {
            return addr;
        }

        public void setAddr(String addr) {
            this.addr = addr;
        }


    }
}

如上代码,修改clone方法,将浅拷贝改成深拷贝,主要步骤如下:

  1. 所有的引用类型属性自身都实现cloneable接口,重写clone方法(引用类型属性自身还含有引用类型属性,则继续往下实现);
  2. 重写对象的clone方法,调用super.clone()实现自身的拷贝,然后再对每个引用类型属性分别调用引用类型的clone方法实现引用类型属性的拷贝;

***:使用工具类BeanUtils(org.apache.commons.beanutils)实现拷贝

    public static void main(String[] args) throws Exception{
        Person p1 = new Person(23,"lisi",new Address("hefei"));
        Person p2 = new Person();
        BeanUtils.copyProperties(p2,p1);
        System.out.println(p1.toString());
        System.out.println(p2.toString());
    }

注意使用工具类仅可拷贝基本类型变量,引用类型变量全部都是null。


以上系个人理解,如果存在错误,欢迎大家指正。原创不易,转载请注明出处!

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值