站稳马步
——整理 Object 类—— clone (克隆)
A. 关键字:
Object 、 clone ()、 Cloneable 接口、克隆、浅拷贝、深拷贝
B. 为什么需要克隆?
如果你碰到这种情况,你感觉是否需要 copy 一份呢?
假设你从数据库中得到一个 List<T> 列表后,你需要在各种情况下对此列表进行重新包装后返回此列表,那怎么办呢?如果在各种不同情况下分别去数据库取这个 List ,来操作,那岂不是要多次操作数据库!?于是,我一次读出,然后在不同情况下,对它进行 copy 。
C. 错误的实现:
People p1= new People () ;
People p2= new People () ;
p 2=p1;
// 或
People p 3=p1;
或许,好多新手都会认为 p2 、 p3 都是 p1 的拷贝。 P2 虽然申请了内存空间,但转而又指向 p1 的内存引用( 刚才 new 的操作等于制造一堆垃圾等着 JVM 回收 ); p3 和 p1 只不过是 2 个不同的变量指向同一内存区域。可以说: p1 、 p2 、 p3 依然是同一对象。
D. 正确实现:
1. 看看 Object 的源代码:
protectednative Object clone () throws CloneNotSupportedException;
可见是受保护的( protected ),而且是调用本地方法( native )
Clone() 规范约定如下:
(1)clone() 将对象复制了一份并返回给调用者。一般而言,clone ()方法满足:
① 对任何的对象x ,都有x.clone() !=x// 克隆对象与原对象不是同一个对象
② 对任何的对象x ,都有x.clone().getClass()= =x.getClass()// 克隆对象与原对象的类型一样
③ 如果对象x 的equals() 方法定义恰当,那么x.clone().equals(x) 应该成立。
(2)Java 中对象的克隆
① 为了获取对象的一份拷贝,我们可以利用Object 类的clone() 方法。
② 在派生类中覆盖基类的clone() 方法,并声明为public 。
③ 在派生类的clone() 方法中,调用super.clone() 。
④ 在派生类中实现Cloneable 接口。
2. Demo:
测试类:
import java.util.ArrayList;
import java.util.List;
/**
* @description :
* 简单类克隆实现 <br>
* 要实现克隆 , 必须实现 Cloneable 接口 , 这是一个标识接口 , 没有接口方法 <br>
* 实现了 Cloneable 接口,以指示 Object.clone() 方法可以合法地对该类实例进行按字段复制。 <br>
* 按照惯例,实现此接口的类应该使用公共方法重写 Object.clone (它是受保护的)。
*
* @author Ydj
* @createTime Oct 31, 2010 2:27:46 PM
* @version 1.0
*/
public class TestClone {
/**
* @param args
*/
public static void main ( String [] args ) {
// TODO Auto-generated method stub
People a= new People (( byte ) 1, "ydj" ,26 ) ;
Room room= new Room ( " 别墅 A" ) ;
a. setRoom ( room ) ;
List < Car > cars= new ArrayList < Car > () ;
cars.add ( new Car ( " 越野 " , "Jeep1" )) ;
cars.add ( new Car ( " 商务 " , "Hongqi" )) ;
a. setCars ( cars ) ;
People p1= new People () ;
People p2= new People () ;
p2=p1; // 或 People p3=p1;// 都是错误的复制
People b = ( People ) a. clone () ;
System .out . println (( a==b )) ;
System .out . println ( " 原本 姓名: " +a. getName () + " 年龄: " +a. getAge () + " 房子名字: " +a. getRoom () . getName () + " 拥有车子数: " +a. getCars () .size ()) ;
System .out . println ( " 副本 姓名: " +b . getName () + " 年龄: " +b . getAge () + " 房子名字: " +b . getRoom () . getName () + " 拥有车子数: " +b . getCars () .size ()) ;
b . setName ( "yidej" ) ;
b . setAge ( 25 ) ;
b . getRoom () . setName ( " 别墅 B" ) ;
b . getCars () .add ( new Car ( " 复制后加一部车 " , "XXX" )) ;
System .out . println ( "############## 修改副本的一些属性后 ##############" ) ;
System .out . println ( " 原本 姓名: " +a. getName () + " 年龄: " +a. getAge () + " 房子名字: " +a. getRoom () . getName () + " 拥有车子数: " +a. getCars () .size ()) ;
}
}
实体类:( set/get 方法均省略)
class People implements Cloneable { // 注意:一定要实现 Cloneable 接口,否则不支持 clone()
byte sex ;
String name ;
int age ;
Room room ;
List < Car > cars ;
People (){}
People ( byte sex , String name , int age ){
this . sex =sex ;
this . name =name ;
this . age =age ;
}
public Object clone (){// 注意 :你不能建立一个私有或默认范围克隆;你只能提高忽略时的可见性
try {
return super . clone () ;
} catch ( CloneNotSupportedException e ) {
// TODO Auto-generated catch block
e. printStackTrace () ;
return this ;
}
}
}
class Car {
String carType ;
String carName ;
。。。
}
class Room {
String name ;
。。。
}
运行结果:
false
原本 姓名: ydj 年龄: 26 房子名字:别墅 A 拥有车子数: 2
副本 姓名: ydj 年龄: 26 房子名字:别墅 A 拥有车子数: 2
############## 修改副本的一些属性后 ##############
原本 姓名: ydj 年龄: 26 房子名字:别墅 B 拥有车子数: 3
分析:
为什么修改了副本,却也修改了原本?
从上面结果可知:当修改对象副本的原始类型数据 ( 或 String) 时,不会影响原本;但修改了对象引用类型的,就会影响原本。因为这是表面的“浅拷贝”。
拷贝分为:浅拷贝 和 深拷贝
浅复制(浅克隆)
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
深复制(深克隆)
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
3. 如何实现深拷贝:
方法 1 :
People 中所有引用的对象( Room 、 Car )每个实体都实现 Cloneable 接口,并都提供 clone ()方法实现。
然后把 People 的 clone 方法实现改为如下:
public Object deepCopy () throws CloneNotSupportedException {
People c= ( People ) super . clone () ;
if ( this . room != null ){
c. room = ( Room ) this . room . clone () ;
}
c. cars = new ArrayList < Car > () ;
if ( this . cars != null ){
for ( Car i: this . cars ){
c. cars .add (( Car ) i. clone ()) ;
}
}
return c;
}
这样,在运行上面测试程序,我们会发现:在修改克隆后的对象时,并不会影响原对象。
方法 2 :
使用序列化( Serilization )。
People 以及所有引用的对象( Room 、 Car )每个实体都要实现 Serializable 接口。然后把 People 的 clone 方法实现改为如下:
public Object deepCopy2 () throws IOException, ClassNotFoundException {
// 将对象写到流里
ByteArrayOutputStream bos= new ByteArrayOutputStream () ;
ObjectOutputStream dos= new ObjectOutputStream ( bos ) ;
dos. writeObject ( this ) ;
// 将对象从流里读出
ByteArrayInputStream bis= new ByteArrayInputStream ( bos. toByteArray ()) ;
ObjectInputStream ois= new ObjectInputStream ( bis ) ;
return ois. readObject () ;
}
4. 总结:
使用“深拷贝”还是“浅拷贝”,取决与需要被拷贝的对象中是否引用了可变对象,取决与实际需要。