天天看點

我的對象Girl會分身,淺克隆、深克隆

我的對象Girl會分身,淺克隆、深克隆

在這個神奇的星球上,有這樣一個群體,比較呆萌,天天沉浸在代碼的世界。這個代碼的世界裡他們天天面向對象Coding,而且這個對象還會克隆,進行分身。

1、什麼時候會用到Clone呢?

一般是想對一個對象進行處理,又想保留原有的資料進行接下來的操作,這時候就需要克隆了。好比來了一件事,複制一個分身,分身去處理;自身還是繼續幹自己的事,分身和自身的行為和狀态互不幹擾影響。

2、既然Clone這麼有用,那如何實作Clone呢?

我的對象Girl會分身,淺克隆、深克隆

Java提供了一個Cloneable接口,這隻是一個标示接口,沒有定義方法,不寫實作這個接口的話,克隆時會報異常CloneNotSupportedException。

我的對象Girl會分身,淺克隆、深克隆
我的對象Girl會分身,淺克隆、深克隆

需要覆寫Object類中的clone()方法,該方法是native修飾的。

定義一個女友類:Girl,很有錢有一輛Car,還是一個貓控,有一群貓咪。

package com.test.clone;

import java.util.List;

public class Girl implements Cloneable{
	private String name;
	private int age;
	private Car car;
	private List<Cat> catList;
	
	@Override
	public String toString() {
		return "name:"+name+", age:"+age+", car:"+car+", catList:"+catList;
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
		
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	public List<Cat> getCatList() {
		return catList;
	}
	public void setCatList(List<Cat> catList) {
		this.catList = catList;
	}
}

           

Car類:

package com.test.clone;

public class Car {
	private String brand;
	private double price;
	
	@Override
	public String toString() {
		return "{brand:"+brand+", price:"+price+"}";
	}
	
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	
}

           

Cat類:

package com.test.clone;

public class Cat {
	private String name;

	@Override
	public String toString() {
		return "{name:"+name+"}";
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
}
           

測試類:

package com.test.clone;

import java.util.ArrayList;
import java.util.List;

public class TestClone {
	
