Java序列化

概念

? ? ? ?Java中號(hào)稱一切皆是對(duì)象,在Java程序運(yùn)行過(guò)程中,都是借助對(duì)象來(lái)完成一系列我們想要的操作。但是對(duì)象它是存儲(chǔ)在內(nèi)存中的,如果我們機(jī)器關(guān)機(jī)了,這些對(duì)象也就不存在了。序列化就是將這些在內(nèi)存中運(yùn)行的對(duì)象的狀態(tài)信息轉(zhuǎn)換為一種可以存儲(chǔ)到磁盤(pán)上的過(guò)程,或者將對(duì)象的信息狀態(tài)進(jìn)行傳輸?shù)倪^(guò)程。在序列化期間,我們可以將當(dāng)前對(duì)象的狀態(tài)寫(xiě)入到一個(gè)臨時(shí)的存儲(chǔ)區(qū)或者持久化到硬盤(pán)中,這樣對(duì)象的信息就可以長(zhǎng)久存儲(chǔ)于硬盤(pán)介質(zhì)中,或者可以在網(wǎng)絡(luò)間進(jìn)行傳輸。

? ? ? ?有正向就有反向,對(duì)象信息持久化的過(guò)程是序列化過(guò)程,那么逆向就是反序列化過(guò)程,也就是說(shuō)可以通過(guò)讀取已經(jīng)持久化到硬盤(pán)上的對(duì)象信息,將其重新轉(zhuǎn)變成對(duì)象應(yīng)用于應(yīng)用程序中。序列化和反序列化應(yīng)用很廣泛,我們常說(shuō)的遠(yuǎn)程調(diào)用,Hibernate的持久化等等,這些都需要用到序列化和反序列化。

怎樣使用序列化

Serializable接口

首先最常見(jiàn)就是這個(gè)序列化標(biāo)識(shí)接口,說(shuō)它是標(biāo)識(shí)是因?yàn)樗慕涌谠创a中其實(shí)什么都沒(méi)有定義,純粹只是為了標(biāo)識(shí)而使用,一個(gè)類只有實(shí)現(xiàn)了這個(gè)接口,我們才能對(duì)它進(jìn)行序列化,否則是會(huì)報(bào)錯(cuò)的。序列化和反序列化的過(guò)程需要用到ObjectInputStream和ObjectOutputStream。

public class User implements Serializable{
 private String name;
 private int age;
 private Data birthday;
 private static final long serialVersionUID = 1L;
 //...省略getter和setter
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + "'" + 
 "age=" + age +
 ",birthday=" + birthday;
 }
}

? ? ? ?這里面的serialVersionUID主要是用于反序列化的時(shí)候做匹配用的,只有serialVersionUID的值一致,最后反序列化的時(shí)候才認(rèn)為它們是同一種類型,如果不定義,程序會(huì)默認(rèn)生成一個(gè)1L。在反序列化的時(shí)候,就是依據(jù)這個(gè)值來(lái)跟當(dāng)前虛擬機(jī)中的類中定義的數(shù)值比對(duì),如果一致,才會(huì)認(rèn)為是同一個(gè)版本,否則即使其他部分完全一樣,也無(wú)法順序反序列化。

? ? ? ?然后對(duì)User做一個(gè)簡(jiǎn)單的序列化demo,代碼如下:

public class Demo1 {
 public static void main(String[] args) {
   User user = new User();
   user.setName("Jack");
   user.setAge(20);
   user.setBirthday(new Date());

 //將對(duì)象序列化并寫(xiě)入文件中
   ObjectOutputStream oos = null;
   try {
     oos = new ObjectOutputStream(new FileInputStream("D:\\tempFile"));
     oos.writeObject(user);
   } catch (Exception e) {
     e.printStackTrace();
   } finally {
     if (oos != null) {
     try {
       oos.close();
     } catch (Exception ex) {
       ex.printStackTrace();
     }
   }
 }

 //讀取tempFile中的內(nèi)容,將其再次轉(zhuǎn)換為內(nèi)存中的對(duì)象(反序列化)
   File file = new File("D:\\tempFile");
   ObjectInputStream ois = null;
   try {
     ois = new ObjectInputStream(new FileInputStream(file));
     User newUser = (User)ois.readObject();
     System.out.println(newUser);
   } catch (Exception e) {
     e.printStackTrace();
   } finally {
   if (ois != null) {
   try {
     ois.close();
   } catch (Exception ex) {
     ex.printStackTrace();
   }
   }
 }
 }
}

