java中建立物件有幾種方式?
一、使用new關鍵字
如 User user=new User();
執行這條語句,jvm做了什麼?
-
首先在方法區的常量池中檢視是否有new 後面引數(也就是類名)的符號引用,並檢查是否有類的載入資訊也就是是否被載入解析和初始化過。如果已經載入過了就不在載入,否則執行類的載入全過程
-
載入完類後,大致做了如下三件事: a、給例項分配記憶體 b、呼叫建構函式,初始化成員欄位 c、user物件指向分配的記憶體空間 注意:new操作不是原子操作,b和c的順序可能會調換
二、使用clone方法
當我們呼叫一個物件的clone方法,jvm就會建立一個新的物件,將前面物件的內容全部拷貝進去。用clone方法建立物件並不會呼叫任何建構函式。因為Object 類的 clone 方法的 原理是從記憶體中(堆記憶體)以二進位制流的方式進行拷貝,重新分配一個記憶體塊,那建構函式沒有被執行也是非常正常的了.
使用clone方法建立物件的例項:
public class CloneTest implements Cloneable{
private String name;
private int 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;
}
public CloneTest(String name, int age) {
super();
this.name = name;
this.age = age;
}
public static void main(String[] args) {
try {
CloneTest cloneTest = new CloneTest("酸辣湯",18);//todo
CloneTest copyClone = (CloneTest) cloneTest.clone();
System.out.println("newclone:"+cloneTest.getName());
System.out.println("copyClone:"+copyClone.getName());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}
輸出:
newclone:酸辣湯
copyClone:酸辣湯
複製程式碼
注意:
1.clone是Object中的方法,Cloneable是一個標識介面,它表明這個類的物件是可以拷貝的。如果沒有實現Cloneable介面卻呼叫了clone()函式將丟擲異常
2.Object.clone()未做同步處理,執行緒不安全
3.clone()有深拷貝和淺拷貝兩種方式
複製程式碼
三、使用反序列化
序列化是幹什麼的?
簡單說就是為了儲存在記憶體中的各種物件的狀態(也就是例項變數,不是方法),並且可以把儲存的物件狀態再讀出來。雖然你可以用你自 己的各種各樣的方法來儲存object states,但是Java給你提供一種應該比你自己好的儲存物件狀態的機制,那就是序列化。一句話概括:序列化是指將物件的狀態資訊轉換為可以儲存或傳輸的形式的過程。
java中要序列化的類必要實現Serializable介面
什麼情況下需要序列化
a)當你想把的記憶體中的物件狀態儲存到一個檔案中或者資料庫中時候;
b)當你想用套接字在網路上傳送物件的時候;
c)當你想通過RMI(遠端方法呼叫)傳輸物件的時候;
使用反序列化建立物件例項:
1.物件要實現Serializable介面
import java.io.Serializable;
public class Person implements Serializable {
int age;
int height;
String name;
public Person(String name, int age, int height){
this.name = name;
this.age = age;
this.height = height;
}
}
複製程式碼
2、序列化與反序列化
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyTestSer {
/**
* Java物件的序列化與反序列化
*/
public static void main(String[] args) {
Person zhangsan = new Person("zhangsan", 30, 170);
Person lisi = new Person("lisi", 35, 175);
Person wangwu = new Person("wangwu", 28, 178);
try {
//需要一個檔案輸出流和物件輸出流;檔案輸出流用於將位元組輸出到檔案,物件輸出流用於將物件輸出為位元組
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser"));
out.writeObject(zhangsan);
out.writeObject(lisi);
out.writeObject(wangwu);
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser"));
Person one = (Person) in.readObject();
Person two = (Person) in.readObject();
Person three = (Person) in.readObject();
System.out.println("name:"+one.name + " age:"+one.age + " height:"+one.height);
System.out.println("name:"+two.name + " age:"+two.age + " height:"+two.height);
System.out.println("name:"+three.name + " age:"+three.age + " height:"+three.height);
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果:
name:zhangsan age:30 height:170 //todo
name:lisi age:35 height:175
name:wangwu age:28 height:178
複製程式碼
android中的場景
1.元件間(如activity間)的物件傳遞 (實現Parcelable或Serializable介面)
2.使用 Binder進行程式間的通訊傳遞的物件必須實現Parcelable介面
Serializable 是java的序列化介面,使用簡單但是開銷比較大,序列化和反序列化都涉及到大量的I/O操作,效率相對較低。Parcelable是Android提供的序列化方法,更適用於Android平臺,效率很高,但是使用起來比較麻煩.Parcelable主要用在記憶體序列化上,序列化儲存裝置或將序列化後的物件通過網路傳輸建議使用Serializable。
四、使用反射
通過反射來建立類物件的例項,有兩個步驟:
-
首先我們得拿到類物件的Class
如何獲取? 有三種方式(反射章節會詳細講解)
- 類.class,如Person.class
- 物件.getClass()
- Class.forName("類全路徑")
-
通過反射建立類物件的例項物件
在拿到類物件的Class後,就可以通過Java的反射機制來建立類物件的例項物件了,主要分為兩種方式:
-
Class.newInstance()
-
呼叫類物件的構造方法
舉個例子:
首先準備一個Person的類:
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
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{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
複製程式碼
使用Class.newInstance()建立物件:
public class ClassNewInstance {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Person person = Person.class.newInstance();
person.setAge(18);
person.setName("酸辣湯");
System.out.println(person);
}
}
執行結果:
Person{name='酸辣湯', age=18}
複製程式碼
注意 :newInstance建立物件例項的時候會呼叫無參的建構函式,所以必需確保類中有無引數的可見的建構函式,否則將會丟擲異常。
呼叫類物件的構造方法——Constructor
Constructor
是Java反射機制中的建構函式物件,獲取該物件的方法有以下幾種:
-
Class.getConstructors():獲取類物件的所有可見的建構函式
-
Class.getConstructor(Class... paramTypes):獲取指定的建構函式
獲取類物件所有的構造方法並遍歷:
public class ConstructorInstance {
public static void main(String[] args) {
Class p = Person.class;
for(Constructor constructor : p.getConstructors()){
System.out.println(constructor);
}
}
}
執行結果:
public com.eft.reflect.Person()
public com.eft.reflect.Person(java.lang.String,int)
複製程式碼
獲取指定的構造方法
通過Class.getConstructor(Class... paramTypes)即可獲取類物件指定的構造方法,其中paramTypes為引數型別的Class可變引數,當不傳paramTypes時,獲取的構造方法即為預設的構造方法。
public class ConstructorInstance {
public static void main(String[] args) throws Exception {
Class p = Person.class;
Constructor constructor1 = p.getConstructor();//獲取預設的構造方法
Constructor constructor2 = p.getConstructor(String.class,int.class);//獲取指定的構造方法
System.out.println(constructor1);
System.out.println(constructor2);
}
}
執行結果:
public com.eft.reflect.Person()
public com.eft.reflect.Person(java.lang.String,int)
複製程式碼
通過構造方法建立物件
Constructor物件中有一個方法newInstance(Object ... initargs),這裡的initargs即為要傳給構造方法的引數,如Person(String,int),通過其對應的Constructor例項,呼叫newInstance方法並傳入相應的引數,即可通過Person(String,int)來建立類物件的例項物件。
測試程式碼如下:
public class ConstructorInstance {
public static void main(String[] args) throws Exception {
Class p = Person.class;
Constructor constructor = p.getConstructor(String.class,int.class);
Person person = (Person) constructor.newInstance("酸辣湯",18);
System.out.println(person);
}
}
執行結果:
Person{name='酸辣湯', age=18}
複製程式碼
如上通過反射建立物件,只是反射機制中的冰山一角,詳細瞭解java反射知識,可以參考這篇 java反射全解
五、使用Unsafe
sun.misc.Unsafe中提供allocateInstance
方法,僅通過Class物件就可以建立此類的例項物件,而且不需要呼叫其建構函式、初始化程式碼、JVM安全檢查等。它抑制修飾符檢測,也就是即使構造器是private修飾的也能通過此方法例項化,只需提類物件即可建立相應的物件。由於這種特性,allocateInstance在java.lang.invoke、Objenesis(提供繞過類構造器的物件生成方式)、Gson(反序列化時用到)中都有相應的應用。
直接看例子
package cn.eft.llj.unsafe;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class Demo9 {
static Unsafe unsafe;
static {
//獲取Unsafe物件
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
static class C1 {
private String name;
private C1() {
System.out.println("C1 default constructor!");
}
private C1(String name) {
this.name = name;
System.out.println("C1 有參 constructor!");
}
public void test(){
System.out.println("執行了test方法");
}
}
public static void main(String[] args) throws InstantiationException {
C1 c= (C1) unsafe.allocateInstance(C1.class);
System.out.println(c);
c.test();
}
}
複製程式碼
輸出結果:
cn.eft.llj.unsafe.Demo9$C1@6bc7c054
執行了test方法
複製程式碼
關於Unsafe的詳細瞭解,可以參考這篇Java魔法類:Unsafe應用解析
結尾
如果還有其他建立物件的方式,歡迎在評論區留言~