java中深拷贝与浅拷贝详细解释,并结合BeanUtils.copyProperties方法分析

java 的深拷贝,浅拷贝与 BeanUtils.copyProperties 方法分析

浅拷贝和深拷贝是指在 Java 中对象复制时所涉及到的两种不同方式。
浅拷贝只是复制了基本类型值和对其他对象的引用,而没有复制引用的对象,即被复制对象和复制对象共用一份引用对象。如果原始对象中某个引用对象发生了变化,那么对应的复制对象也会同步变化。
深拷贝则是将对象本身及其引用对象都复制一份,即被复制对象和复制对象各自拥有一份独立的引用对象。即使原始对象中某个引用对象发生变化,复制对象对应的引用对象也不会变化,互相独立。
下面是一个浅拷贝和深拷贝的简单例子:

// 浅拷贝例子
class Person implements Cloneable {
    private String name;
    private int age;
    private List<String> hobbies;

    public Person(String name, int age, List<String> hobbies) {
        this.name = name;
        this.age = age;
        this.hobbies = hobbies;
    }

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

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

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public Person clone() throws CloneNotSupportedException {
        Person clone = (Person)super.clone();
        return clone;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", hobbies=" + hobbies + "}";
    }
}


// 浅拷贝示例代码
List<String> hobbies = new ArrayList<>();
hobbies.add("reading");

Person person1 = new Person("Alice", 20, hobbies);
System.out.println(person1);

// 浅拷贝 person2 和 person1 共享 hobbies 对象
Person person2 = person1.clone();
person2.setName("Bob");
person2.getHobbies().add("swimming");
System.out.println(person1);
System.out.println(person2);
// 深拷贝示例代码
List<String> hobbies = new ArrayList<>();
hobbies.add("reading");

Person person1 = new Person("Alice", 20, hobbies);
System.out.println(person1);
// 深拷贝 person2 和 person1 拥有不同的 hobbies 对象
Person person2 = person1.clone();
person2.setName("Bob");
person2.setHobbies(new ArrayList<>(person1.getHobbies()));
person2.getHobbies().add("swimming");
System.out.println(person1);
System.out.println(person2);
// 浅拷贝示例运行结果
Person{name='Alice', age=20, hobbies=[reading]}
Person{name='Bob', age=20, hobbies=[reading, swimming]}
Person{name='Bob', age=20, hobbies=[reading, swimming]}

// 深拷贝示例运行结果
Person{name='Alice', age=20, hobbies=[reading]}
Person{name='Bob', age=20, hobbies=[reading, swimming]}
Person{name='Alice', age=20, hobbies=[reading]}

以上代码中,我们定义了一个 Person 类,包含了一个 String 类型的 name 属性,一个 int 类型的 age 属性以及一个 List 类型的 hobbies 属性,作为该类的引用类型属性。
我们先进行了浅拷贝,创建了一个 Person 对象 person1 和它的一个副本 person2,然后修改了 person2 的 name 和 hobbies 属性,发现 person1 的 hobbies 属性也发生了变化。
接着我们进行了深拷贝,创建了一个 Person 对象 person1 和它的一个副本 person2,然后修改了 person2 的 name 和 hobbies 属性,发现 person1 的 hobbies 属性没有发生变化。
总之,浅拷贝和深拷贝是对象复制时常用的两种方式,各自适用于不同的场景。需要根据具体情况选择合适的复制方式。

深拷贝和浅拷贝是在程序设计中常见的对象复制方式,它们具有不同的应用场景。下面,我们将就深拷贝和浅拷贝在工作中具有的应用场景分别进行介绍,并给出相应的例子。

深拷贝的应用场景

深拷贝是将一个对象复制一份,并且与原对象的引用地址无关,即新对象不仅与原对象在属性值上相同,而且它们在内存空间中的地址不同。深拷贝通常适用于下列场景:

操作对象的属性是可变的并且需要独立于原始对象;
对象属性中包含了其他对象的引用,同时需要避免修改这些对象的引用;
在多线程环境中,需要避免多个线程同时访问同一个对象。

例如,在一些 Java 应用程序中,我们需要对包含有敏感数据的对象进行复制操作,以避免在传递给其他组件或模块的时候被滥用,这种情况下我们通常会使用深拷贝。另外,对于一些多线程的应用程序而言,由于对象的属性值是可变的,为了避免多个线程同时访问同一块内存区域,我们通常也会使用深拷贝来创建独立的副本。
下面是一个应用场景:

class User {
    private String name;
    private List<Account> accounts;
    // 省略构造函数和 getter/setter 方法
}

class Account {
    private String accountNo;
    private BigDecimal balance;
    // 省略构造函数和 getter/setter 方法
}

// 假设我们有一个User对象需要在多个地方进行使用,但是该对象中的账户信息不能对外部程序做出修改,否则会产生安全隐患
// 在这个场景下,我们可以使用深拷贝来创建一个新的User对象,这样便可以避免对原有对象进行修改
User user = new User("Tom", Arrays.asList(new Account("1001", new BigDecimal("100")), new Account("1002", new BigDecimal("50"))));
User copyUser = new User(user.getName(), user.getAccounts().stream().map(account -> new Account(account.getAccountNo(), account.getBalance())).collect(Collectors.toList()));

在上述代码中,我们通过 Java Stream API 的 collect 方法将原有 User 对象中的账户信息依次拷贝到新的 List 列表中,并创建一个新的 User 对象 copyUser。由于拷贝过程中创建了新的对象,因此 copyUser 对象中的账户信息与原有 User 对象之间没有任何关联。