? ? ? ?上面就是一個(gè)簡(jiǎn)單的利用Java API進(jìn)行序列化的操作,如果User類沒(méi)有實(shí)現(xiàn)Serializable接口,再次運(yùn)行程序的時(shí)候會(huì)發(fā)現(xiàn),它在writeObject的時(shí)候會(huì)報(bào)錯(cuò):java.io.NotSerializableException。仔細(xì)想想應(yīng)該也能猜個(gè)大概:一定是writeObject方法中在進(jìn)行序列化的時(shí)候,對(duì)傳入的對(duì)象進(jìn)行了Serializable類型的判斷,只有實(shí)現(xiàn)了它才能繼續(xù)進(jìn)行序列化。

Externalizable接口

? ? ? ?這個(gè)接口其實(shí)是繼承了Serializable,它更加靈活一點(diǎn),它里面定義了writeExternal和readExternal兩個(gè)方法分別用于序列化和反序列化使用。通過(guò)這兩個(gè)方法,我們可以自己決定需要序列化那些數(shù)據(jù)。如果對(duì)象中涉及到很少的屬性需要序列化,大多數(shù)屬性無(wú)需序列化,這種情況使用Externalizable接口是比較靈活的。當(dāng)然Serializable也同樣能實(shí)現(xiàn)只序列化出一部分屬性,只要將不需要序列化的屬性前面加一個(gè)trasient修飾符即可。

具體它的使用方式與前面Serializable接口的使用方式幾乎一樣,只是需要在實(shí)現(xiàn)了Externalizable接口的類中實(shí)現(xiàn)writeExternal和readExternal方法,在方法里面自己定義寫(xiě)出的屬性,如:

public void writeExternal(ObjectOutput out) throws IOException {
 //這里只寫(xiě)入了name和gender
 out.writeObject(name);
 out.writeObject(gender);
}
public void readExternal(ObjectInput in) throws IOException,
 ClassNotFoundException {
 //注意讀取的順序與write的順序保持一致
 name = (String)in.readObject();
 age = (String)in.readObject();
}

? ? ? ?以上這兩種序列化方式都是Java原生提供的序列化API,在一些特殊需求的場(chǎng)景中,可以借助于第三方包來(lái)實(shí)現(xiàn)序列化,例如:可以通過(guò)jackson包,利用ObjectMapper來(lái)實(shí)現(xiàn)JSON式的序列化;fastjson同樣也能實(shí)現(xiàn)json式序列化;還有比較有名的hessian序列化,hessian更加側(cè)重于數(shù)據(jù),有些場(chǎng)景下它的性能是比較好的,但是它也有有些不適用的場(chǎng)景,主要還是因?yàn)樗膬?nèi)部原理特性決定的。

hessian序列化

? ? ? ?首先它的一個(gè)最大特色就是跨語(yǔ)言,hessian提供了一整套的byte[]的寫(xiě)入規(guī)范。這樣其他語(yǔ)言在實(shí)現(xiàn)hessian序列化的時(shí)候就可以參照這套的標(biāo)準(zhǔn)規(guī)范,從而達(dá)到不同語(yǔ)言之間的兼容效果,因此hessian的序列化都是圍繞這byte數(shù)組來(lái)的。來(lái)看一下它的簡(jiǎn)單使用:

<!-- 首先需要引入hessian包 -->
<!-- https://mvnrepository.com/artifact/com.caucho/hessian -->
<dependency>
 <groupId>com.caucho</groupId>
 <artifactId>hessian</artifactId>
 <version>4.0.38</version>
