Java創(chuàng)建對象的四種方式詳解
前言
以String類為例
String string = null; Class class1 = String.class;// 該方法最為安全可靠,程序性能更高。 Class class2 = string.getClass(); Class class3 = Class.forName(“java.lang.String”);// 可能拋出ClassNotFoundException異常
一旦獲取了該類所對應(yīng)的Class對象之后,就可以通過調(diào)用Class對象的方法來獲得該對象和該類的真實(shí)信息了
1. new 出一個(gè)對象
例:
String s = new String("abc");2. 利用反射創(chuàng)建對象
1.創(chuàng)建對象
通過反射來生成對象有如下兩種方式:
(1)使用Class對象的newInstance()方法來創(chuàng)建該Class對象對應(yīng)類的實(shí)例。但是這種方式要求該Class對象的對應(yīng)類有默認(rèn)的構(gòu)造器,而執(zhí)行newInstance()方法時(shí)實(shí)際上是利用默認(rèn)構(gòu)造器來創(chuàng)建該類的實(shí)例。
(2)先使用Class對象獲取指定的Constructor對象,再調(diào)用Construtor對象的newInstance()方法來創(chuàng)建該Class對象對應(yīng)類的實(shí)例。通過這種方式可以選擇使用某個(gè)類的指定構(gòu)造器來創(chuàng)建實(shí)例。
另外,如果我們不想利用默認(rèn)構(gòu)造器來創(chuàng)建java對象,而想利用指定的構(gòu)造器來創(chuàng)建java對象,則需要利用Construtor對象,每個(gè)Construtor對應(yīng)一個(gè)構(gòu)造器,為了利用指定構(gòu)造器來創(chuàng)建java對象,需要如下三個(gè)步驟:
(1)獲取該Class對象;
(2)利用該Class對象的getConstrutor方法來獲取指定的構(gòu)造器;
(3)調(diào)用Construtor的newInstance方法來創(chuàng)建Java對象。
2.調(diào)用方法
獲取到某個(gè)類的Class對象之后,可以通過該Class對象的getMethods方法或者getMethod方法獲取全部或指定方法。
每個(gè)Method對象對應(yīng)一個(gè)方法,獲得Method對象后,程序就可通過該Method來調(diào)用對應(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)用對應(yīng)的方法時(shí),Java會(huì)要求程序必要要有調(diào)用該方法的權(quán)限。如果程序確實(shí)需要調(diào)用該對象的私有方法,則可先調(diào)用Method對象的:
setAccessible(boolean flag);方法,將Method對象的accessoble標(biāo)志設(shè)置為指示的布爾值。
布爾值為true,則表示該Method在使用時(shí)應(yīng)該取消Java語言訪問權(quán)限檢查;
布爾值為false,則表示該Method在使用時(shí)應(yīng)該實(shí)施Java語言訪問權(quán)限檢查;
3.訪問屬性值
通過Class對象的getFields或getField方法可以獲取該類所包括的全部Field(屬性)或指定Field,F(xiàn)ield提供了如下兩組方法來訪問屬性:
getXxx(Object obj);獲取obj對象該Field的屬性值,此處的Xxx對應(yīng)8個(gè)基本類型,如果該屬性的類型是引用類型則取消get后面的Xxx。
setXxx(Object obj,Xxx val);將obj對象的該Field設(shè)置成val值,此處的Xxx對應(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)建對象
什么是"clone"?
在實(shí)際編程過程中,我們常常要遇到這種情況:有一個(gè)對象A,在某一時(shí)刻A中已經(jīng)包含了一些有效值,此時(shí)可能 會(huì)需要一個(gè)和A完全相同新對象B,并且此后對B任何改動(dòng)都不會(huì)影響到A中的值,也就是說,A與B是兩個(gè)獨(dú)立的對象,但B的初始值是由A對象確定的。在 Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實(shí)現(xiàn)clone()方法是其中最簡單,也是最高效的手段。
Java的所有類都默認(rèn)繼承java.lang.Object類,在java.lang.Object類中有一個(gè)方法clone()。JDK API的說明文檔解釋這個(gè)方法將返回Object對象的一個(gè)拷貝。要說明的有兩點(diǎn):一是拷貝對象返回的是一個(gè)新對象,而不是一個(gè)引用。二是拷貝對象與用 new操作符返回的新對象的區(qū)別就是這個(gè)拷貝已經(jīng)包含了一些原來對象的信息,而不是對象的初始信息。
1. Clone&Copy
假設(shè)現(xiàn)在有一個(gè)Employee對象,Employee tobby =new Employee(“CMTobby”,5000),通 常我們會(huì)有這樣的賦值Employee cindyelf=tobby,這個(gè)時(shí)候只是簡單了copy了一下reference,cindyelf和tobby都指向內(nèi)存中同一個(gè)object,這樣cindyelf或者tobby的一個(gè)操作都可能影響到對方。打個(gè)比方,如果我們通過cindyelf.raiseSalary()方法改變了salary域的值,那么tobby通過getSalary()方法得到的就是修改之后的salary域的值,顯然這不是我們愿意看到的。我們希望得到tobby的一個(gè)精確拷貝,同時(shí)兩者互不影響,這時(shí)候我們就可以使用Clone來滿足我們的需求。Employee cindy=tobby.clone(),這時(shí)會(huì)生成一個(gè)新的Employee對象,并且和tobby具有相同的屬性值和方法。
2. Shallow Clone&Deep
Clone Clone是如何完成的呢?Object在對某個(gè)對象實(shí)施Clone時(shí)對其是一無所知的,它僅僅是簡單地執(zhí)行域?qū)τ虻腸opy,這就是Shallow Clone。這樣,問題就來了咯,以Employee為例,它里面有一個(gè)域hireDay不是基本型別的變量,而是一個(gè)reference變量,經(jīng)過Clone之后就會(huì)產(chǎn)生一個(gè)新的Date型別的reference,它和原始對象中對應(yīng)的域指向同一個(gè)Date對象,這樣克隆類就和原始類共享了一部分信息,而這樣顯然是不利的,過程下圖所示:

