• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
掬一束月光
路漫漫其修远兮,吾将上下而求索.
博客园    首页    新随笔    联系   管理    订阅  订阅
关于创建一个相同的对象(引用数据类型)的思考

1.首先想到的是用"="

 

Object a = new Object();
Object b = a;

除了初学者,稍微懂些java语言的人都会注意到引用数据类型的问题.

b并不是被创建的和a相同的对象,b就是a,b只是获得a指向的对象的引用.

 

2.其次就是最朴素的,怎么创建的a就怎么创建b,重新走一遍获得a的流程

 

这算是最基础的方法了,刚学数组的时候会做很多复制数组的练习,这些练习就是这种最简单的方法.

 

针对于数组有System.arraycopy(Object src ,int srcPos ,Object dest ,int destPos , int length),

和Arrays.copyOf().

在这里存在一个问题,即如果数组里的元素还是引用数据类型,则最后复制的也并不是一个全新的数组对象,而只是一个简单的新的"数组"对象.

 

3.对于普通的对象的话,有一个api是Object.clone()

这个类的说明是Creates and returns a copy of this object.创建并返回一个此对象的复制.

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;

 

  1. 第一次声明,保证克隆对象将有单独的内存地址分配。
  2. 第二次声明,表明原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
  3. 第三此声明,表明原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

 

对于2.中提到的重新创建一遍对象存在许多问题,因为原始对象可能在初始化后经历了许多逻辑,如果再走一遍跟提高复用性背道而驰,

所以clone()应该算是最优的解决方案.它即可以复制一个刚初始化的对象,也可以复制一个已经修改过属性的对象.

 

而对于克隆,针对2.中最后提到的问题,分为浅克隆(ShallowClone)与深克隆(DeepClone),他们的区别就在于是否支持引用数据类型的成员变量的复制

 

浅克隆的步骤:

1.被复制的类需要实现Cloneable接口

如果不实现此接口而调用clone()方法会抛出CloneNotSupportedException异常.

此接口为标记接口,不含任何方法.

2.重写clone()方法,访问权限设为public,方法中调用super,clone()方法得到需要复制的对象.

 

class Student implements Cloneable{  
    private int number;  
  
    public int getNumber() {  
        return number;  
    }  
  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return stu;  
    }  
}  

 

 

深克隆的步骤:

1.被复制的类,和类中所有的引用数据类型,都要实现Cloneable接口

2.被复制的类,和类中所有的引用数据类型,都要重写clone()方法.

class Address implements Cloneable {  
    private String add;  
  
    public String getAdd() {  
        return add;  
    }  
  
    public void setAdd(String add) {  
        this.add = add;  
    }  
      
    @Override  
    public Object clone() {  
        Address addr = null;  
        try{  
            addr = (Address)super.clone();  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        return addr;  
    }  
}  
  
class Student implements Cloneable{  
    private int number;  
  
    private Address addr;  
      
    public Address getAddr() {  
        return addr;  
    }  
  
    public void setAddr(Address addr) {  
        this.addr = addr;  
    }  
  
    public int getNumber() {  
        return number;  
    }  
  
    public void setNumber(int number) {  
        this.number = number;  
    }  
      
    @Override  
    public Object clone() {  
        Student stu = null;  
        try{  
            stu = (Student)super.clone();   //浅复制  
        }catch(CloneNotSupportedException e) {  
            e.printStackTrace();  
        }  
        stu.addr = (Address)addr.clone();   //深度复制  
        return stu;  
    }  
}  

  

可参考java.util.Date;的clone()

/** 
 * Return a copy of this object. 
 */  
public Object clone() {  
    Date d = null;  
    try {  
        d = (Date)super.clone();  
        if (cdate != null) {  
            d.cdate = (BaseCalendar.Date) cdate.clone();  
        }  
    } catch (CloneNotSupportedException e) {} // Won't happen  
    return d;  
} 

  

4.而在实际问题中,3.中提到的深克隆也会面临一个问题--那就是对象中引用数据类型过多过深,引用数据类型属性还有引用数据类型的属性,使用3.中的深克隆还是很麻烦.

其实深克隆还有另外一种实现方式,就是对象的序列化(Serialization)与反序列化(Deserialization).

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在内存中.通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用数据类型的成员变量.因此通过序列化将对象写到一个流中,再通过反序列化将其读取出来,就可以实现深克隆.

 

虽然Java的序列化非常简单、强大,但是要用好,还有很多地方需要注意。

比如曾经序列化了一个对象,可由于某种原因,该类做了一点点改动,然后重新被编译,那么这时反序列化刚才的对象,将会出现异常。 你可以通过添加serialVersionUID属性来解决这个问题。

如果你的类是个单例(Singleton)类,是否允许用户通过序列化机制复制该类,如果不允许你需要谨慎对待该类的实现。

 

public class Attribute {   
    private String no;   
}   
public class Product {   
    private String name;   
    private Attribute attribute;   
    public Product clone() {   
        ByteArrayOutputStream byteOut = null;   
        ObjectOutputStream objOut = null;   
        ByteArrayInputStream byteIn = null;   
        ObjectInputStream objIn = null;   
        try {   
            byteOut = new ByteArrayOutputStream();    
            objOut = new ObjectOutputStream(byteOut);    
            objOut.writeObject(prototype);     
            byteIn = new ByteArrayInputStream(byteOut.toByteArray());   
            objIn = new ObjectInputStream(byteIn);                  
            return (ContretePrototype) objIn.readObject();   
        } catch (IOException e) {   
            throw new RuntimeException("Clone Object failed in IO.",e);      
        } catch (ClassNotFoundException e) {   
            throw new RuntimeException("Class not found.",e);      
        } finally{   
            try{   
                byteIn = null;   
                byteOut = null;   
                if(objOut != null) objOut.close();      
                if(objIn != null) objIn.close();      
            }catch(IOException e){      

            }      

        }   

    }   

} 

  

 

注意:基于序列化和反序列化实现的克隆不仅仅是深度克隆,更重要的是通过泛型限定,可以检查出要克隆的对象是否支持序列化,这项检查是编译器完成的,不是在运行时抛出异常,这种是方案明显优于使用Object类的clone方法克隆对象。让问题在编译的时候暴露出来总是优于把问题留到运行时。

 

专注Java开发

作者:翟亚豪

邮箱:zyh186214@hotmail.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted on 2017-12-29 09:47  掬一束月光  阅读(356)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3