	public static void main(String[] args) throws CloneNotSupportedException {
		Car car = new Car();
		car.setBrand("Audi");
		car.setPrice(213456.78);
		
		List<Cat> catList = new ArrayList<Cat>();
		Cat c1 = new Cat();
		c1.setName("cat1");
		Cat c2 = new Cat();
		c2.setName("cat2");
		catList.add(c1);
		catList.add(c2);
		
		Girl g = new Girl();
		g.setName("LiLi");
		g.setAge(18);
		g.setCar(car);
		g.setCatList(catList);
		
		System.out.println("原來的女友:"+g);
		//進行克隆
		Girl g2 = (Girl)g.clone();
		//修改屬性和引用對象的屬性
		g.setName("Lucy");
		g.setAge(19);
		car.setBrand("QQ");
		car.setPrice(1.99);
		c1.setName("cat1-1");
		
		System.out.println("克隆的女友:"+g2);
	}
	
}
           

運作結果:

原來的女友:name:LiLi, age:18, car:{brand:Audi, price:213456.78}, catList:[{name:cat1}, {name:cat2}]
克隆的女友:name:LiLi, age:18, car:{brand:QQ, price:1.99}, catList:[{name:cat1-1}, {name:cat2}]

           

我們可以看到,設定的Lucy和19歲沒有生效,還是LiLi 18歲,設定的car和cat的屬性值變化了。說明對象的非引用類型是成功複制的,而引用類型沒有進行複制,還是一份。這種克隆稱之為:淺克隆。

本來女友g挺有錢的,有一輛21萬多的Audi;克隆後女友g2變沒錢了,隻有一輛QQ價值1.99元,容我哭會~~

那如何實作全部屬性的克隆,讓女友g2還是那麼有錢呢? 這就需要實作對象的深度克隆了,簡稱:深克隆。

實作深克隆有以下方法:

a、所有引用類型屬性實作Cloneable接口

b、利用序列化和反序列化實作

c、利用json實作

d、利用反射實作

3、實作深克隆:所有引用類型屬性實作Cloneable接口

類Girl修改方法clone():

@Override
	protected Object clone() throws CloneNotSupportedException {
		Girl g = null;
		g = (Girl)super.clone();
		if(g!=null) {
			g.setCar((Car)car.clone());
			if(catList!=null) {
				List<Cat> cList = new ArrayList<Cat>();
				for(Cat c: catList) {
					cList.add((Cat)c.clone());
				}
				g.setCatList(cList);
			}
		}
		return g;
	}
           

類Car和類cat實作Cloneable接口,重寫方法clone():

@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
           

再次運作測試類,結果如下:

原來的女友:name:LiLi, age:18, car:{brand:Audi, price:213456.78}, catList:[{name:cat1}, {name:cat2}]
克隆的女友:name:LiLi, age:18, car:{brand:Audi, price:213456.78}, catList:[{name:cat1}, {name:cat2}]

           

有錢的女友LiLi成功複制了,還是有Audi車,還是那麼有錢。

不過這種方法比較繁瑣,不推薦。

4、實作深克隆:利用序列化和反序列化實作

類Girl、Car、Cat實作Serializable接口;

添加方法deepCloneObject:

/**
    * 利用序列化和反序列化實作深克隆
    *
    * @param object 待克隆的對象
    * @param <T> 待克隆對象的資料類型
    * @return 已經深度克隆過的對象
    */
public static <T extends Serializable> T deepCloneObject(T object) {
       T deepClone = null;
       ByteArrayOutputStream baos = null;
       ObjectOutputStream oos = null;
       ByteArrayInputStream bais = null;
       ObjectInputStream ois = null;
       try {
           baos = new ByteArrayOutputStream();
           oos = new ObjectOutputStream(baos);
           oos.writeObject(object);
           bais = new ByteArrayInputStream(baos.toByteArray());
           ois = new ObjectInputStream(bais);
           deepClone = (T)ois.readObject();
       } catch (IOException | ClassNotFoundException e) {
          e.printStackTrace();
       } finally {
       	//資源關閉
           try {
               if(baos != null) baos.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
           try {
               if(oos != null) oos.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
           try{
               if(bais != null) bais.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
           try{
               if(ois != null) ois.close();
           } catch (IOException e) {
               e.printStackTrace();
           }
       }
       return deepClone;
}
           

修改測試類:

//進行克隆
//Girl g2 = (Girl)g.clone();
Girl g2 = deepCloneObject(g);
           

運作結果為:

原來的女友:name:LiLi, age:18, car:{brand:Audi, price:213456.78}, catList:[{name:cat1}, {name:cat2}]
克隆的女友:name:LiLi, age:18, car:{brand:Audi, price:213456.78}, catList:[{name:cat1}, {name:cat2}]
           

已經成功深度克隆,這種方法比第一種簡單很多,推薦使用。

5、實作深克隆:利用json實作

修改測試類,運作也是ok的

String json = new Gson().toJson(g);
Girl g2 = new Gson().fromJson(json, Girl.class);
           

6、實作深克隆:利用反射實作

添加方法reflectClone():

/**
* 拷貝對象方法(适合同一類型的對象複制)
 * 
 * @param objSource 源對象
 * @param clazz 目标類
 * @return
 * @throws InstantiationException
 * @throws IllegalAccessException
 */
public static <T> T reflectClone(Object objSource, Class<T> clazz)
		throws InstantiationException, IllegalAccessException {
	// 如果源對象為空,則直接傳回null
	if (null == objSource) {
		return null;
	}
	
	T objDes = clazz.newInstance();
	// 獲得源對象所有屬性
	Field[] fields = clazz.getDeclaredFields();

	// 循環周遊字段,擷取字段對應的屬性值
	for (Field field : fields) {
		// 如果不為空,設定可見性,然後傳回
		field.setAccessible(true);

		try {
			// 設定字段可見,即可用get方法擷取屬性值。
			field.set(objDes, field.get(objSource));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	return objDes;
}
           

修改測試類,運作也是ok的

Girl g2 = reflectClone(g, Girl.class);
g2.setCar(reflectClone(g.getCar(), Car.class));
List<Cat> catList2 = new ArrayList<Cat>();
g2.setCatList(catList2);
for(Cat c: g.getCatList()) {
	catList2.add(reflectClone(c, Cat.class));
}
           

實作深克隆的方式比較多,根據項目本身需要進行選擇~~

歡迎朋友們關注轉發點贊,謝謝~~

浏覽更多文章可關注微信公衆号:diggkr

我的對象Girl會分身,淺克隆、深克隆

原文參看:我的對象Girl會分身