</dependency>
public static <T> byte[] serialize(T obj) {
   byte[] bytes = null;
   ByteArrayOutputStream bos = new ByteArrayOutputStream();
   HessianOutput hessianOutput = new HessianOutput(bos);
   try {
     //obj必須實(shí)現(xiàn)Serializable接口
     hessianOutput.writeObject(obj);
     bytes = bos.toByteArray();
   } catch (IOException e) {
     e.printStackTrace();
   }
   return bytes;
}
?
@SuppressWarnings("unchecked")
public static <T> T deserialize(byte[] data) {
   if (data == null) return null;
   ByteArrayInputStream bis = new ByteArrayInputStream(data);
   HessianInput hessianInput = new HessianInput(bis);
   Object object = null;
   try {
     object = hessianInput.readObject();
   } catch (IOException e) {
     e.printStackTrace();
   }
   return (T)object;
}

? ? ? ?主要的序列化和反序列化的過(guò)程都在這里了。需注意一點(diǎn)的是:這里一定要使用HessianOutput和HessianInput,因?yàn)樵诰帉?xiě)代碼時(shí)會(huì)發(fā)現(xiàn),IDE還會(huì)提示一個(gè)Hessian2Output和Hessian2Input,這是兩個(gè)不同的版本,Hessian2的使用方法需要去查閱具體的文檔。另外經(jīng)過(guò)Hessian序列化出來(lái)的內(nèi)容要比原生的Java序列化暫用空間要少,這是因?yàn)镠essian內(nèi)部做了一些優(yōu)化操作。此外還有protobuf序列化,它是Google的一個(gè)序列化開(kāi)發(fā)工具,如果有需要,可以到網(wǎng)上查閱相關(guān)信息,目前還沒(méi)遇到過(guò)使用protobuf的情況,這里就不贅述了。

? ? ? ?除此之外,還有其他的一些序列化方案(比如:avro,kryo等等),而且那些序列化方式我沒(méi)有用過(guò),所以這里就不再一一分析使用了,如果有需要,可以網(wǎng)上搜索一下,基本資料比較齊全了。

? ? ? ?不同的序列化方式,各自的優(yōu)缺點(diǎn)都不同,但是可以確定的是:第三方的序列化工具都會(huì)對(duì)序列化的結(jié)果做一定的優(yōu)化,可以使得序列化的結(jié)果更加精簡(jiǎn),占用空間更少,但是這些都是有代價(jià),更少的空間占用,就意味著更多的時(shí)間去解析。對(duì)于普通的序列化需求,Java的原生API已經(jīng)可以滿足,除非特別特殊的場(chǎng)景需要使用第三方工具的某些特性,否則沒(méi)必要糾結(jié)于到底哪種性能更好,只有哪種更合適而已。試想:如果出現(xiàn)一個(gè)很完美的序列化方案,其他的序列化方式早就被淘汰了,既然存在,就一定有獨(dú)到之處。

Java原生序列化原理

? ? ? ?在此之前首先看一個(gè)例子:Java中有一個(gè)類叫ArrayList,開(kāi)發(fā)中我們會(huì)經(jīng)常遇到,稍微了解ArrayList源碼設(shè)計(jì)的都應(yīng)該知道,它的內(nèi)部實(shí)際上是維護(hù)了一個(gè)Object數(shù)組elementData作為數(shù)據(jù)的存儲(chǔ)容器。對(duì)于ArrayList對(duì)象的增刪改查實(shí)際上都是對(duì)這個(gè)elementData進(jìn)行操作得出的結(jié)果,而且ArrayList是實(shí)現(xiàn)了Serializable接口的,所以它是可以被序列化的,但是它的elementData這個(gè)屬性前面加了一個(gè)transient修飾符,前面介紹過(guò),如果屬性中加了transient修飾符,在序列化的時(shí)候是會(huì)被忽略的,那么問(wèn)題就來(lái)了:我們?cè)谡P蛄谢疉rrayList對(duì)象以后,再對(duì)其進(jìn)行反序列化的時(shí)候,其實(shí)仍然可以得到它內(nèi)部存儲(chǔ)的那些數(shù)據(jù),這是怎么做到的?這里就牽扯到了序列化的內(nèi)部實(shí)現(xiàn)原理了,仔細(xì)查閱ArrayList源碼可以發(fā)現(xiàn),該類中定義了writeObject和readObject兩個(gè)私有方法,這兩個(gè)會(huì)在序列化的時(shí)候使用到。

