Java創(chuàng)建對(duì)象的四種方式詳解
前言
以String類為例
String string = null; Class class1 = String.class;// 該方法最為安全可靠,程序性能更高。 Class class2 = string.getClass(); Class class3 = Class.forName(“java.lang.String”);// 可能拋出ClassNotFoundException異常
一旦獲取了該類所對(duì)應(yīng)的Class對(duì)象之后,就可以通過調(diào)用Class對(duì)象的方法來獲得該對(duì)象和該類的真實(shí)信息了
1. new 出一個(gè)對(duì)象
例:
String s = new String("abc");
2. 利用反射創(chuàng)建對(duì)象
1.創(chuàng)建對(duì)象
通過反射來生成對(duì)象有如下兩種方式:
(1)使用Class對(duì)象的newInstance()方法來創(chuàng)建該Class對(duì)象對(duì)應(yīng)類的實(shí)例。但是這種方式要求該Class對(duì)象的對(duì)應(yīng)類有默認(rèn)的構(gòu)造器,而執(zhí)行newInstance()方法時(shí)實(shí)際上是利用默認(rèn)構(gòu)造器來創(chuàng)建該類的實(shí)例。
(2)先使用Class對(duì)象獲取指定的Constructor對(duì)象,再調(diào)用Construtor對(duì)象的newInstance()方法來創(chuàng)建該Class對(duì)象對(duì)應(yīng)類的實(shí)例。通過這種方式可以選擇使用某個(gè)類的指定構(gòu)造器來創(chuàng)建實(shí)例。
另外,如果我們不想利用默認(rèn)構(gòu)造器來創(chuàng)建java對(duì)象,而想利用指定的構(gòu)造器來創(chuàng)建java對(duì)象,則需要利用Construtor對(duì)象,每個(gè)Construtor對(duì)應(yīng)一個(gè)構(gòu)造器,為了利用指定構(gòu)造器來創(chuàng)建java對(duì)象,需要如下三個(gè)步驟:
(1)獲取該Class對(duì)象;
(2)利用該Class對(duì)象的getConstrutor方法來獲取指定的構(gòu)造器;
(3)調(diào)用Construtor的newInstance方法來創(chuàng)建Java對(duì)象。
2.調(diào)用方法
獲取到某個(gè)類的Class對(duì)象之后,可以通過該Class對(duì)象的getMethods方法或者getMethod方法獲取全部或指定方法。
每個(gè)Method對(duì)象對(duì)應(yīng)一個(gè)方法,獲得Method對(duì)象后,程序就可通過該Method來調(diào)用對(duì)應(yīng)的方法,在Method里包含一個(gè)invoke方法,該方法簽名如下:
Object invoke(Object obj,Object… args);該方法中的obj是執(zhí)行該方法的主調(diào),后面的args是執(zhí)行該方法時(shí)傳入該方法的實(shí)參。
當(dāng)通過Method的invoke方法來調(diào)用對(duì)應(yīng)的方法時(shí),Java會(huì)要求程序必要要有調(diào)用該方法的權(quán)限。如果程序確實(shí)需要調(diào)用該對(duì)象的私有方法,則可先調(diào)用Method對(duì)象的:
setAccessible(boolean flag);方法,將Method對(duì)象的accessoble標(biāo)志設(shè)置為指示的布爾值。
布爾值為true,則表示該Method在使用時(shí)應(yīng)該取消Java語言訪問權(quán)限檢查;
布爾值為false,則表示該Method在使用時(shí)應(yīng)該實(shí)施Java語言訪問權(quán)限檢查;
3.訪問屬性值
通過Class對(duì)象的getFields或getField方法可以獲取該類所包括的全部Field(屬性)或指定Field,F(xiàn)ield提供了如下兩組方法來訪問屬性:
getXxx(Object obj);獲取obj對(duì)象該Field的屬性值,此處的Xxx對(duì)應(yīng)8個(gè)基本類型,如果該屬性的類型是引用類型則取消get后面的Xxx。
setXxx(Object obj,Xxx val);將obj對(duì)象的該Field設(shè)置成val值,此處的Xxx對(duì)應(yīng)8個(gè)基本類型,如果該屬性的類型是引用類型則取消set后面的Xxx。
4.示例代碼
import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class ClassTest { public static void main(String[] args) throws Exception { Object object; Class cl = Class.forName("TestMe"); Method method = cl .getDeclaredMethod("print", new Class[]{String.class}); Constructor constructor = cl .getDeclaredConstructor(new Class[]{String.class}); object = constructor.newInstance(new Object[]{"Hello"}); method.invoke(object, new Object[]{"zhouxianli"}); } } class TestMe { private String str; public TestMe(String str) { this.str = str; System.out.println("In Constructor str = " + str); } public void print(String name) { System.out.println("In print str = " + str + " and name = " + name); } }
3. 利用clone創(chuàng)建對(duì)象
什么是"clone"?
在實(shí)際編程過程中,我們常常要遇到這種情況:有一個(gè)對(duì)象A,在某一時(shí)刻A中已經(jīng)包含了一些有效值,此時(shí)可能 會(huì)需要一個(gè)和A完全相同新對(duì)象B,并且此后對(duì)B任何改動(dòng)都不會(huì)影響到A中的值,也就是說,A與B是兩個(gè)獨(dú)立的對(duì)象,但B的初始值是由A對(duì)象確定的。在 Java語言中,用簡(jiǎn)單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實(shí)現(xiàn)clone()方法是其中最簡(jiǎn)單,也是最高效的手段。
Java的所有類都默認(rèn)繼承java.lang.Object類,在java.lang.Object類中有一個(gè)方法clone()。JDK API的說明文檔解釋這個(gè)方法將返回Object對(duì)象的一個(gè)拷貝。要說明的有兩點(diǎn):一是拷貝對(duì)象返回的是一個(gè)新對(duì)象,而不是一個(gè)引用。二是拷貝對(duì)象與用 new操作符返回的新對(duì)象的區(qū)別就是這個(gè)拷貝已經(jīng)包含了一些原來對(duì)象的信息,而不是對(duì)象的初始信息。
1. Clone&Copy
假設(shè)現(xiàn)在有一個(gè)Employee對(duì)象,Employee tobby =new Employee(“CMTobby”,5000),通 常我們會(huì)有這樣的賦值Employee cindyelf=tobby,這個(gè)時(shí)候只是簡(jiǎn)單了copy了一下reference,cindyelf和tobby都指向內(nèi)存中同一個(gè)object,這樣cindyelf或者tobby的一個(gè)操作都可能影響到對(duì)方。打個(gè)比方,如果我們通過cindyelf.raiseSalary()方法改變了salary域的值,那么tobby通過getSalary()方法得到的就是修改之后的salary域的值,顯然這不是我們?cè)敢饪吹降摹N覀兿M玫絫obby的一個(gè)精確拷貝,同時(shí)兩者互不影響,這時(shí)候我們就可以使用Clone來滿足我們的需求。Employee cindy=tobby.clone(),這時(shí)會(huì)生成一個(gè)新的Employee對(duì)象,并且和tobby具有相同的屬性值和方法。
2. Shallow Clone&Deep
Clone Clone是如何完成的呢?Object在對(duì)某個(gè)對(duì)象實(shí)施Clone時(shí)對(duì)其是一無所知的,它僅僅是簡(jiǎn)單地執(zhí)行域?qū)τ虻腸opy,這就是Shallow Clone。這樣,問題就來了咯,以Employee為例,它里面有一個(gè)域hireDay不是基本型別的變量,而是一個(gè)reference變量,經(jīng)過Clone之后就會(huì)產(chǎn)生一個(gè)新的Date型別的reference,它和原始對(duì)象中對(duì)應(yīng)的域指向同一個(gè)Date對(duì)象,這樣克隆類就和原始類共享了一部分信息,而這樣顯然是不利的,過程下圖所示:
這個(gè)時(shí)候我們就需要進(jìn)行deep Clone了,對(duì)那些非基本型別的域進(jìn)行特殊的處理,例如本例中的hireDay。我們可以重新定義Clone方法,對(duì)hireDay做特殊處理,如下代碼所示:
class Employee implements Cloneable { public Object clone() throws CloneNotSupportedException { Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone() return cloned; } }
3. Clone()方法的保護(hù)機(jī)制
在Object中Clone()是被申明為protected的,這樣做是有一定的道理的,以Employee
類為例,通過申明為protected,就可以保證只有Employee類里面才能“克隆”Employee對(duì)象,原理可以參考我前面關(guān)于public、protected、private的學(xué)習(xí)筆記。
4. Clone()方法的使用
Clone()方法的使用比較簡(jiǎn)單,注意如下幾點(diǎn)即可:
a. 什么時(shí)候使用shallow Clone,什么時(shí)候使用deep Clone,這個(gè)主要看具體對(duì)象的域是什么性質(zhì)的,基本型別還是reference variable
b. 調(diào)用Clone()方法的對(duì)象所屬的類(Class)必須implements Clonable接口,否則在調(diào)用Clone方法的時(shí)候會(huì)拋出CloneNotSupportedException。
4. 利用反序列化創(chuàng)建對(duì)象
1、為什么要進(jìn)行序列化
再介紹之前,我們有必要先了解下對(duì)象的生命周期,我們知道Java中的對(duì)象都是存在于堆內(nèi)存中的,而堆內(nèi)存是可以被垃圾回收器不定期回收的。從對(duì)象被創(chuàng)建到被回收這一段時(shí)間就是Java對(duì)象的生命周期,也即Java對(duì)象只存活于這個(gè)時(shí)間段內(nèi)。
對(duì)象被垃圾回收器回收意味著對(duì)象和對(duì)象中的成員變量所占的內(nèi)存也就被回收,這意味著我們就再也得不到該對(duì)象的任何內(nèi)容了,因?yàn)橐呀?jīng)被銷毀了嘛,當(dāng)然我們可以再重新創(chuàng)建,但這時(shí)的對(duì)象的各種屬性都又被重新初始化了。所以如果我們需要保存某對(duì)象的狀態(tài),然后再在未來的某段時(shí)間將該對(duì)象再恢復(fù)出來的話,則必須要在對(duì)象被銷毀即被垃圾回收器回收之前保存對(duì)象的狀態(tài)。要保存對(duì)象狀態(tài)的話,我們可以使用文件、數(shù)據(jù)庫,也可以使用序列化,這里我們主要介紹對(duì)象序列化。我們很有必要了解這方面的內(nèi)容,因?yàn)閷?duì)象序列化不僅在保存對(duì)象狀態(tài)時(shí)可以被用到(對(duì)象持久化),在Java中的遠(yuǎn)程方法調(diào)用RMI也會(huì)被用到,在網(wǎng)絡(luò)中要傳輸對(duì)象的話,則必須要對(duì)對(duì)象進(jìn)行序列化,關(guān)于RMI有機(jī)會(huì)我會(huì)再專門開貼介紹。
簡(jiǎn)單總結(jié)起來,進(jìn)行對(duì)象序列化的話的主要原因就是實(shí)現(xiàn)對(duì)象持久化和進(jìn)行網(wǎng)絡(luò)傳輸,這里先只介紹怎樣通過對(duì)象序列化保存對(duì)象的狀態(tài)。
下面我們通過一個(gè)簡(jiǎn)單的例子來介紹下如何進(jìn)行對(duì)象序列化。
2、怎樣進(jìn)行對(duì)象序列化
假設(shè)我們要保存Person類的某三個(gè)對(duì)象的name、age、height這三個(gè)成員變量,當(dāng)然這里只是簡(jiǎn)單舉例
我們先看下Person類,要序列化某個(gè)類的對(duì)象的話,則該類必要實(shí)現(xiàn)Serializable接口,從Java API中我們發(fā)現(xiàn)該接口是個(gè)空接口,即該接口中沒聲明任何方法。
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; } }
下面我們看一下如何來進(jìn)行序列化,這其中主要涉及到 Java 的 I/O 方面的內(nèi)容,主要用到兩個(gè)類 FileOutputStream 和 ObjectOutputStream , FileOutputStream 用于將字節(jié)輸出到文件, ObjectOutputStream 通過調(diào)用 writeObject 方法將對(duì)象轉(zhuǎn)換為可以寫出到流的數(shù)據(jù)。所以整個(gè)流程是這樣的: ObjectOutputStream 將要序列化的對(duì)象轉(zhuǎn)換為某種數(shù)據(jù),然后通過 FileOutputStream 連接某磁盤文件,再對(duì)象轉(zhuǎn)化的數(shù)據(jù)轉(zhuǎn)化為字節(jié)數(shù)據(jù)再將其寫出到磁盤文件。下面是具體代碼:
import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; public class MyTestSer { /** * Java對(duì)象的序列化與反序列化 */ 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 { //需要一個(gè)文件輸出流和對(duì)象輸出流;文件輸出流用于將字節(jié)輸出到文件,對(duì)象輸出流用于將對(duì)象輸出為字節(jié) ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser")); out.writeObject(zhangsan); out.writeObject(lisi); out.writeObject(wangwu); out.close(); } catch (IOException e) { e.printStackTrace(); } } }
3、對(duì)象的反序列化
我們存儲(chǔ)的目的主要是為了再恢復(fù)使用,下面我們來看下加上反序列化后的代碼:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; public class MyTestSer { /** * Java對(duì)象的序列化與反序列化 */ 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 { //需要一個(gè)文件輸出流和對(duì)象輸出流;文件輸出流用于將字節(jié)輸出到文件,對(duì)象輸出流用于將對(duì)象輸出為字節(jié) 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(); } } }
輸出結(jié)果如下:
name:zhangsan age:30 height:170
name:zhangsan age:35 height:175
name:zhangsan age:28 height:178
從添加的代碼我們可以看到進(jìn)行反序列化也很簡(jiǎn)單,主要用到的流是FileInputstream和ObjectInputstream正好與存儲(chǔ)時(shí)用到的流相對(duì)應(yīng)。另外從結(jié)果順序我們可以看到反序列化后得到對(duì)象的順序與序列化時(shí)的順序一致。
4、總結(jié)
進(jìn)行對(duì)象序列化主要目的是為了保存對(duì)象的狀態(tài)(成員變量)。
進(jìn)行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用于連接磁盤文件,并把字節(jié)寫出到該磁盤文件;ObjectOutputStream主要用于將對(duì)象寫出為可轉(zhuǎn)化為字節(jié)的數(shù)據(jù)。
要將某類的對(duì)象序列化,則該類必須實(shí)現(xiàn)Serializable接口,該接口僅是一個(gè)標(biāo)志,告訴JVM該類的對(duì)象可以被序列化。如果某類未實(shí)現(xiàn)Serializable接口,則該類對(duì)象不能實(shí)現(xiàn)序列化。
保存狀態(tài)的目的就是為了在未來的某個(gè)時(shí)候再恢復(fù)保存的內(nèi)容,這可以通過反序列化來實(shí)現(xiàn)。對(duì)象的反序列化過程與序列化正好相反,主要用到的兩個(gè)流是FileInputstream和ObjectInputStream。
反序列化后得到的對(duì)象的順序與保存時(shí)的順序一致。
5、補(bǔ)充
補(bǔ)充一:上面我們舉得例子很簡(jiǎn)單,要保存的成員變量要么是基本類型的要么是String類型的。但有時(shí)成員變量有可能是引用類型的,這是的情況會(huì)復(fù)雜一點(diǎn)。那就是當(dāng)要對(duì)某對(duì)象進(jìn)行序列化時(shí),該對(duì)象中的引用變量所引用的對(duì)象也會(huì)被同時(shí)序列化,并且該對(duì)象中如果也有引用變量的話則該對(duì)象也將被序列化??偨Y(jié)說來就是在序列化的時(shí)候,對(duì)象中的所有引用變量所對(duì)應(yīng)的對(duì)象將會(huì)被同時(shí)序列化。這意味著,引用變量類型也都要實(shí)現(xiàn)Serializable接口。當(dāng)然其他對(duì)象的序列化都是自動(dòng)進(jìn)行的。所以我們只要保證里面的引用類型是都實(shí)現(xiàn)Serializable接口就行了,如果沒有的話,會(huì)在編譯時(shí)拋出異常。如果序列化的對(duì)象中包含沒有實(shí)現(xiàn)Serializable的成員變量的話,這時(shí)可以使用transient關(guān)鍵字,讓序列化的時(shí)候跳過該成員變量。使用關(guān)鍵字transient可以讓你在序列化的時(shí)候自動(dòng)跳過transient所修飾的成員變量,在反序列化時(shí)這些變量會(huì)恢復(fù)到默認(rèn)值。
補(bǔ)充二:如果某類實(shí)現(xiàn)了Serializable接口的話,其子類會(huì)自動(dòng)編程可序列化的,這個(gè)好理解,繼承嘛。
補(bǔ)充三:在反序列化的時(shí)候,并不會(huì)調(diào)用對(duì)象的構(gòu)造器,這也好理解,如果調(diào)用了構(gòu)造器的話,對(duì)象的狀態(tài)不就又重新初始化了嗎。
補(bǔ)充四:我們說到對(duì)象序列化的是為了保存對(duì)象的狀態(tài),即對(duì)象的成員變量,所以靜態(tài)變量不會(huì)被序列化。
到此這篇關(guān)于Java創(chuàng)建對(duì)象的四種方式詳解的文章就介紹到這了,更多相關(guān)Java創(chuàng)建對(duì)象內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章

JavaWeb搭建網(wǎng)上圖書商城畢業(yè)設(shè)計(jì)

SpringBoot啟動(dòng)器Starters使用及原理解析

MyBatisPlus?TypeHandler自定義字段類型轉(zhuǎn)換Handler

后端返回各種圖片形式在前端的轉(zhuǎn)換及展示方法對(duì)比

java操作elasticsearch詳細(xì)方法總結(jié)

C#創(chuàng)建Web應(yīng)用程序代碼實(shí)例

SpringBoot使用Async注解失效原因分析及解決(spring異步回調(diào))