更多精彩
先案例后讲解,这里是代码教父,今天讲解JAVA中的clone
目录
- 什么是clone
- 如何实现clone
- 浅克隆
- 深克隆
- 小结
- 什么时候使用clone
- clone 相关类库的实现分析
什么是clone
在Java中,克隆(Clone)指的是创建一个现有对象的副本。这个副本将是一个全新的对象,但是它的属性值与原始对象相同。
如何实现clone
Java提供了clone()
方法来实现对象的克隆。要使用clone()
方法进行克隆操作,需要满足以下条件:
- 对象的类必须实现
Cloneable
接口,该接口是一个标记接口,表示该类可以被克隆。 clone()
方法必须在类中被重写为public
访问修饰符,并且返回类型应该与类本身兼容。
默认情况下,clone()
方法执行的是浅拷贝(Shallow Copy),它复制对象的字段值。如果对象中包含其他对象的引用,那么克隆对象和原始对象将共享这些引用,因此修改其中一个对象的引用将影响到另一个对象。
如果需要实现深拷贝(Deep Copy),也就是复制对象及其引用的所有对象,就需要在clone()
方法中手动实现对其他对象的克隆操作。
下面请看例子:
浅克隆
public class CloneClassExample implements Cloneable{
/**
* 字符串
*/
private String str;
/**
* Map 对象
*/
private Map<String, String> map;
public CloneClassExample(String str, Map<String, String> map) {
this.str = str;
this.map = map;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "CloneClassExample{" +
"str='" + str + '\'' +
", map=" + map +
'}';
}
}
class MainClass{
public static void main(String[] args) {
HashMap<String, String> map = Maps.newHashMap();
map.put("key1","value1");
map.put("key2","value1");
CloneClassExample cloneClassExample = new CloneClassExample("A", map );
try {
CloneClassExample clone = cloneClassExample.clone();
System.out.println("原来对象 \t" + cloneClassExample);
System.out.println("clone对象 \t" + clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
输出结果
原来对象 CloneClassExample{str='A', map={key1=value1, key2=value1}}
clone对象 CloneClassExample{str='A', map={key1=value1, key2=value1}}
-
浅克隆存在问题
-
如果对象中包含其他对象的引用,那么克隆对象和原始对象将共享这些引用,因此修改其中一个对象的引用将影响到另一个对象
也就是说,如果我们改变了
cloneClassExample
中map
的值,相对应的clone
中的map
值也会变,现在来演示下这种问题
-
public static void main(String[] args) {
HashMap<String, String> map = Maps.newHashMap();
map.put("key1","value1");
map.put("key2","value1");
CloneClassExample cloneClassExample = new CloneClassExample("A", map );
try {
CloneClassExample clone = cloneClassExample.clone();
System.out.println("原来对象 \t" + cloneClassExample);
System.out.println("clone对象 \t" + clone);
// 改变cloneClassExample 中map的值
cloneClassExample.getMap().put("changeKey1","changeVal1");
System.out.println("change后原来对象 \t" + cloneClassExample);
System.out.println("clone对象 \t" + clone);
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
输出结果
原来对象 CloneClassExample{str='A', map={key1=value1, key2=value1}}
clone对象 CloneClassExample{str='A', map={key1=value1, key2=value1}}
change后原来对象 CloneClassExample{str='A', map={key1=value1, key2=value1, changeKey1=changeVal1}}
clone对象 CloneClassExample{str='A', map={key1=value1, key2=value1, changeKey1=changeVal1}}
要解决这个问题,只能使用深克隆,下面上例子
深克隆
- 还是以上面的例子为例,只需要重写上
clone
方法
@Override
public CloneClassExample clone() throws CloneNotSupportedException {
CloneClassExample clone = (CloneClassExample) super.clone();
clone.setMap(Maps.newHashMap(map));
return clone;
}
- 再次执行
main
方法输出
原来对象 CloneClassExample(str=A, map={key1=value1, key2=value1})
clone对象 CloneClassExample(str=A, map={key1=value1, key2=value1})
change后原来对象 CloneClassExample(str=A, map={key1=value1, key2=value1, changeKey1=changeVal1})
clone对象 CloneClassExample(str=A, map={key1=value1, key2=value1})
根据输出结果,可以发现,clone对象并没有随着原来对象引用的改变而改变
小结
要实现对象克隆,需要满足以下条件
- 对象的类必须实现
Cloneable
接口,该接口标识类可以被克隆。 - 在类中重写
clone()
方法,并修改方法的访问修饰符为public
,返回类型为类本身。 - 如果需要对引用类型深克隆,需要在
clone()
方法中,对引用类型的属性进行深拷贝。
什么时候使用clone
使用对象克隆可以在以下情况下非常有用:
- 复制对象:当需要创建一个对象的独立副本时,可以使用克隆来快速复制对象,而不必手动复制每个属性。
- 缓存数据:在某些情况下,需要缓存一些数据,但是不希望改动缓存中的数据影响到原始数据。克隆可以用于将数据缓存到另一个对象中,从而保持缓存数据的独立性。
- 多线程环境:当多个线程需要访问相同的对象时,为了避免对原始对象进行并发修改,可以使用克隆来创建每个线程的独立副本。
- 框架和库设计:在设计框架或库时,克隆提供了一种方便的方式来复制对象,以实现更高级别的功能或满足特定的需求。
需要注意的是,克隆并不总是必需的,并且在某些情况下可能存在性能和安全性方面的问题。因此,在使用克隆时需要谨慎考虑,并根据具体的需求和情况来决定是否使用克隆。此外,为了实现正确和有效的克隆操作,必须保证被克隆的类实现了Cloneable
接口,并正确地重写了clone()
方法。
clone 相关类库的实现分析
值得一提的是,我们在使用对象复制的时候,常用的就是Spring的BeanUtils和,Apache Commons 的BeanUtils
-
Apache Commons BeanUtils:Apache Commons BeanUtils提供了
copyProperties()
方法,用于将源对象的属性值复制到目标对象。它使用Java反射机制来访问对象的属性,并通过属性的getter和setter方法进行赋值。示例使用Apache Commons BeanUtils进行属性复制:
import org.apache.commons.beanutils.BeanUtils; BeanUtils.copyProperties(source, target);
建议使用Apache Commons BeanUtils的情况:
- 需要进行简单的属性复制,不需要关注特定的转换逻辑。
- 源对象和目标对象具有相同的属性名,且属性类型兼容。
需要注意的是
如果源对象和目标对象的属性是引用类型,复制操作只是将引用复制给目标对象,并不会创建新的实例。
-
Spring的BeanUtils:Spring的BeanUtils提供了
copyProperties()
方法,与Apache Commons BeanUtils类似,也是将源对象的属性值复制到目标对象。除了支持简单的属性复制,它还提供了更多的功能,如忽略Null值、类型转换、自定义转换器等。示例使用Spring的BeanUtils进行属性复制:
import org.springframework.beans.BeanUtils; BeanUtils.copyProperties(source, target);
建议使用Spring的BeanUtils的情况:
- 需要进行属性复制,且有特定的转换需求。
- 需要忽略源对象中的Null值属性。
- 需要进行自定义的属性转换操作,如日期的格式转换。
使用建议:
- 对于简单的属性复制,两者都可以使用,具体选择根据个人偏好和项目需求。
- 如果项目已经使用了Spring,可以优先考虑使用Spring的BeanUtils,因为它提供了更多的功能和选项。
- 如果项目没有使用Spring,或者只需要进行简单的属性复制,可以选择使用Apache Commons BeanUtils,因为它是一个独立的类库,不依赖于Spring框架。
- 如果需要进行更复杂的属性转换和自定义逻辑,可以考虑通过编写自定义的转换器或使用其他专门的映射工具,如MapStruct、Dozer等。
- 通常情况下,Spring的BeanUtils在执行属性复制时比Apache Commons BeanUtils更快, 这主要是Spring的BeanUtils内部增加了缓存机制、字段赋值、简化逻辑等操作
总之,根据具体需求和项目背景,选择适合的属性复制工具可以提高开发效率和代码可维护性。
更多内容,敬请关注:#公众号:代码教父