? ? ? ?結(jié)合前面的代碼示例,可以看到Java原生序列化有兩個(gè)關(guān)鍵的類:ObjectOutputStream和ObjectInputStream。對(duì)于序列化,需要用到ObjectOutputStream的writeObject方法,這里直接給出源碼中writeObject內(nèi)部的調(diào)用關(guān)系:writeObject --》writeObject0 --》writeOrdinaryObject --》writeSerialData --》invokeWriteObject。(這里的調(diào)用關(guān)系主要給出的是對(duì)于一個(gè)普通對(duì)象序列化時(shí)的調(diào)用關(guān)系,有些特殊的類型,比如:String,Enum等可能中間有所不一樣)。下面是invokeWriteObject方法的源代碼

void invokeWriteObject(Object obj, ObjectOutputStream out)
 throws IOException, UnsupportedOperationException
{
   requireInitialized();
   if (writeObjectMethod != null) {
     try {
       writeObjectMethod.invoke(obj, new Object[]{ out });
     } catch (InvocationTargetException ex) {
       Throwable th = ex.getTargetException();
       if (th instanceof IOException) {
         throw (IOException) th;
       } else {
         throw MiscException(th);
       }
     } catch (IllegalAccessException ex) {
       // should not occur, as access checks have been suppressed
       throw new InternalError(ex);
     }
    } else {
     throw new UnsupportedOperationException();
   }
}

? ? ? ?注意:核心是writeObjectMethod.invoke(obj, new Object[]{ out })這一句。這一句很明顯就是一個(gè)反射的語(yǔ)法,writeObjectMethod指的就是序列化對(duì)象所屬類中定義的writeObject方法對(duì)象。如果當(dāng)前對(duì)象所屬類中沒(méi)有定義writeObject這么一個(gè)方法,就會(huì)調(diào)用一個(gè)默認(rèn)的defaultWriteFields方法(走defaultWriteFields分支的判斷邏輯可在writeSerialData方法中查看)。

現(xiàn)在再來(lái)回想前面說(shuō)過(guò)的:如果要對(duì)一個(gè)對(duì)象進(jìn)行序列化,該對(duì)象所屬的類必須實(shí)現(xiàn)Serializable接口,這是為什么?可以在writeObject0 方法中找到答案:

這是writeObject0 方法中的一段代碼:

...
if (obj instanceof String) {
 writeString((String) obj, unshared);
} else if (cl.isArray()) {
 writeArray(obj, desc, unshared);
} else if (obj instanceof Enum) {
 writeEnum((Enum<?>) obj, desc, unshared);
} else if (obj instanceof Serializable) {
 writeOrdinaryObject(obj, desc, unshared);
} else {
 if (extendedDebugInfo) {
 throw new NotSerializableException(
 cl.getName() + "\n" + debugInfoStack.toString());
 } else {
 throw new NotSerializableException(cl.getName());
 }
}
...

? ? ? ?可以看到,第三個(gè)else if 判斷中對(duì)obj進(jìn)行了類型判斷,判斷其是否是Serializable類型,說(shuō)明前面的猜想是對(duì)的。除此之外:它還進(jìn)行了String,數(shù)組以及枚舉類型的判斷。所以說(shuō)如果待序列化的對(duì)象如果是上面的任何一種都可以進(jìn)行序列化,否則會(huì)拋出NotSerializableException。

