站稳马步——(2)clone(克隆)、Cloneable

站稳马步

——整理 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()// 克隆对象与原对象的类型一样

如果对象xequals() 方法定义恰当,那么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.       总结:

使用“深拷贝”还是“浅拷贝”,取决与需要被拷贝的对象中是否引用了可变对象,取决与实际需要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值