浅拷贝介绍

拷贝出当前对象的一个副本,这个新对象和当前对象处于不同的堆内存中,两个对象的基本数据类型的值完全一样,但是引用数据类型还是指向同一个对象的。

深拷贝介绍

深拷贝拷贝出当前对象的一个副本,这个新对象和当前对象处于不同的堆内存中,两个对象的基本数据类型的值完全一样,引用数据类型指向的对象也拷贝出了一份一模一样的副本。

可以看到我们如果在拷贝对象时不清楚深拷贝和浅拷贝的话很容易出现一些未知的异常,接下来我们用代码来演示一下

public class User implements Cloneable {

    private String name;

    private Address address;

    public String getName() {
        return name;
    }

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

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

    public Address getAddress() {
        return address;
    }

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

    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
public class CopyDemo {

    public static void main(String[] args) throws CloneNotSupportedException {
        Address address = new Address();
        address.setAddress("杭州市");
        User user = new User();
        user.setName("jack");
        user.setAddress(address);
        User newUser = (User) user.clone();
        System.out.println("拷贝后对象的address:"+newUser.getAddress().getAddress());
        user.getAddress().setAddress("湖州市");
        System.out.println("修改了拷贝前对象的address后,拷贝后对象的address:"+newUser.getAddress().getAddress());
    }

}

输出值:
拷贝后对象的address:杭州市
修改了拷贝前对象的address后,拷贝后对象的address:湖州市

可以看到这里我们修改了拷贝前对象的Address,但是拷贝后对象Address也被改变了,由此可得拷贝前和拷贝后的Address执行的是堆内存中的同一个地址
科普:这里浅拷贝使用的是Jdk提供的Cloneable,使用时需要拷贝的对象实现Cloneable接口并且重写Object的clone()方法,Cloneable没有任何定义,只是一种规范。

深拷贝最容易想到的是使用Serializable接口序列化

public class User implements Cloneable,Serializable{

    private String name;

    private Address address;

    public String getName() {
        return name;
    }

    public User deepCopy() throws IOException, ClassNotFoundException {
        // 写入字节流
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream obs = new ObjectOutputStream(out);
        obs.writeObject(this);
        obs.close();

        // 分配内存,写入原始对象,生成新对象
        ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(ios);
        // 返回生成的新对象
        User newUser = (User) ois.readObject();
        ois.close();
        return newUser;
    }

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

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

    public Address getAddress() {
        return address;
    }

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

    private String address;

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
public class CopyDemo {

    public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
        Address address = new Address();
        address.setAddress("杭州市");
        User user = new User();
        user.setName("jack");
        user.setAddress(address);
        // 浅拷贝
        User newUser = (User) user.clone();
        // 深拷贝
        User newUser2 = user.deepCopy();
        System.out.println("拷贝后对象的address:"+newUser.getAddress().getAddress());
        user.getAddress().setAddress("湖州市");
        System.out.println("修改了拷贝前对象的address后,拷贝后对象的address:"+newUser.getAddress().getAddress());
        System.out.println("修改了拷贝前对象的address后,深拷贝后对象的address:"+newUser2.getAddress().getAddress());

    }
}

输出:
拷贝后对象的address:杭州市
修改了拷贝前对象的address后,拷贝后对象的address:湖州市
修改了拷贝前对象的address后,深拷贝后对象的address:杭州市

使用序列化对象,然后在反序列化即可得到一个全新的对象,但是使用起来性能比较低,所以不推荐使用,作为了解即可。

拷贝的工具类推荐

1、commons-beanutils的介绍:commons-beanutils是Apache组织下的一个基础的开源库,其主要目的是利用反射机制对JavaBean的属性进行处理。

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
        Address address = new Address();
        address.setAddress("杭州市");
        User user = new User();
        user.setName("jack");
        user.setAddress(address);
        
        User newUser = new User();
        // 把user对象内容拷贝到newUser
        BeanUtils.copyProperties(newUser,user);
        user.getAddress().setAddress("湖州市");
        System.out.println("修改了拷贝前对象的address后,拷贝后对象的address:"+newUser.getAddress().getAddress());

    }

输出:修改了拷贝前对象的address后,拷贝后对象的address:湖州市
可见BeanUtils.copyProperties是浅拷贝

2、Orika(极力推荐):Orika底层采用了javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件,因此在速度上比使用反射进行赋值会快很多,下面详细介绍Orika的使用方法

定义工具类:

public class OrikaMapperUtils {

    private static final MapperFacade mapperFacade;

    static {
        MapperFactory mapperFactory = new DefaultMapperFactory.Builder().useAutoMapping(true).mapNulls(true).build();
        mapperFacade = mapperFactory.getMapperFacade();
    }

    public static <S, D> void map(S from, D to) {
        mapperFacade.map(from, to);
    }

    public static <S, D> D map(S from, Class<D> clazz) {
        return mapperFacade.map(from, clazz);
    }

    public static MapperFacade getMapperFacade() {
        return mapperFacade;
    }

    public static <S, D> List<D> mapAsList(Iterable<S> source, Class<D> destinationClass) {
        return mapperFacade.mapAsList(source, destinationClass);
    }

}
public class CopyUtilsDemo {

    public static void main(String[] args) {
        Address address = new Address();
        address.setAddress("杭州市");
        User user = new User();
        user.setName("jack");
        user.setAddress(address);
        List<User> users = new ArrayList<>();
        users.add(user);
        // 浅拷贝
        User newUser = new User();
        // 1、把user对象内容拷贝到newUser
        OrikaMapperUtils.map(user,newUser);
        // 2、把user对象内容拷贝到newUser
        User newUser2 = OrikaMapperUtils.map(user,User.class);
        // 3、深拷贝集合
        List<User> users2 = OrikaMapperUtils.mapAsList(users,User.class);

        user.getAddress().setAddress("湖州市");
        System.out.println("newUser的address:"+newUser.getAddress().getAddress());
        System.out.println("newUser2的address:"+newUser.getAddress().getAddress());
        System.out.println("users2的address:"+users2.get(0).getAddress().getAddress());
    }
}

由上可见Orika包含了平时对拷贝的常用功能
1、对象到对象的的深拷贝
2、对象到Class类的深拷贝,即不同类型对象也可以拷贝
3、集合的深拷贝

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