深入理解 Java 中四種創建對象的方式
作者:六尺帳篷
鏈接:https://www.jianshu.com/p/7584b028cbda
-
調用 new 語句創建對象
-
調用對象的 clone() 方法
-
運用反射手段創建對象
-
運用反序列化手段
調用 new 語句創建對象
// 使用java語言的關鍵字 new 創建對象,初始化對象數據
MyObject mo = new MyObject() ;
調用對象的 clone() 方法
MyObject anotherObject = new MyObject();
MyObject object = anotherObject.clone();
使用 clone() 方法克隆一個對象的步驟:
- 被克隆的類要實現 Cloneable 接口。
- 被克隆的類要重寫 clone()方法。
原型模式主要用於對象的複製,實現一個接口(實現 Cloneable 接口),重寫一個方法(重寫 Object 類中的 clone 方法),即完成了原型模式。
原型模式中的拷貝分爲 "淺拷貝" 和 "深拷貝":
-
淺拷貝: 對值類型的成員變量進行值的複製, 對引用類型的成員變量只複製引用, 不復制引用的對象.
-
深拷貝: 對值類型的成員變量進行值的複製, 對引用類型的成員變量也進行引用對象的複製.
(Object 類的 clone 方法只會拷貝對象中的基本數據類型的值,對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝。)
原型模式的優點。
-
- 如果創建新的對象比較複雜時,可以利用原型模式簡化對象的創建過程。
-
- 使用原型模式創建對象比直接 new 一個對象在性能上要好的多,因爲 Object 類的 clone 方法是一個本地方法,它直接操作內存中的二進制流,特別是複製大對象時,性能的差別非常明顯。
原型模式的使用場景。
因爲以上優點,所以在需要重複地創建相似對象時可以考慮使用原型模式。
比如需要在一個循環體內創建對象,假如對象創建過程比較複雜或者循環次數很多的話,使用原型模式不但可以簡化創建過程,而且可以使系統的整體性能提高很多。
運用反射手段創建對象
我們先介紹一下反射:
反射的定義
-
反射機制是在運行時, 對於任意一個類, 都能夠知道這個類的所有屬性和方法; 對於任意一個對象, 都能夠調用它的任意一個方法。 在 java 中,只要給定類的名字, 那麼就可以通過反射機制來獲得類的所有信息。
-
反射機制主要提供了以下功能: 在運行時判定任意一個對象所屬的類;在運行時創建對象; 在運行時判定任意一個類所具有的成員變量和方法; 在運行時調用任意一個對象的方法; 生成動態代理。
哪裏用到反射機制?
jdbc 中有一行代碼:
Class.forName('com.mysql.jdbc.Driver.class');// 加載 MySql 的驅動類。 這就
是反射, 現在很多框架都用到反射機制, hibernate, struts 都是用反射機制實
現的。
反射的實現方式
在 Java 中實現反射最重要的一步, 也是第一步就是獲取 Class 對象, 得到 Class 對象後可以通過該對象調用相應的方法來獲取該類中的屬性、方法以及調用該類中的方法。
有 4 種方法可以得到 Class 對象:
-
Class.forName(“類的路徑” );
-
類名. class
-
對象名. getClass()
-
如果是基本類型的包裝類, 則可以通過調用包裝類的 Type 屬性來獲得該包裝類的 Class 對象, Class clazz = Integer.TYPE;
實現 Java 反射的類
-
1)Class: 它表示正在運行的 Java 應用程序中的類和接口。
-
2)Field: 提供有關類或接口的屬性信息, 以及對它的動態訪問權限。
-
3)Constructor: 提供關於類的單個構造方法的信息以及對它的訪問權限
-
4)Method: 提供關於類或接口中某個方法信息。
注意:Class 類是 Java 反射中最重要的一個功能類,所有獲取對象的信息 (包
括: 方法 / 屬性 / 構造方法 / 訪問權限) 都需要它來實現。
反射機制的優缺點
優點:
-
(1) 能夠運行時動態獲取類的實例, 大大提高程序的靈活性。
-
(2) 與 Java 動態編譯相結合, 可以實現無比強大的功能。
缺點:
-
(1) 使用反射的性能較低。 java 反射是要解析字節碼, 將內存中的對象進行解析。
解決方案:- 由於 JDK 的安全檢查耗時較多, 所以通過 setAccessible(true) 的方式關閉安全檢查來(取消對訪問控制修飾符的檢查) 提升反射速度。
- 需要多次動態創建一個類的實例的時候, 有緩存的寫法會比沒有緩存要快很多:
3.ReflectASM 工具類 , 通過字節碼生成的方式加快反射速度。
-
(2) 使用反射相對來說不安全, 破壞了類的封裝性, 可以通過反射獲取這個
類的私有方法和屬性。
運用反序列化手段
序列化與反序列化
Java 序列化是指把 Java 對象轉換爲字節序列的過程;而 Java 反序列化是指把字節序列恢復爲 Java 對象的過程。
爲什麼需要序列化與反序列化
我們知道,當兩個進程進行遠程通信時,可以相互發送各種類型的數據,包括文本、圖片、音頻、視頻等, 而這些數據都會以二進制序列的形式在網絡上傳送。那麼當兩個 Java 進程進行通信時,能否實現進程間的對象傳送呢?答案是可以的。如何做到呢?這就需要 Java 序列化與反序列化了。換句話說,一方面,發送方需要把這個 Java 對象轉換爲字節序列,然後在網絡上傳送;另一方面,接收方需要從字節序列中恢復出 Java 對象。基本原理和網絡通信是一致的,通過特殊的編碼方式:寫入數據將對象以及其內部數據編碼,存在在數組或者文件裏面然後發送到目的地後,在進行解碼,讀出數據。OK 到此顯示出來爲我們所用即可。
當我們明晰了爲什麼需要 Java 序列化和反序列化後,我們很自然地會想 Java 序列化的好處。其好處一是實現了數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件裏),二是,利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。
對象序列化
-
java.io.ObjectOutputStream 代表對象輸出流,它的 writeObject(Object obj) 方法可對參數指定的 obj 對象進行序列化,把得到的字節序列寫到一個目標輸出流中。只有實現了 Serializable 和 Externalizable 接口的類的對象才能被序列化。
-
java.io.ObjectInputStream 代表對象輸入流,它的 readObject() 方法從一個源輸入流中讀取字節序列,再把它們反序列化爲一個對象,並將其返回。
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/*
* 序列化流:把對象按照流一樣的方式存入文本或者在網絡中傳輸; 對象 ---> 流 :ObjectOutputStream
* 反序列化流:把文本文件中的流對象數據或者網絡中的流對象數據還原成對象。 流---> 對象 :ObjectInputStream
*/
public class ObjectStreamDemo {
public static void main(String[] args) throws IOException {
// 序列化數據其實就是把對象寫到文本文件
//write();
read();
}
private static void read() throws IOException {
// 創建反序列化流對象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
"a.txt"));
// 讀取,還原對象
try {
Person p = (Person) ois.readObject();
System.out.println(p.toString());
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ois.close();
}
private static void write() throws IOException {
// 創建序列化流對象
// public ObjectOutputStream(OutputStream out)
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
"a.txt"));
// 創建對象
Person p = new Person("java", 20);
oos.writeObject(p);
// 釋放資源
oos.close();
}
}
import java.io.Serializable;
/*
* NotSerializableException爲序列化異常,
* 該類需要實現一個接口:Serializable序列化接口,該接口中並沒有任何方法,僅僅作爲標識。
* 類似於此的沒有方法的接口是標記接口
*
* !!!每一次去修改該類的時候都會生成一個新的序列化標識的值!,需要重新新,重新讀,這是基本方法。
* 想辦法來固定該類的標識ID,人爲設定。這樣即使再次修改類的內容,只要ID固定了就可以保證,在讀取的時候一直是匹配的。
* 增加 generated serial version ID,在類裏面直接點擊黃色即可,增加一個變化的ID值
*/
/*
* 當有的成員變量不需要被序列化時:如何解決。
* 方法使用transient關鍵字聲明不需要序列化的成員變量
*/
public class Person implements Serializable{
/**
* serialVersionUID
*/
private static final long serialVersionUID = -9164765814868887767L;
private String name;
private transient int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
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;
}
@Override
public String toString() {
return "Person []";
}
}
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/Kz6-YSP9EYjfk2MAHaexNQ