? ? ? ?根據(jù)同樣的思路可以查閱ObjectInputStream源碼中的readObject方法調(diào)用鏈:readObject --》readObject0 --》 readOrdinaryObject --》readSerialData --》invokeReadObject。

? ? ? ?可以發(fā)現(xiàn):反序列化中的調(diào)用鏈跟序列化中的調(diào)用鏈非常相似。所以分析它的原理完全可以參照序列化中分析思路。它同樣在invokeReadObject方法內(nèi)部有一句核心的反射調(diào)用:readObjectMethod.invoke(obj, new Object[]{ in });利用反射調(diào)用readObject方法(如果對(duì)象所屬類自定義了該方法)。如果沒(méi)有,在readSerialData方法中就會(huì)直接走defaultReadFields方法,而不會(huì)走invokeReadObject方法了。而且在readObject0方法中也存在了大量的類型判斷,而且用的是switch-case,具體可以查閱源碼,這里就不再貼出代碼了。

? ? ? ?現(xiàn)在再來(lái)考慮:為什么ArrayList需要這么設(shè)計(jì)它的序列化方式,直接序列化elementData不是也可以?其實(shí)這是一個(gè)空間存儲(chǔ)效率的問(wèn)題,因?yàn)樵谡5氖褂脠?chǎng)景中,elementData數(shù)組內(nèi)部其實(shí)都是存不滿的,可以說(shuō)99%的情況都不會(huì)滿,而數(shù)組是固定長(zhǎng)度的,剩余的空間其實(shí)存儲(chǔ)的都是null。如果直接序列化elementData中的內(nèi)容,就會(huì)發(fā)現(xiàn)有很多null也被序列化出來(lái)的,但是這些內(nèi)容是完全沒(méi)用的,這就浪費(fèi)了效率和存儲(chǔ)空間。而ArrayList定義了writeObject方法,恰恰就解決了這個(gè)問(wèn)題,在該方法中將elementData數(shù)組內(nèi)部真正有用的數(shù)據(jù)序列化出去,這樣既節(jié)約了空間,又提高了效率。

序列化的使用場(chǎng)景

持久化操作或網(wǎng)絡(luò)傳輸

? ? ? ?一個(gè)最常見(jiàn)的場(chǎng)景就是持久化操作,將對(duì)象作為一種數(shù)據(jù)存儲(chǔ)到某種介質(zhì)中一遍將來(lái)復(fù)用,這時(shí)就會(huì)使用到序列化技術(shù),不同的是實(shí)現(xiàn)序列化依賴的技術(shù)不同,但是核心仍然是序列化的概念。

? ? ? ?同樣的對(duì)于需要進(jìn)行網(wǎng)絡(luò)減傳輸?shù)模残枰獙?duì)其進(jìn)行序列化,否則對(duì)象只能存在與當(dāng)前虛擬機(jī)的內(nèi)存中,無(wú)法通過(guò)夸虛擬機(jī),跨網(wǎng)絡(luò)的數(shù)據(jù)通信,比如:現(xiàn)在常用的一些消息中間件,核心功能就是消息的傳遞,而且是不在不同環(huán)境下的消息傳遞,需要大量的數(shù)據(jù)在網(wǎng)絡(luò)中傳輸,如果沒(méi)有序列化,這些傳輸是完全無(wú)法完成的。

? ? ? ?這個(gè)是所有使用場(chǎng)景的核心,其它的應(yīng)用場(chǎng)景基本不會(huì)逃出這持久化和網(wǎng)絡(luò)傳輸這兩個(gè)概念。

RPC框架