浅拷贝的应用场景

浅拷贝是将一个对象复制一份,并将新对象的引用地址指向原对象,即新对象和原对象在某些属性值上相同,但它们在内存空间中的地址是相同的。当修改这些属性值的时候,会同时影响到复制的对象和原始对象。因此,浅拷贝通常适用于下列场景:

对象属性值都是不可变的,或者不需要被修改;
需要共享一部分已有的信息,同时也需要有独立的信息。

例如,在一些 Java 应用程序中,我们需要对包含一些只读属性的对象进行创建操作,如果创建时直接将这些只读属性进行赋值是比较麻烦的,此时我们通常会使用浅拷贝来进行对象的创建操作。例如:

class OnlyReadUser {
    private final String name;
    private final int age;
    private final Date birthday;
    // 省略构造函数和 getter 方法
}

// 在该类的实例化过程中,我们可以使用浅拷贝创建一个新的对象 onlyReadUser2
OnlyReadUser onlyReadUser1 = new OnlyReadUser("Tom", 20, new Date());
OnlyReadUser onlyReadUser2 = (OnlyReadUser)onlyReadUser1.clone();

在上述代码中,我们通过 Java 中的 clone 方法来创建了一个新的 OnlyReadUser 对象,它与原有的对象在 name、age 以及 birthday 属性的值上相同,并且新的对象和原始对象在内存空间中的地址是相同的。
需要注意的是,在浅拷贝的应用场景下,被复制的对象的属性值都应该是不可变的或者不需要被修改的,因为如果在复制的过程中对这些属性进行修改,那么会同时相应到复制的对象和原始对象。

BeanUtils.copyProperties 是 BeanUtils 类中提供的一个静态方法,用于将源对象的属性值拷贝到目标对象中。该方法同时支持浅拷贝和部分深拷贝操作。下面我们分别对 BeanUtils.copyProperties 方法的浅拷贝和部分深拷贝进行讲解。

BeanUtils.copyProperties 的浅拷贝

在 BeanUtils.copyProperties 方法的参数中,目标对象是一个 Java Bean 对象,属性值通过 get/set 方法进行获取/设置。将一个源对象的属性值拷贝到目标对象时,BeanUtils.copyProperties 方法仅仅是对源对象的属性值进行读取,将它们逐一赋值给目标对象的属性。因此,该方法可以被看作一种浅拷贝的操作。
具体而言,BeanUtils.copyProperties 方法中的属性值拷贝方式类似于以下的代码:

for each property in source object:
    if the target object has the same property:
        read the value of the property from source object
        write the value of the property to target object

需要注意的是,BeanUtils.copyProperties 方法中的属性值拷贝方式与 Java 中常见的浅拷贝方式有所不同,其方式更为灵活,可以处理目标对象和源对象属性值的转换、类型匹配等问题。
以下代码演示了 BeanUtils.copyProperties 方法的应用:

class User {
    private String name;
    private int age;
    // 省略构造函数和 getter/setter 方法
}

User user1 = new User();
user1.setName("Tom");
user1.setAge(20);

User user2 = new User();
BeanUtils.copyProperties(user2, user1);

System.out.println(user2.getName());  // Tom
System.out.println(user2.getAge());   // 20

在上述代码中,我们通过 BeanUtils.copyProperties 方法将 user1 对象的属性值拷贝到 user2 对象中,从结果可以看出,user1 和 user2 对象在属性值上是相等的。

BeanUtils.copyProperties 的部分深拷贝

BeanUtils.copyProperties 方法可以进行部分深拷贝,这也是它与其他浅拷贝方法的不同之处。部分深拷贝是指源对象属性中引用类型的属性值,在进行拷贝时是新创建了一个对象的副本而不是直接引用到源对象的属性值。因此,在使用 BeanUtils.copyProperties 方法拷贝引用类型的属性值时,该方法会进行一定程度的深拷贝。
以下代码演示了 BeanUtils.copyProperties 方法的部分深拷贝:

class Account {
    private BigDecimal balance;
    // 省略构造函数和 getter/setter 方法
}

class User {
    private String name;
    private int age;
    private Account account;
    // 省略构造函数和 getter/setter 方法
}

User user1 = new User();
user1.setName("Tom");
user1.setAge(20);
user1.setAccount(new Account(new BigDecimal(100)));

User user2 = new User();
BeanUtils.copyProperties(user2, user1);
user2.getAccount().setBalance(new BigDecimal(50));

System.out.println(user1.getAccount().getBalance());  // 100
System.out.println(user2.getAccount().getBalance());  // 50

在上述代码中,我们创建了一个 User 对象和一个 Account 对象,并将 Account 对象赋值给 User 对象的 account 属性。当使用 BeanUtils.copyProperties 方法将 user1 对象的属性值拷贝到 user2 对象中时,user2 对象中的 account 属性会被新创建一个 Account 对象进行赋值。因此,对于 user2 对象中的 account 属性的 balance 属性进行修改,不会影响到 user1 对象中 account 属性的 balance 属性。
综上,使用 BeanUtils.copyProperties 方法既可以进行浅拷贝,也可以进行部分深拷贝,具有一定的灵活性和适用范围。需要注意的是,在使用 BeanUtils.copyProperties 方法进行引用类型属性值的拷贝时,如果新对象中属性值是可变的,需要进行进一步的深拷贝操作以确保目标对象与原始对象之间没有任何关系。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值