Java創(chuàng)建對象的四種方式詳解
前言
以String類為例
String string = null; Class class1 = String.class;// 該方法最為安全可靠,程序性能更高。 Class class2 = string.getClass(); Class class3 = Class.forName(“java.lang.String”);// 可能拋出ClassNotFoundException異常
一旦獲取了該類所對應的Class對象之后,就可以通過調(diào)用Class對象的方法來獲得該對象和該類的真實信息了
1. new 出一個對象
例:
String s = new String("abc");
2. 利用反射創(chuàng)建對象
1.創(chuàng)建對象
通過反射來生成對象有如下兩種方式:
(1)使用Class對象的newInstance()方法來創(chuàng)建該Class對象對應類的實例。但是這種方式要求該Class對象的對應類有默認的構(gòu)造器,而執(zhí)行newInstance()方法時實際上是利用默認構(gòu)造器來創(chuàng)建該類的實例。
(2)先使用Class對象獲取指定的Constructor對象,再調(diào)用Construtor對象的newInstance()方法來創(chuàng)建該Class對象對應類的實例。通過這種方式可以選擇使用某個類的指定構(gòu)造器來創(chuàng)建實例。
另外,如果我們不想利用默認構(gòu)造器來創(chuàng)建java對象,而想利用指定的構(gòu)造器來創(chuàng)建java對象,則需要利用Construtor對象,每個Construtor對應一個構(gòu)造器,為了利用指定構(gòu)造器來創(chuàng)建java對象,需要如下三個步驟:
(1)獲取該Class對象;
(2)利用該Class對象的getConstrutor方法來獲取指定的構(gòu)造器;
(3)調(diào)用Construtor的newInstance方法來創(chuàng)建Java對象。
2.調(diào)用方法
獲取到某個類的Class對象之后,可以通過該Class對象的getMethods方法或者getMethod方法獲取全部或指定方法。
每個Method對象對應一個方法,獲得Method對象后,程序就可通過該Method來調(diào)用對應的方法,在Method里包含一個invoke方法,該方法簽名如下:
Object invoke(Object obj,Object… args);該方法中的obj是執(zhí)行該方法的主調(diào),后面的args是執(zhí)行該方法時傳入該方法的實參。
當通過Method的invoke方法來調(diào)用對應的方法時,Java會要求程序必要要有調(diào)用該方法的權(quán)限。如果程序確實需要調(diào)用該對象的私有方法,則可先調(diào)用Method對象的:
setAccessible(boolean flag);方法,將Method對象的accessoble標志設(shè)置為指示的布爾值。
布爾值為true,則表示該Method在使用時應該取消Java語言訪問權(quán)限檢查;
布爾值為false,則表示該Method在使用時應該實施Java語言訪問權(quán)限檢查;
3.訪問屬性值
通過Class對象的getFields或getField方法可以獲取該類所包括的全部Field(屬性)或指定Field,F(xiàn)ield提供了如下兩組方法來訪問屬性:
getXxx(Object obj);獲取obj對象該Field的屬性值,此處的Xxx對應8個基本類型,如果該屬性的類型是引用類型則取消get后面的Xxx。
setXxx(Object obj,Xxx val);將obj對象的該Field設(shè)置成val值,此處的Xxx對應8個基本類型,如果該屬性的類型是引用類型則取消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)建對象
什么是"clone"?
在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經(jīng)包含了一些有效值,此時可能 會需要一個和A完全相同新對象B,并且此后對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在 Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現(xiàn)clone()方法是其中最簡單,也是最高效的手段。
Java的所有類都默認繼承java.lang.Object類,在java.lang.Object類中有一個方法clone()。JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。要說明的有兩點:一是拷貝對象返回的是一個新對象,而不是一個引用。二是拷貝對象與用 new操作符返回的新對象的區(qū)別就是這個拷貝已經(jīng)包含了一些原來對象的信息,而不是對象的初始信息。
1. Clone&Copy
假設(shè)現(xiàn)在有一個Employee對象,Employee tobby =new Employee(“CMTobby”,5000),通 常我們會有這樣的賦值Employee cindyelf=tobby,這個時候只是簡單了copy了一下reference,cindyelf和tobby都指向內(nèi)存中同一個object,這樣cindyelf或者tobby的一個操作都可能影響到對方。打個比方,如果我們通過cindyelf.raiseSalary()方法改變了salary域的值,那么tobby通過getSalary()方法得到的就是修改之后的salary域的值,顯然這不是我們愿意看到的。我們希望得到tobby的一個精確拷貝,同時兩者互不影響,這時候我們就可以使用Clone來滿足我們的需求。Employee cindy=tobby.clone(),這時會生成一個新的Employee對象,并且和tobby具有相同的屬性值和方法。
2. Shallow Clone&Deep
Clone Clone是如何完成的呢?Object在對某個對象實施Clone時對其是一無所知的,它僅僅是簡單地執(zhí)行域?qū)τ虻腸opy,這就是Shallow Clone。這樣,問題就來了咯,以Employee為例,它里面有一個域hireDay不是基本型別的變量,而是一個reference變量,經(jīng)過Clone之后就會產(chǎn)生一個新的Date型別的reference,它和原始對象中對應的域指向同一個Date對象,這樣克隆類就和原始類共享了一部分信息,而這樣顯然是不利的,過程下圖所示:
這個時候我們就需要進行deep Clone了,對那些非基本型別的域進行特殊的處理,例如本例中的hireDay。我們可以重新定義Clone方法,對hireDay做特殊處理,如下代碼所示:
class Employee implements Cloneable { public Object clone() throws CloneNotSupportedException { Employee cloned = (Employee) super.clone(); cloned.hireDay = (Date) hireDay.clone() return cloned; } }
3. Clone()方法的保護機制
在Object中Clone()是被申明為protected的,這樣做是有一定的道理的,以Employee
類為例,通過申明為protected,就可以保證只有Employee類里面才能“克隆”Employee對象,原理可以參考我前面關(guān)于public、protected、private的學習筆記。
4. Clone()方法的使用
Clone()方法的使用比較簡單,注意如下幾點即可:
a. 什么時候使用shallow Clone,什么時候使用deep Clone,這個主要看具體對象的域是什么性質(zhì)的,基本型別還是reference variable
b. 調(diào)用Clone()方法的對象所屬的類(Class)必須implements Clonable接口,否則在調(diào)用Clone方法的時候會拋出CloneNotSupportedException。
4. 利用反序列化創(chuàng)建對象
1、為什么要進行序列化
再介紹之前,我們有必要先了解下對象的生命周期,我們知道Java中的對象都是存在于堆內(nèi)存中的,而堆內(nèi)存是可以被垃圾回收器不定期回收的。從對象被創(chuàng)建到被回收這一段時間就是Java對象的生命周期,也即Java對象只存活于這個時間段內(nèi)。
對象被垃圾回收器回收意味著對象和對象中的成員變量所占的內(nèi)存也就被回收,這意味著我們就再也得不到該對象的任何內(nèi)容了,因為已經(jīng)被銷毀了嘛,當然我們可以再重新創(chuàng)建,但這時的對象的各種屬性都又被重新初始化了。所以如果我們需要保存某對象的狀態(tài),然后再在未來的某段時間將該對象再恢復出來的話,則必須要在對象被銷毀即被垃圾回收器回收之前保存對象的狀態(tài)。要保存對象狀態(tài)的話,我們可以使用文件、數(shù)據(jù)庫,也可以使用序列化,這里我們主要介紹對象序列化。我們很有必要了解這方面的內(nèi)容,因為對象序列化不僅在保存對象狀態(tài)時可以被用到(對象持久化),在Java中的遠程方法調(diào)用RMI也會被用到,在網(wǎng)絡(luò)中要傳輸對象的話,則必須要對對象進行序列化,關(guān)于RMI有機會我會再專門開貼介紹。
簡單總結(jié)起來,進行對象序列化的話的主要原因就是實現(xiàn)對象持久化和進行網(wǎng)絡(luò)傳輸,這里先只介紹怎樣通過對象序列化保存對象的狀態(tài)。
下面我們通過一個簡單的例子來介紹下如何進行對象序列化。
2、怎樣進行對象序列化
假設(shè)我們要保存Person類的某三個對象的name、age、height這三個成員變量,當然這里只是簡單舉例
我們先看下Person類,要序列化某個類的對象的話,則該類必要實現(xiàn)Serializable接口,從Java API中我們發(fā)現(xiàn)該接口是個空接口,即該接口中沒聲明任何方法。
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; } }
下面我們看一下如何來進行序列化,這其中主要涉及到 Java 的 I/O 方面的內(nèi)容,主要用到兩個類 FileOutputStream 和 ObjectOutputStream , FileOutputStream 用于將字節(jié)輸出到文件, ObjectOutputStream 通過調(diào)用 writeObject 方法將對象轉(zhuǎn)換為可以寫出到流的數(shù)據(jù)。所以整個流程是這樣的: ObjectOutputStream 將要序列化的對象轉(zhuǎn)換為某種數(shù)據(jù),然后通過 FileOutputStream 連接某磁盤文件,再對象轉(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對象的序列化與反序列化 */ 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 { //需要一個文件輸出流和對象輸出流;文件輸出流用于將字節(jié)輸出到文件,對象輸出流用于將對象輸出為字節(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、對象的反序列化
我們存儲的目的主要是為了再恢復使用,下面我們來看下加上反序列化后的代碼:
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 { //需要一個文件輸出流和對象輸出流;文件輸出流用于將字節(jié)輸出到文件,對象輸出流用于將對象輸出為字節(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
從添加的代碼我們可以看到進行反序列化也很簡單,主要用到的流是FileInputstream和ObjectInputstream正好與存儲時用到的流相對應。另外從結(jié)果順序我們可以看到反序列化后得到對象的順序與序列化時的順序一致。
4、總結(jié)
進行對象序列化主要目的是為了保存對象的狀態(tài)(成員變量)。
進行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用于連接磁盤文件,并把字節(jié)寫出到該磁盤文件;ObjectOutputStream主要用于將對象寫出為可轉(zhuǎn)化為字節(jié)的數(shù)據(jù)。
要將某類的對象序列化,則該類必須實現(xiàn)Serializable接口,該接口僅是一個標志,告訴JVM該類的對象可以被序列化。如果某類未實現(xiàn)Serializable接口,則該類對象不能實現(xiàn)序列化。
保存狀態(tài)的目的就是為了在未來的某個時候再恢復保存的內(nèi)容,這可以通過反序列化來實現(xiàn)。對象的反序列化過程與序列化正好相反,主要用到的兩個流是FileInputstream和ObjectInputStream。
反序列化后得到的對象的順序與保存時的順序一致。
5、補充
補充一:上面我們舉得例子很簡單,要保存的成員變量要么是基本類型的要么是String類型的。但有時成員變量有可能是引用類型的,這是的情況會復雜一點。那就是當要對某對象進行序列化時,該對象中的引用變量所引用的對象也會被同時序列化,并且該對象中如果也有引用變量的話則該對象也將被序列化??偨Y(jié)說來就是在序列化的時候,對象中的所有引用變量所對應的對象將會被同時序列化。這意味著,引用變量類型也都要實現(xiàn)Serializable接口。當然其他對象的序列化都是自動進行的。所以我們只要保證里面的引用類型是都實現(xiàn)Serializable接口就行了,如果沒有的話,會在編譯時拋出異常。如果序列化的對象中包含沒有實現(xiàn)Serializable的成員變量的話,這時可以使用transient關(guān)鍵字,讓序列化的時候跳過該成員變量。使用關(guān)鍵字transient可以讓你在序列化的時候自動跳過transient所修飾的成員變量,在反序列化時這些變量會恢復到默認值。
補充二:如果某類實現(xiàn)了Serializable接口的話,其子類會自動編程可序列化的,這個好理解,繼承嘛。
補充三:在反序列化的時候,并不會調(diào)用對象的構(gòu)造器,這也好理解,如果調(diào)用了構(gòu)造器的話,對象的狀態(tài)不就又重新初始化了嗎。
補充四:我們說到對象序列化的是為了保存對象的狀態(tài),即對象的成員變量,所以靜態(tài)變量不會被序列化。
到此這篇關(guān)于Java創(chuàng)建對象的四種方式詳解的文章就介紹到這了,更多相關(guān)Java創(chuàng)建對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
struts2.5+框架使用通配符與動態(tài)方法常見問題小結(jié)
這篇文章主要介紹了struts2.5+框架使用通配符與動態(tài)方法常見問題 ,在文中給大家提到了Struts2.5框架使用通配符指定方法 ,需要的朋友可以參考下2018-09-09java.nio.file.WatchService?實時監(jiān)控文件變化的示例代碼
在?Java?語言中,從?JDK7?開始,新增了java.nio.file.WatchService類,用來實時監(jiān)控文件的變化,這篇文章主要介紹了java.nio.file.WatchService?實時監(jiān)控文件變化,需要的朋友可以參考下2022-05-05Spring Security學習之rememberMe自動登錄的實現(xiàn)
這篇文章主要給大家介紹了關(guān)于Spring Security學習之rememberMe自動登錄的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2020-06-06TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析
這篇文章主要介紹了TCP/IP協(xié)議中三次握手四次揮手的原理及流程分析,具有一定參考價值,需要的朋友可以了解下。2017-11-11Java超詳細講解SpringMVC如何獲取請求數(shù)據(jù)
Spring?MVC?是?Spring?提供的一個基于?MVC?設(shè)計模式的輕量級?Web?開發(fā)框架,本質(zhì)上相當于?Servlet,Spring?MVC?角色劃分清晰,分工明細,本章來講解SpringMVC如何獲取請求數(shù)據(jù)2022-04-04解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題
這篇文章主要介紹了解決Mybatis-Plus操作分頁后數(shù)據(jù)失效問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11SpringBoot使用ApplicationEvent&Listener完成業(yè)務解耦
這篇文章主要介紹了SpringBoot使用ApplicationEvent&Listener完成業(yè)務解耦示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-05-05