這個(gè)時(shí)候我們就需要進(jìn)行deep Clone了,對那些非基本型別的域進(jìn)行特殊的處理,例如本例中的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()方法的保護(hù)機(jī)制
在Object中Clone()是被申明為protected的,這樣做是有一定的道理的,以Employee
類為例,通過申明為protected,就可以保證只有Employee類里面才能“克隆”Employee對象,原理可以參考我前面關(guān)于public、protected、private的學(xué)習(xí)筆記。
4. Clone()方法的使用
Clone()方法的使用比較簡單,注意如下幾點(diǎn)即可:
a. 什么時(shí)候使用shallow Clone,什么時(shí)候使用deep Clone,這個(gè)主要看具體對象的域是什么性質(zhì)的,基本型別還是reference variable
b. 調(diào)用Clone()方法的對象所屬的類(Class)必須implements Clonable接口,否則在調(diào)用Clone方法的時(shí)候會(huì)拋出CloneNotSupportedException。
4. 利用反序列化創(chuàng)建對象
1、為什么要進(jìn)行序列化
再介紹之前,我們有必要先了解下對象的生命周期,我們知道Java中的對象都是存在于堆內(nèi)存中的,而堆內(nèi)存是可以被垃圾回收器不定期回收的。從對象被創(chuàng)建到被回收這一段時(shí)間就是Java對象的生命周期,也即Java對象只存活于這個(gè)時(shí)間段內(nèi)。
對象被垃圾回收器回收意味著對象和對象中的成員變量所占的內(nèi)存也就被回收,這意味著我們就再也得不到該對象的任何內(nèi)容了,因?yàn)橐呀?jīng)被銷毀了嘛,當(dāng)然我們可以再重新創(chuàng)建,但這時(shí)的對象的各種屬性都又被重新初始化了。所以如果我們需要保存某對象的狀態(tài),然后再在未來的某段時(shí)間將該對象再恢復(fù)出來的話,則必須要在對象被銷毀即被垃圾回收器回收之前保存對象的狀態(tài)。要保存對象狀態(tài)的話,我們可以使用文件、數(shù)據(jù)庫,也可以使用序列化,這里我們主要介紹對象序列化。我們很有必要了解這方面的內(nèi)容,因?yàn)閷ο笮蛄谢粌H在保存對象狀態(tài)時(shí)可以被用到(對象持久化),在Java中的遠(yuǎn)程方法調(diào)用RMI也會(huì)被用到,在網(wǎng)絡(luò)中要傳輸對象的話,則必須要對對象進(jìn)行序列化,關(guān)于RMI有機(jī)會(huì)我會(huì)再專門開貼介紹。
簡單總結(jié)起來,進(jìn)行對象序列化的話的主要原因就是實(shí)現(xiàn)對象持久化和進(jìn)行網(wǎng)絡(luò)傳輸,這里先只介紹怎樣通過對象序列化保存對象的狀態(tài)。
下面我們通過一個(gè)簡單的例子來介紹下如何進(jìn)行對象序列化。
2、怎樣進(jìn)行對象序列化
假設(shè)我們要保存Person類的某三個(gè)對象的name、age、height這三個(gè)成員變量,當(dāng)然這里只是簡單舉例
我們先看下Person類,要序列化某個(gè)類的對象的話,則該類必要實(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 方法將對象轉(zhuǎn)換為可以寫出到流的數(shù)據(jù)。所以整個(gè)流程是這樣的: 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 {
//需要一個(gè)文件輸出流和對象輸出流;文件輸出流用于將字節(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、對象的反序列化
我們存儲(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對象的序列化與反序列化
*/
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è)文件輸出流和對象輸出流;文件輸出流用于將字節(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
從添加的代碼我們可以看到進(jìn)行反序列化也很簡單,主要用到的流是FileInputstream和ObjectInputstream正好與存儲(chǔ)時(shí)用到的流相對應(yīng)。另外從結(jié)果順序我們可以看到反序列化后得到對象的順序與序列化時(shí)的順序一致。
4、總結(jié)
進(jìn)行對象序列化主要目的是為了保存對象的狀態(tài)(成員變量)。
進(jìn)行序列化主要用到的流是FileOutputStream和ObjectOutputStream。FileOutputStream主要用于連接磁盤文件,并把字節(jié)寫出到該磁盤文件;ObjectOutputStream主要用于將對象寫出為可轉(zhuǎn)化為字節(jié)的數(shù)據(jù)。
要將某類的對象序列化,則該類必須實(shí)現(xiàn)Serializable接口,該接口僅是一個(gè)標(biāo)志,告訴JVM該類的對象可以被序列化。如果某類未實(shí)現(xiàn)Serializable接口,則該類對象不能實(shí)現(xiàn)序列化。
保存狀態(tài)的目的就是為了在未來的某個(gè)時(shí)候再恢復(fù)保存的內(nèi)容,這可以通過反序列化來實(shí)現(xiàn)。對象的反序列化過程與序列化正好相反,主要用到的兩個(gè)流是FileInputstream和ObjectInputStream。
反序列化后得到的對象的順序與保存時(shí)的順序一致。
5、補(bǔ)充
補(bǔ)充一:上面我們舉得例子很簡單,要保存的成員變量要么是基本類型的要么是String類型的。但有時(shí)成員變量有可能是引用類型的,這是的情況會(huì)復(fù)雜一點(diǎn)。那就是當(dāng)要對某對象進(jìn)行序列化時(shí),該對象中的引用變量所引用的對象也會(huì)被同時(shí)序列化,并且該對象中如果也有引用變量的話則該對象也將被序列化。總結(jié)說來就是在序列化的時(shí)候,對象中的所有引用變量所對應(yīng)的對象將會(huì)被同時(shí)序列化。這意味著,引用變量類型也都要實(shí)現(xiàn)Serializable接口。當(dāng)然其他對象的序列化都是自動(dòng)進(jìn)行的。所以我們只要保證里面的引用類型是都實(shí)現(xiàn)Serializable接口就行了,如果沒有的話,會(huì)在編譯時(shí)拋出異常。如果序列化的對象中包含沒有實(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)用對象的構(gòu)造器,這也好理解,如果調(diào)用了構(gòu)造器的話,對象的狀態(tài)不就又重新初始化了嗎。
補(bǔ)充四:我們說到對象序列化的是為了保存對象的狀態(tài),即對象的成員變量,所以靜態(tài)變量不會(huì)被序列化。
到此這篇關(guān)于Java創(chuàng)建對象的四種方式詳解的文章就介紹到這了,更多相關(guān)Java創(chuàng)建對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
hadoop運(yùn)行java程序(jar包)并運(yùn)行時(shí)動(dòng)態(tài)指定參數(shù)
這篇文章主要介紹了hadoop如何運(yùn)行java程序(jar包)并運(yùn)行時(shí)動(dòng)態(tài)指定參數(shù),使用hadoop 運(yùn)行 java jar包,Main函數(shù)一定要加上全限定類名,需要的朋友可以參考下2021-06-06
Java如何利用return結(jié)束方法調(diào)用
這篇文章主要介紹了Java如何利用return結(jié)束方法調(diào)用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
SpringCloud Zuul服務(wù)功能與使用方法解析
這篇文章主要介紹了SpringCloud Zuul服務(wù)功能與使用方法解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
詳解Java編程中static關(guān)鍵字和final關(guān)鍵字的使用
這篇文章主要介紹了詳解Java編程中static關(guān)鍵字和final關(guān)鍵字的使用,是Java入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-09-09