? ? ? ?也就是遠(yuǎn)程調(diào)用框架,例如dubbo,本質(zhì)上說(shuō),遠(yuǎn)程調(diào)用就必須將對(duì)象序列化后通過(guò)網(wǎng)絡(luò)傳輸?shù)竭h(yuǎn)程調(diào)用機(jī)器上,這樣遠(yuǎn)程調(diào)用的機(jī)器無(wú)需實(shí)現(xiàn)具體的對(duì)象,僅僅通過(guò)網(wǎng)絡(luò),就可以完成一系列原本無(wú)法在本地完成的工作。而且RPC的調(diào)用方式極大的保護(hù)了代碼的安全,發(fā)放給調(diào)用端的可能僅僅是定義的一些接口,但是在調(diào)用端使用的時(shí)候,它的具體實(shí)現(xiàn)的類的對(duì)象則是由網(wǎng)絡(luò)傳輸提供,這樣避免了代碼的泄露風(fēng)險(xiǎn)。

? ? ? ?其實(shí)RPC的序列化核心場(chǎng)景就是:網(wǎng)絡(luò)傳輸。

緩存

? ? ? ?現(xiàn)在比較熱門(mén)的話題之一,現(xiàn)在系統(tǒng)中幾乎必不可少的一個(gè)模塊,為了提高系統(tǒng)的響應(yīng)速度,我們可能會(huì)用到大量的緩存,有些緩存是純內(nèi)存級(jí)別的,就是程序關(guān)閉后,數(shù)據(jù)丟失。但是現(xiàn)在一些緩存框架,如:Redis等,可以實(shí)現(xiàn)定期的持久化,用以保證程序出現(xiàn)問(wèn)題時(shí)不會(huì)完全丟失數(shù)據(jù),可能只是丟失很少一部分?jǐn)?shù)據(jù)。這個(gè)持久化的過(guò)程就需要涉及到序列化。

? ? ? ?緩存序列化核心場(chǎng)景:持久化操作。

其他

單例中的序列化安全問(wèn)題

? ? ? ?面試中常常被問(wèn)到的一個(gè)問(wèn)題就是:?jiǎn)卫J降膸追N實(shí)現(xiàn)方式。這里可以強(qiáng)烈推薦H大的 單例模式的七種實(shí)現(xiàn)。但是大多的單例模式設(shè)計(jì)方案都無(wú)法保證序列化下的安全,換句話說(shuō):在將對(duì)象序列化以后,再次進(jìn)行反序列化,可以繞過(guò)單例模式的檢查機(jī)制,從而導(dǎo)致內(nèi)存中存在兩個(gè)甚至更多個(gè)相同類型的對(duì)象,這對(duì)于單例模式來(lái)說(shuō)是破壞性的,因?yàn)閱卫脑瓌t就是:運(yùn)行期間,內(nèi)存中只會(huì)存在一個(gè)對(duì)象。常用的一種解決方案就是使用readResolve()方法來(lái)避免此事發(fā)生(可網(wǎng)上搜索相關(guān)用法,比較簡(jiǎn)單)。

? ? ? ?在七種單例模式的寫(xiě)法當(dāng)中,有一種采用枚舉的方式,它是《Effective Java》作者Josh Bloch提倡使用的一種方式,雖然在實(shí)際應(yīng)用中很少使用這種方式(因?yàn)槊杜e方式的應(yīng)用場(chǎng)景非常狹小,很多場(chǎng)景下是不能使用的)。但是Josh Bloch既然提倡使用,肯定是有一定道理的。

寫(xiě)法簡(jiǎn)單

首先枚舉單例的寫(xiě)法非常簡(jiǎn)單,三行代碼即可搞定:

public enum Singleton{
 INSTANCE;
}

枚舉類型可以自己處理序列化

? ? ? ?前面在分析序列化的時(shí)候,可以發(fā)現(xiàn),在writeObject0方法中其實(shí)是有枚舉類型的判斷的。而且Java的規(guī)范中明確說(shuō)明了:每一個(gè)枚舉類型及其定義的枚舉變量在JVM中都是唯一的,在枚舉類型的序列化和反序列化上,Java做了特殊的規(guī)定:在對(duì)枚舉對(duì)象進(jìn)行序列化的時(shí)候,Java僅僅是將枚舉對(duì)象的name屬性輸出,在反序列化的時(shí)候,通過(guò)java.lang.Enum的valueOf方法,根據(jù)name查找對(duì)應(yīng)的枚舉對(duì)象;同時(shí)編譯器不允許任何對(duì)該序列化過(guò)程的定制。禁用了writeObject、readObject、readObjectNoData、writeReplace和readResolve方法。

