zoukankan      html  css  js  c++  java
  • cloneable和Serializable的应用(java深复制、浅复制)

    普通类

    //普通类
    class Clazz {
    	int myId;
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	Clazz zz = new Clazz();
    	zz.setMyId(1);
    	System.out.println("原始id: " + zz.getMyId());
        
        //添加新引用
    	Clazz clazz = zz;
    	System.out.println("新引用的id: " + clazz.getMyId());
    
        //改变原类的属性
    	zz.setMyId(2);
    	System.out.println("原始id: " + zz.getMyId() + "; 新引用的id: " + clazz.getMyId());
    }
    

    想必大家都很熟悉,我们拥有指向同一个对象的两个引用,通过任何一个引用改变对象的内容,对于另外的引用都即时可见。

    但是当我们想要复制一份该怎么办呢?于是就有了下边的接口

    Cloneable类

    //克隆类
    class CloneClass implements Cloneable {
    	int myId;
    
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		return super.clone();
    	}
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	CloneClass cc = new CloneClass();
    	cc.setMyId(1);
    	System.out.println("原始id: " + cc.getMyId());
        
        //克隆
    	CloneClass cloneClass = (CloneClass) cc.clone();
    	System.out.println("克隆后的id: " + cloneClass.getMyId());
        
        //改变原类的属性
    	cc.setMyId(2);
    	System.out.println("原始id: " + cc.getMyId() + "; 克隆后的id: " + cloneClass.getMyId());
    }
    
    //结果
    原始id: 1
    克隆后的id: 1
    原始id: 2; 克隆后的id: 1
    

    改变原始类的id后,复制的类的信息保持不变,说明拷贝的类是一个不同的类,也就达到了复制一份的目的。

    我要想克隆类,就要调用clone方法,由于Object类的clone方法是protect的,所以要想我们的类在所有包中都能够克隆,就要将其实现为public的。有clone方法后,如果我们不实现Cloneable接口,编译就会报不能克隆的错。所以重写方法和实现接口必须共存。

    关于protect修饰符,我们知道可以子类调用,但同时还有一个条件是,只能在子类所在的包中调用,在其他地方调不到。

    但是如果类里面有对象的话,就存在问题了

    //类内引用的类
    class User {
    	int id;
    	String name;
    	
    	...
    }
    
    //克隆类
    class CloneClass implements Cloneable {
    	int myId;
    	User user;
    
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		return super.clone();
    	}
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	CloneClass cc = new CloneClass();
    	User user = new User(100, "张三");
    	cc.setUser(user);
    	System.out.println("原始User: " + cc.getUser());
    
        //克隆
    	CloneClass cloneClass = (CloneClass) cc.clone();
    	System.out.println("克隆后的User: " + cloneClass.getUser());
    
        //改变原类引用的类的属性
    	cc.getUser().setName("赵二麻子");
    	System.out.println("原始User: " + cc.getUser() + "; 克隆后的User: " + cloneClass.getUser());
    
    }
    
    //结果
    原始User: 张三
    克隆后的User: 张三
    原始User: 赵二麻子; 克隆后的User: 赵二麻子
    

    通过clone一个类,类内有引用类的时候,并不会重新拷贝一份(仅仅是复制了一个引用),指向的还是原类内引用指向的对象。所以改变原类的属性,对于克隆的类的引用即时可见。这就是所谓的浅拷贝。

    可以用如下的方法进行克服。

    //类内引用的类, 让其实现Cloneable
    class User implements Cloneable {
    	int id;
    	String name;
    
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		return super.clone();
    	}
    
        ...
    }
    
    //改变克隆类的clone方法
    class CloneClass implements Cloneable {
    	User user;
    
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		CloneClass cloneClass = (CloneClass) super.clone();
    		cloneClass.user = (User) cloneClass.user.clone();
    		return cloneClass;
    	}
    	
    	...
    }
    
    //测试,同上
    
    //结果
    原始User: 张三
    克隆后的User: 张三
    原始User: 赵二麻子; 克隆后的User: 张三
    

    内部引用对象的复制问题是解决了,但是类里每次新引用一个类,就得在clone里增加一段代码,是不是很繁琐,假如这个类很多类里都用到了呢,是不是一场灾难。

    Serializable接口将其操作简化了。

    Serializable类

    先引入一个工具类,不用看具体内容,作用是给一个类(Serializable类),返回这个类的副本

    abstract class BeanUtil {
    	@SuppressWarnings("unchecked")
    	public static <T> T cloneTo(T src) throws RuntimeException {
    		ByteArrayOutputStream memoryBuffer = new ByteArrayOutputStream();
    		ObjectOutputStream out = null;
    		ObjectInputStream in = null;
    		T dist = null;
    		try {
    			out = new ObjectOutputStream(memoryBuffer);
    			out.writeObject(src);
    			out.flush();
    			in = new ObjectInputStream(new ByteArrayInputStream(memoryBuffer.toByteArray()));
    			dist = (T) in.readObject();
    		} catch (Exception e) {
    			throw new RuntimeException(e);
    		} finally {
    			if (out != null)
    				try {
    					out.close();
    					out = null;
    				} catch (IOException e) {
    					throw new RuntimeException(e);
    				}
    			if (in != null)
    				try {
    					in.close();
    					in = null;
    				} catch (IOException e) {
    					throw new RuntimeException(e);
    				}
    		}
    		return dist;
    	}
    }
    

    Serializable类

    //类内引用的类, 让其实现Serializable类
    class User implements Serializable {
    	int id;
    	String name;
    	
    	...
    }
    
    //序列化类
    class SerializClass implements Serializable {
    	User user;
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	//复制类
    	SerializClass sc = new SerializClass();
    	User user = new User(100, "张三");
    	sc.setUser(user);
    	System.out.println("原始User: " + sc.getUser());
    
    	SerializClass serializClass = BeanUtil.cloneTo(sc);
    	System.out.println("克隆后的User: " + serializClass.getUser());
    
    	sc.getUser().setName("赵二麻子");
    	System.out.println("原始User: " + sc.getUser() + "; 克隆后的User: " + serializClass.getUser());
    }
    
    //结果
    原始User: 张三
    克隆后的User: 张三
    原始User: 赵二麻子; 克隆后的User: 张三
    

    达到了复制一份的目的,同时代码是不是也清爽了很多(工具类是复杂了点,但是只写一份就够了)。

    这种复制的方法,连同上边比较复杂的一种clone,都成为深拷贝。

    但这么好的方法也有失效的时候。

    //序列化类,有transient修饰符的字段
    class SerializClass implements Serializable {
    	transient int i;
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	//复制类
    	SerializClass sc = new SerializClass();
    	sc.setI(1);
    	System.out.println("原始i: " + sc.getI());
    
    	SerializClass serializClass = BeanUtil.cloneTo(sc);
    	System.out.println("克隆后的i: " + serializClass.getI());
    
    	sc.setI(2);
    	System.out.println("原始i: " + sc.getI() + "; 克隆后的i: " + serializClass.getI());
    }
    
    //结果
    原始i: 1
    克隆后的i: 0
    原始i: 2; 克隆后的i: 0
    

    也就是这种序列化会跳过带有transient修饰符的字段(该修饰符只能修饰字段,不能修饰方法),那么还能有什么方法可以复制吗?答案是有

    //序列化类
    class SerializPlusClass implements Serializable {
    	transient int i;
    
    	private void writeObject(java.io.ObjectOutputStream s) throws IOException {
    		s.defaultWriteObject();
    		s.writeInt(i);
    	}
    
    	private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
    		s.defaultReadObject();
    		i = s.readInt();
    	}
    	
    	...
    }
    
    //测试
    public static void main(String[] args) throws Exception {
    	//复制类
    	SerializPlusClass sc = new SerializPlusClass();
    	sc.setI(1);
    	System.out.println("原始i: " + sc.getI());
    
    	SerializPlusClass serializClass = BeanUtil.cloneTo(sc);
    	System.out.println("克隆后的i: " + serializClass.getI());
    
    	sc.setI(2);
    	System.out.println("原始i: " + sc.getI() + "; 克隆后的i: " + serializClass.getI());
    }
    
    //结果
    原始i: 1
    克隆后的i: 1
    原始i: 2; 克隆后的i: 1
    

    可以看到我们实现了writeObject和readObject方法,但这个方法既不是Object的方法,也不是Serializable的方法,那为什么这样就可以呢?

    我们可以回头看看那个工具类,在序列化的时候我们用到了ObjectInputStream和ObjectOutputStream类,就是说在序列化和反序列化的时候,这两个类调用了我们写的方法。从而用这种方法也可以自定序列化内容,包括transient修饰的字段。

    由于序列化对static修饰的字段也无效,所以也可以在这里实现。由于实验相对麻烦,这里没有给出。如果有兴趣可以序列化包含静态变量的类到文件,然后重启虚拟机(重新运行),执行反序列化。

    因为静态变量属于类,所以同一个虚拟机反序列化后还是原来的值,并不是反序列化出来的。

    后话

    我们知道transient修饰符,就是为了标识出不需要反序列化的字段,那为什么我们还要费尽心思来序列化它呢?

    考虑这样一种情况,有一个长度为100的list,里面只有一个有值,其他的都是null,如果全序列化出来,岂不是很浪费空间。所以通过这种方法有选择地序列化。具体实例参见ArrayList的实现,只序列化elementData的size个数据。

  • 相关阅读:
    JavaScript排序算法——快速排序
    JavaScript排序算法——归并排序
    JavaScript排序算法——堆排序
    JavaScript排序算法——插入排序
    ubuntu sublime text3 lisence
    jQuery学习笔记——弹出对话框
    CSS学习笔记——响应式布局
    CSS学习笔记——选择器
    JavaScript学习笔记——事件
    JavaScript学习笔记——节点
  • 原文地址:https://www.cnblogs.com/so-easy/p/12525490.html
Copyright © 2011-2022 走看看