Android 從原型模式看java實例化對象clone和new的區別

一、原型模式

原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

這種模式是實現了一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用。

1、原型模式舉例(參考菜鳥教程)

public abstract class Shape implements Cloneable {

   private String id;
   protected String type;

   abstract void draw();

   public String getType(){
      return type;
   }

   public String getId() {
      return id;
   }

   public void setId(String id) {
      this.id = id;
   }

   public Object clone() {
      Object clone = null;
      try {
         clone = super.clone();
      } catch (CloneNotSupportedException e) {
         e.printStackTrace();
      }
      return clone;
   }
}

這是需要創建的類的基類(父類),必須實現Cloneable接口才能進行clone。

public class Square extends Shape {
 
   public Square(){
     type = "Square";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Square::draw() method.");
   }
}
public class Circle extends Shape {
 
   public Circle(){
     type = "Circle";
   }
 
   @Override
   public void draw() {
      System.out.println("Inside Circle::draw() method.");
   }
}

這個Square類就是我們需要大量重複使用的類,這裏需要說明一點,如果你需要複用的類和這個Square一樣簡單,或者獲取非常容易。請不要使用原型模式,如果類過於簡單使用clone的效率反而會比new來的低。

import java.util.Hashtable;
 
public class ShapeCache {
    
   private static Hashtable<String, Shape> shapeMap 
      = new Hashtable<String, Shape>();
 
   public static Shape getShape(String shapeId) {
      Shape cachedShape = shapeMap.get(shapeId);
      return (Shape) cachedShape.clone();
   }
 
   // 對每種形狀都運行數據庫查詢,並創建該形狀
   // shapeMap.put(shapeKey, shape);
   // 例如,我們要添加兩種形狀
   public static void loadCache() {
      Circle circle = new Circle();
      circle.setId("1");
      shapeMap.put(circle.getId(),circle);
 
      Square square = new Square();
      square.setId("2");
      shapeMap.put(square.getId(),square);
   }
}

ShapeCache就是我們用來創建方法的工具類。loadCache先用new的方式創建了基本類,這個類是用來以後克隆使用的。

ShapeCache.loadCache();
 
Shape clonedShape = (Shape) ShapeCache.getShape("1");
System.out.println("Shape : " + clonedShape.getType());        
 
Shape clonedShape2 = (Shape) ShapeCache.getShape("2");
System.out.println("Shape : " + clonedShape2.getType()); 

以上是最終調用的方法,可以發現,我們獲取的方法並不是new出來的也不是反射出來的而是clone出來的。

2、原型模式特點

優點: 1、性能提高。 2、逃避構造函數的約束。

缺點: 1、配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類不是很難,但對於已有的類不一定很容易,特別當一個類引用不支持串行化的間接對象,或者引用含有循環結構的時候。 2、必須實現 Cloneable 接口。

使用場景: 1、資源優化場景。 2、類初始化需要消化非常多的資源,這個資源包括數據、硬件資源等。 3、性能和安全要求的場景。 4、通過 new 產生一個對象需要非常繁瑣的數據準備或訪問權限,則可以使用原型模式。 5、一個對象多個修改者的場景。

二、java實例化對象的方式

  1. new
  2. clone
  3. 反序列化
  4. 反射

java常見的實例化對象的方式就以上4中,在原型模式中我們使用了new和clone的方式,那我們就來對比一下這兩個方式的區別吧。

1、new

new,這個方法我們再熟悉不過了,new一個實例時,java虛擬機先會調用類的加載,分別通過加載、連接、初始化、使用然後使用完之後卸載的過程,我們new一個類時也一樣,jvm會去判斷這個類是否已經加載過,如果加載過就會去開闢一段堆內存去創建類。然後調用構造方法之後實例化就會成功。new操作符的本意是分配內存。

程序執行到new操作符時, 首先去看new操作符後面的類型,因爲知道了類型,才能知道要分配多大的內存空間。分配完內存之後,再調用構造函數,填充對象的各個域,這一步叫做對象的初始化,構造方法返回後,一個對象創建完畢,可以把他的引用(地址)發佈到外部,在外部就可以使用這個引用操縱這個對象(當然,由於指令的重排序,發佈對象可能在構造函數返回之前。

2、clone

clone在第一步是和new相似的, 都是分配內存,調用clone方法時,分配的內存和源對象(即調用clone方法的對象)相同,然後再使用源對象中對應的各個域,填充新對象的域, 填充完成之後,clone方法返回,一個新的相同的對象被創建,同樣可以把這個新對象的引用發佈到外部。

需要注意的是,和new不同,clone涉及到深拷貝和淺拷貝的問題

情報:

淺複製
  被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不復制它所引用的對象。

深複製
  被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。

java克隆需要注意的點

Java的clone()方法
⑴clone方法將對象複製了一份並返回給調用者。一般而言,clone()方法滿足:
①對任何的對象x,都有x.clone() !=x//克隆對象與原對象不是同一個對象
②對任何的對象x,都有x.clone().getClass()= =x.getClass()//克隆對象與原對象的類型一樣
③如果對象x的equals()方法定義恰當,那麼x.clone().equals(x)應該成立。

⑵Java中對象的克隆
①爲了獲取對象的一份拷貝,我們可以利用Object類的clone()方法。
②在派生類中覆蓋基類的clone()方法,並聲明爲public。
③在派生類的clone()方法中,調用super.clone()。
④在派生類中實現Cloneable接口。

繼承自java.lang.Object類的clone()方法是淺複製

如果要實現深拷貝就需要手動實現clone方法。

	static class Body implements Cloneable {
		public Head head;

		public Body() {
		}

		public Body(Head head) {
			this.head = head;
		}

		@Override
		protected Object clone() throws CloneNotSupportedException {
			Body newBody = (Body) super.clone();
			newBody.head = (Head) head.clone();
			return newBody;
		}
	}

	static class Head implements Cloneable {
		public Face face;

		public Head() {
		}

		public Head(Face face) {
			this.face = face;
		}

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

	static class Face implements Cloneable {
		public Face() {
		}
	}

上述代碼參考)如果我們clone Body類A、B。A.head 和 B.head指向的內存地址不同,但是A.head.face 和 B.head.face指向的內存是一樣的。

所以,實現clone是比較困難的,因爲子子孫孫無窮盡也。所有的內部引用都需要單獨重寫clone方法。

三、總結

java的每一種實例化方法都有它不同的作用,new是最基礎的加載,反射是獲取該類的所有內容,clone可以減少CPU開支,序列化用來存儲實例的狀態,這個大家可以自行體會一下哦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章