? ? ? ?簡(jiǎn)單來(lái)說(shuō)就是:Java對(duì)枚舉類型的序列化和反序列化有特殊規(guī)定,不允許定制,那些傳統(tǒng)的方法全部失效,而且序列化寫(xiě)出的時(shí)候只有一個(gè)name屬性,反序列化的時(shí)候,在虛擬機(jī)中通過(guò)name查找相應(yīng)的枚舉對(duì)象。而且枚舉對(duì)象在虛擬機(jī)內(nèi)部是唯一的。

? ? ? ?所以說(shuō)枚舉類型的安全性保障是JVM提供的,它是非??煽康?。

枚舉實(shí)例的創(chuàng)建是線程安全的

現(xiàn)在以前面的Singleton的枚舉寫(xiě)法為例,使用cfr工具對(duì)其進(jìn)行反編譯可以看到:

//使sugarenums參數(shù)設(shè)置為false,可以得到較為詳細(xì)的編譯結(jié)果
root@ubuntu:/usr/local/workspace/demo3# java -jar ../cfr_0_132.jar Singleton.class --sugarenums false
/*
 * Decompiled with CFR 0_132.
 */
public final class Singleton extends Enum<Singleton> {
 public static final /* enum */ Singleton INSTANCE = new Singleton();
 private static final /* synthetic */ Singleton[] $VALUES;
?
 public static Singleton[] values() {
 return (Singleton[])$VALUES.clone();
 }
?
 public static Singleton valueOf(String string) {
 return Enum.valueOf(Singleton.class, string);
 }
?
 private Singleton() {
 super(string, n);
 }
?
 static {
 $VALUES = new Singleton[]{INSTANCE};
 }
}

? ? ? ?可以看到,Singleton實(shí)際上是繼承了Enum類,并且將其定義成成了final類型,無(wú)法被繼承了。它的內(nèi)部幾乎都是static屬性的,static類型的屬性會(huì)在類被加載之后被初始化。當(dāng)一個(gè)Java類第一次被真正使用到的時(shí)候,會(huì)對(duì)它的靜態(tài)資源(static修飾)進(jìn)行初始化以及Java類的加載,這個(gè)過(guò)程是線程安全的。這個(gè)線程安全是虛擬機(jī)保證的,所以說(shuō)它同樣是可靠的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java序列化機(jī)制 序列化和反序列化 Java序列化是Java內(nèi)建的數(shù)據(jù)(對(duì)象)持久化機(jī)制,通過(guò)序列化可以將運(yùn)行時(shí)...
    0x70e8閱讀 510評(píng)論 0 1
  • 1. 對(duì)象序列化定義 Java平臺(tái)允許我們?cè)趦?nèi)存中創(chuàng)建可復(fù)用的Java對(duì)象,但一般情況下,只有當(dāng)JVM處于運(yùn)行時(shí),...
    mance閱讀 305評(píng)論 0 0
  • 正如前文《Java序列化心得(一):序列化設(shè)計(jì)和默認(rèn)序列化格式的問(wèn)題》中所提到的,默認(rèn)序列化方法存在各種各樣的問(wèn)題...
    登高且賦閱讀 8,787評(píng)論 0 19
  • 如果你只知道實(shí)現(xiàn) Serializable 接口的對(duì)象,可以序列化為本地文件。那你最好再閱讀該篇文章,文章對(duì)序列化...
    jiangmo閱讀 563評(píng)論 0 2
  • 序言 將 Java 對(duì)象序列化為二進(jìn)制文件的 Java 序列化技術(shù)是 Java 系列技術(shù)中一個(gè)較為重要的技術(shù)點(diǎn),在...
    martin6699閱讀 387評(píng)論 0 1

友情鏈接更多精彩內(nèi)容