[改善Java代码]避免对象的浅拷贝
建议43: 避免对象的浅拷贝
我们知道一个类实现了Cloneable接口就表示它具备了被拷贝的能力,如果再覆写clone()方法就会完全具备拷贝能力。拷贝是在内存中进行的,所以在性能方面比直接通过new生成对象要快很多,特别是在大对象的生成上,这会使性能的提升非常显著。但是对象拷贝也有一个比较容易忽略的问题:浅拷贝(Shadow Clone,也叫做影子拷贝)存在对象属性拷贝不彻底的问题。我们来看这样一段代码:
public class Client {
public static void main(String[] args) {
//定义父亲
Person f = new Person("父亲");
//定义大儿子
Person s1 = new Person("大儿子",f);
//小儿子的信息是通过大儿子拷贝过来的
Person s2 = s1.clone();
s2.setName("小儿子");
System.out.println(s1.getName() +" 的父亲是 " + s1.getFather().getName());
System.out.println(s2.getName() +" 的父亲是 " + s2.getFather().getName());
}
}
class Person implements Cloneable{
//姓名
private String name;
//父亲
private Person father;
public Person(String _name){
name = _name;
}
public Person(String _name,Person _parent){
name = _name;
father = _parent;
}
/*name和parent的getter/setter方法省略*/
//拷贝的实现
@Override
public Person clone(){
Person p = null;
try {
p = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person getFather() {
return father;
}
public void setFather(Person father) {
this.father = father;
}
}
程序中,我们描述了这样一个场景:一个父亲,有两个儿子,大小儿子同根同种,所以小儿子对象就通过拷贝大儿子对象来生成,运行输出的结果如下:
大儿子 的父亲是 父亲
小儿子 的父亲是 父亲
这很正确,没有问题。突然有一天,父亲心血来潮想让大儿子去认个干爹,也就是大儿子的父亲名称需要重新设置一下,代码如下:
public static void main(String[] args) {
//定义父亲
Person f = new Person("父亲");
//定义大儿子
Person s1 = new Person("大儿子",f);
//小儿子的信息是通过大儿子拷贝过来的
Person s2 = s1.clone();
s2.setName("小儿子");
//认干爹
s1.getFather().setName("干爹");
System.out.println(s1.getName() +" 的父亲是 " + s1.getFather().getName());
System.out.println(s2.getName() +" 的父亲是 " + s2.getFather().getName());
}
上面仅仅修改了加粗字体部分,大儿子重新设置了父亲名称,我们期望的输出是:将大儿子父亲的名称修改为干爹,小儿子的父亲名称保持不变。下面来检查一下结果是否如此:
大儿子 的父亲是 干爹
小儿子 的父亲是 干爹
怎么回事,小儿子的父亲也成了“干爹”?两个儿子都没有,岂不是要气死“父亲”了!出现这个问题的原因就在于clone方法,我们知道所有类都继承自Object,Object提供了一个对象拷贝的默认方法,即上面代码中的super.clone方法,但是该方法是有缺陷的,它提供的是一种浅拷贝方式,也就是说它并不会把对象的所有属性全部拷贝一份,而是有选择性的拷贝,它的拷贝规则如下:
(1)基本类型
如果变量是基本类型,则拷贝其值,比如int、float等。
(2)对象
如果变量是一个实例对象,则拷贝地址引用,也就是说此时新拷贝出的对象与原有对象共享该实例变量,不受访问权限的限制。这在Java中是很疯狂的,因为它突破了访问权限的定义:一个private修饰的变量,竟然可以被两个不同的实例对象访问,这让Java的访问权限体系情何以堪!
(3)String字符串
这个比较特殊,拷贝的也是一个地址,是个引用,但是在修改时,它会从字符串池(String Pool)中重新生成新的字符串,原有的字符串对象保持不变,在此处我们可以认为String是一个基本类型。(有关字符串的知识详见第4章。)
明白了这三个规则,上面的例子就很清晰了,小儿子对象是通过拷贝大儿子产生的,其父亲都是同一个人,也就是同一个对象,大儿子修改了父亲名称,小儿子也就跟着修改了—于是,父亲的两个儿子都没了!其实要更正也很简单,clone方法的代码如下:
public Person clone(){
Person p = null;
try {
p = (Person) super.clone();
p.setFather(new Person(p.getFather().getName()));
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
然后再运行,小儿子的父亲就不会是“干爹”了。如此就实现了对象的深拷贝(Deep Clone),保证拷贝出来的对象自成一体,不受“母体”的影响,和new生成的对象没有任何区别。
注意 浅拷贝只是Java提供的一种简单拷贝机制,不便于直接使用。
[改善Java代码]避免对象的浅拷贝的更多相关文章
- [改善Java代码]警惕数组的浅拷贝
建议62:警惕数组的浅拷贝 一.分析 在日常工作中,我们会遇见很多数组的拷贝和复制的问题,但是在你使用系统提供的API进行编码的时候,无形中会留下浅拷贝的隐患. 二.场景 有这样一个例子,第一个箱 ...
- [改善Java代码] 推荐使用序列化实现对象的拷贝
建议44: 推荐使用序列化实现对象的拷贝 上一个建议说了对象的浅拷贝问题,实现Cloneable接口就具备了拷贝能力,那我们来思考这样一个问题:如果一个项目中有大量的对象是通过拷贝生成的,那我们该如何 ...
- [改善Java代码]在接口中不要存在实现代码
第3章 类.对象及方法 书读得多而不思考,你会觉得自己知道的很多. 书读得多而思考,你会觉得自己不懂的越来越多. —伏尔泰 在面向对象编程(Object-Oriented Programming,O ...
- [改善Java代码]推荐使用String直接量赋值
建议52:推荐使用String直接量赋值 一.建议 String对象的生成方式有两种: 1.通过new关键字生成,String str3 = new String(“中国”); 2.直接声明,如:St ...
- [改善Java代码]易变业务使用脚本语言编写
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...
- [改善Java代码]asList方法产生的List对象不可更改
上一个建议之处了asList方法在转换基本类型数组时候存在的问题,在看下asList方法返回的列表有何特殊的地方.看代码: import java.util.Arrays; import java.u ...
- [改善Java代码]非稳定排序推荐使用List
我们知道Set与List的最大区别就是Set中的元素不可以重复(这个重复指的equals方法的返回值相等),其他方面则没有太大的区别了,在Set的实现类中有一个比较常用的类需要了解一下:TreeSet ...
- [改善Java代码]子列表只是原列表的一个视图
List接口提供了subList方法,其作用是返回一个列表的子列表.这与String类的subString有点类似.但是他们的功能是否相同?看代码: import java.util.ArrayLis ...
- [改善Java代码]推荐在复杂字符串操作中使用正则表达式
一.分析 字符串的操作,诸如追加.合并.替换.倒序.分隔等,都是在编码过程中经常用到的,而且Java也提供了append.replace.reverse.split等方法来完成这些操作,它们使用起来 ...
随机推荐
- PopupWindow添加动画效果
1.定义PopupWindow弹出与消失的两个动画文件,放在anim文件夹下 popup_enter.xml <?xml version="1.0" encoding=&qu ...
- 问题-Delphi2007跟踪变量时提示“E2171 Variable 'APolygon' inaccessible here due to optimization”
问题现象:Delphi2007跟踪变量时提示“E2171 Variable 'APolygon' inaccessible here due to optimization” . 问题原因:可能是因为 ...
- POJ 3259 Wormholes(最短路,判断有没有负环回路)
Wormholes Time Limit: 2000MS Memory Limit: 65536K Total Submissions: 24249 Accepted: 8652 Descri ...
- hdu 1429 胜利大逃亡(续)(bfs+位压缩)
胜利大逃亡(续) Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Su ...
- Java 8 正式发布,新特性全搜罗
经过2年半的努力.屡次的延期和9个里程碑版本,甲骨文的Java开发团队终于发布了Java 8正式版本. Java 8版本最大的改进就是Lambda表达式,其目的是使Java更易于为多核处理器编写代码: ...
- ASP.NET(C#)中的try catch异常处理机制
在开发一个Umbraco平台系统的过程中,遇到了问题. 写的代码如下 fileUrl = MediaHelper.GetMediaUrl(Convert.ToInt32(publishedConten ...
- JMS开发(二):深入PTP,Pub-Sub两种模式
1.PTP模型 PTP(Point-to-Point)模型是基于队列(Queue)的,对于PTP消息模型而言,它的消息目的是一个消息队列(Queue),消息生产者每次发送消息总是把消息送入消息队列中, ...
- Hql处理日期格式化问题
1. Date date=Calendar.getInstance().getTime(); Date date1=Calendar.getInstance().getTime(); String h ...
- 《JavaScript高级程序设计》 读书笔记(三)
操作符 递增和递减操作符 var num1 = 2; var num2 = 20; var num3 = --num1 + num2; // 等于 21 var num4 = num1 + num2; ...
- android 多线程数据库读写分析与优化
最新需要给软件做数据库读写方面的优化,之前无论读写,都是用一个 SQLiteOpenHelper.getWriteableDataBase() 来操作数据库,现在需要多线程并发读写,项目用的是2.2的 ...