欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java 內(nèi)置接口 Serializable示例詳解

 更新時(shí)間:2022年11月02日 15:20:13   作者:kevinyan  
這篇文章主要為大家介紹了Java 內(nèi)置接口 Serializable示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

上一部分我們著重講了 Java 集合框架中在開發(fā)項(xiàng)目時(shí)經(jīng)常會(huì)被用到的數(shù)據(jù)容器,在講解、演示使用實(shí)踐的同時(shí),把這個(gè)過程中遇到的各種相關(guān)知識(shí)點(diǎn):泛型、Lambada、Stream 操作,一并給大家做了梳理。

從這篇開始我們進(jìn)入下一部分,用三到五部分給大家梳理一下,在用 Java 編程時(shí),那些我們繞不開的 interface;從最基本的 Serializable ComparableIterator 這些,再到 Java 為了支持函數(shù)式編程而提供的 Function、Predicateinterface

這些 Java 內(nèi)置提供的 interface 或多或少我們在寫 Java 代碼的時(shí)候都見過,有的甚至是潛移默化地在日常編碼中已經(jīng)實(shí)現(xiàn)過其中的一些 interface,只不過我們沒有察覺到罷了。相信通過閱讀著幾篇文章,一定會(huì)讓你在寫 Java 代碼時(shí)更清楚自己是在做什么,不會(huì)再被這些個(gè)似曾相識(shí)的 interface 困擾到。

本文大綱如下:

Serializable 接口

作為 Java 中那些繞不開的內(nèi)置接口 這個(gè)小系列的開篇文章,首先要給大家介紹的 interface 是 Serializable。

Serializable這個(gè)接口的全限定名(包名 + 接口名)是 java.io.Serializable,這里給大家說個(gè)小技巧,當(dāng)你看到一個(gè)類或者接口的包名前綴里包含java.io那就證明這個(gè)類 / 接口它跟數(shù)據(jù)的傳輸有關(guān)。

Serializable 是 Java 中非常重要的一個(gè)接口,如果一個(gè)類的對象是可序列化的,即對象在程序里可以進(jìn)行序列化和反序列化,對象的類就一定要實(shí)現(xiàn)Serializable接口。那么為什么要進(jìn)行序列化和反序列化呢?

序列化的意思是將對象的狀態(tài)轉(zhuǎn)換為字節(jié)流;反序列化則相反。換句話說,序列化是將 Java 對象轉(zhuǎn)換為靜態(tài)字節(jié)流(序列),然后我們可以將其保存到文件、數(shù)據(jù)庫或者是通過通過網(wǎng)絡(luò)傳輸,反序列化則是在我們讀取到字節(jié)流后再轉(zhuǎn)換成 Java 對象的過程;這也正好解釋了為什么Serializable 接口會(huì)歸屬到java.io包下面。

Serializable 是一個(gè)標(biāo)記型接口

雖說需要進(jìn)行序列化的對象,它們的類都需要實(shí)現(xiàn) Serializable 接口,但其實(shí)你會(huì)發(fā)現(xiàn),我們在讓一個(gè)類實(shí)現(xiàn) Serializable 接口時(shí),并沒有額外實(shí)現(xiàn)過什么抽線方法。

import java.io.Serializable;
public class Person implements Serializable {
    private String name;
    private int age;
}

比如向上面?zhèn)€類文件里的內(nèi)容,Person 類聲明實(shí)現(xiàn) Serializable 接口后,并沒有去實(shí)現(xiàn)什么抽象方法,IDE 也不會(huì)用紅線警告提示我們:“你有一個(gè)抽象方法需要實(shí)現(xiàn)” ,原因是 Serializable 接口里并沒有聲明抽象方法。

public interface Serializable {
}

這種不包含任何方法的 interface 被稱為標(biāo)記型接口,類實(shí)現(xiàn) Serializable接口不必實(shí)現(xiàn)任何特定方法,它只起標(biāo)記作用,讓 Java 知道該類可以用于對象序列化。

serializable Version UID

雖說一個(gè)類實(shí)現(xiàn)了 Serializable 接口的時(shí)候不需要實(shí)現(xiàn)特定的方法,但是經(jīng)常會(huì)看到一些實(shí)現(xiàn)了Serializable的類中,都有一個(gè)名為serialVersionUID類型為long的私有靜態(tài) 屬性。

import java.io.Serializable;
public static class Person implements Serializable {
    private static final long serialVersionUID = -7792628363939354385L;
    public String name;
    public int    age;
}

該屬性修飾符里使用了final即賦值后不可更改。Java 的對象序列化 API 在從讀取到的字節(jié)序列中反序列化出對象時(shí),使用 serialVersionUID 這個(gè)靜態(tài)類屬性來判斷:是否序列化對象時(shí)使用了當(dāng)前相同版本的類進(jìn)行的序列化。Java 使用它來驗(yàn)證保存和加載的對象是否具有相同的屬性,確保在序列化上是兼容的。

大多數(shù)的 IDE 都可以自動(dòng)生成這個(gè) serialVersionUID靜態(tài)屬性的值,規(guī)則是基于類名、屬性和相關(guān)的訪問修飾符。任何更改都會(huì)導(dǎo)致不同的數(shù)字,并可能導(dǎo)致 InvalidClassException。 如果一個(gè)實(shí)現(xiàn) Serializable 的類沒有聲明 serialVersionUID,JVM 會(huì)在運(yùn)行時(shí)自動(dòng)生成一個(gè)。但是,強(qiáng)烈建議每個(gè)可序列化類都聲明 serialVersionUID,因?yàn)槟J(rèn)生成的serialVersionUID依賴于編譯器,因此可能會(huì)導(dǎo)致意外的InvalidClassExceptions。

我上面那個(gè)例子里,Person 類的serialVersionUID是用 Intelij IDEA 自動(dòng)生成的,所以值看起來一大串,不是我自己些的。IDEA 默認(rèn)不會(huì)給可序列化類自動(dòng)生成 serialVersionUID 需要安裝一個(gè)插件。

這里給大家放一個(gè)截圖,插件的安裝和使用,網(wǎng)上有很多例子,大家需要的話動(dòng)手搜一下,這里就不再占用太多篇幅講怎么安裝和使用這個(gè)插件了。

Java 序列化與JSON序列化的區(qū)別

Java 的序列化與現(xiàn)在互聯(lián)網(wǎng)上 Web 應(yīng)用交互數(shù)據(jù)常用的 JSON 序列化并不是一回事兒,這是咱們需要注意的,像 Java、C#、PHP 這些編程語言,都有自己的序列化機(jī)制把自家的對象序列化成字節(jié)然后進(jìn)行傳輸或者保存,但是這些語言的序列化機(jī)制之間并不能互認(rèn),即用 Java 把對象序列化成字節(jié)、通過網(wǎng)絡(luò) RESTful API 傳給一個(gè) PHP 開發(fā)的服務(wù),PHP 是沒辦法反序列化還原出這個(gè)對象的。這樣才有了 JSON、XML、Protocol Buffer 這樣的更通用的序列化標(biāo)準(zhǔn)。

例如在實(shí)際項(xiàng)目開發(fā)的時(shí)候,Java 對象往往被序列化為 JSON、XML 后再在網(wǎng)絡(luò)上傳輸,如果對數(shù)據(jù)大小敏感的場景,會(huì)把 Java 對象序列化成空間占用更小的一些二進(jìn)制格式,比如 Protocol Buffer ( 分布式 RPC 框架 gRPC 的數(shù)據(jù)交換格式)。這樣做的好處是序列化后的數(shù)據(jù)可以被非 Java 應(yīng)用程序讀取和反序列化,例如,在 Web 瀏覽器中運(yùn)行的 JavaScript 可以在本地將對象序列化成 JSON 傳輸給 Java 寫的 API 接口,也可以從 Java API接口返回響應(yīng)中的 JSON 數(shù)據(jù),反序列化成 JavaScript 本地的對象 。

像上面列舉的這些對象序列化機(jī)制,是不需要我們的 Java 類實(shí)現(xiàn) Serializable 接口的。這些 JSON、XML 等格式的序列化類,通常使用 Java 反射來檢查類,配合一些特定的注解完成序列化。

Java序列化相較于 JSON 的優(yōu)勢

上面介紹了 JSON 這樣的通用序列化格式的優(yōu)勢,有的可能會(huì)問了,那還用 Java 序列化干啥。這里再給大家分析一下,Java 對象序列化雖然在通用性上不如 JSON 那些序列化格式,但是在 Java 生態(tài)內(nèi)部卻是十分好用的,其最聰明的一點(diǎn)是,它不僅能保存對象的副本,而且還會(huì)跟著對象里面的reference,把它所引用的對象也保存起來,然后再繼續(xù)跟蹤那些對象的reference,以此類推。

這個(gè)機(jī)制所涵蓋的范圍不僅包括對象的成員數(shù)據(jù),而且還包含數(shù)組里面的reference。如果你要自己實(shí)現(xiàn)對象序列化的話,那么編寫跟蹤這些鏈接的程序?qū)?huì)是一件非常痛苦的任務(wù)。但是,Java的對象序列化就能精確無誤地做到這一點(diǎn),毫無疑問,它的遍歷算法是做過優(yōu)化的。

另外你們在一些資料里看過 Java Bean 的定義

1、所有屬性為private

2、提供默認(rèn)構(gòu)造方法

3、提供getter和setter

4、實(shí)現(xiàn)java.io.Serializable接口

那么問題來了,為什么要進(jìn)行序列化?每個(gè)實(shí)體bean都必須實(shí)現(xiàn)serializabel接口嗎?以及我做項(xiàng)目的時(shí)候,沒有實(shí)現(xiàn)序列化,同樣沒什么影響,到底什么時(shí)候應(yīng)該進(jìn)行序列化操作呢?

這里轉(zhuǎn)載一個(gè)網(wǎng)上大佬對這個(gè)問題的解釋

首先第一個(gè)問題,實(shí)現(xiàn)序列化的兩個(gè)原因:

1、將對象的狀態(tài)保存在存儲(chǔ)媒體中以便可以在以后重新創(chuàng)建出完全相同的副本;

2、按值將對象從一個(gè)應(yīng)用程序域發(fā)送至另一個(gè)應(yīng)用程序域。實(shí)現(xiàn)serializabel接口的作用是就是可以把對象存到字節(jié)流,然后可以恢復(fù),所以你想如果你的對象沒實(shí)現(xiàn)序列化怎么才能進(jìn)行持久化和網(wǎng)絡(luò)傳輸呢,要持久化和網(wǎng)絡(luò)傳輸就得轉(zhuǎn)為字節(jié)流,所以在分布式應(yīng)用中及設(shè)計(jì)數(shù)據(jù)持久化的場景中,你就得實(shí)現(xiàn)序列化。

第二個(gè)問題,是不是每個(gè)實(shí)體bean都要實(shí)現(xiàn)序列化,答案其實(shí)還要回歸到第一個(gè)問題,那就是你的bean是否需要持久化存儲(chǔ)媒體中以及是否需要傳輸給另一個(gè)應(yīng)用,沒有的話就不需要,例如我們利用fastjson將實(shí)體類轉(zhuǎn)化成json字符串時(shí),并不涉及到轉(zhuǎn)化為字節(jié)流,所以其實(shí)跟序列化沒有關(guān)系。

第三個(gè)問題,有的時(shí)候并沒有實(shí)現(xiàn)序列化,依然可以持久化到數(shù)據(jù)庫。這個(gè)其實(shí)我們可以看看實(shí)體類中常用的數(shù)據(jù)類型,例如Date、String等等,它們已經(jīng)實(shí)現(xiàn)了序列化,而一些基本類型,數(shù)據(jù)庫里面有與之對應(yīng)的數(shù)據(jù)結(jié)構(gòu),從我們的類聲明來看,我們沒有實(shí)現(xiàn)serializabel接口,其實(shí)是在聲明的各個(gè)不同變量的時(shí)候,由具體的數(shù)據(jù)類型幫助我們實(shí)現(xiàn)了序列化操作。

另外需要注意的是,在NoSql數(shù)據(jù)庫中,并沒有與我們Java基本類型對應(yīng)的數(shù)據(jù)結(jié)構(gòu),所以在往nosql數(shù)據(jù)庫中存儲(chǔ)時(shí),我們就必須將對象進(jìn)行序列化,同時(shí)在網(wǎng)絡(luò)傳輸中我們要注意到兩個(gè)應(yīng)用中javabean的serialVersionUID要保持一致,不然就不能正常的進(jìn)行反序列化。

Java 類對象的序列化代碼演示

到這里 Serializable 需要了解的基礎(chǔ)知識(shí)就都給大家梳理出來了,這塊屬于選讀,用 Java 編程寫序列化代碼的場景并不是太多,不過有興趣就再接著往下看吧,有個(gè)印象,這樣以后寫代碼的時(shí)候,哪天用上了,還能快速想起來在哪看過,再回來翻看。

Java 對象序列化(寫入)由 ObjectOutputStream 完成,反序列化(讀取)由 ObjectInputStream 完成。ObjectInputStream 和 ObjectOutputStream 是分別繼承了 java.io.InputStream 和 java.io.OutputStream 抽象的實(shí)體類。 ObjectOutputStream 可以將對象的原型作為字節(jié)流寫入 OutputStream。然后我們可以使用 ObjectInputStream 讀取這些流。 ObjectOutputStream 中最重要的方法是:

public final void writeObject(Object o) throws IOException;

這個(gè)方法接收一個(gè)可序列化對象(實(shí)現(xiàn)了 Serializable 接口的類的對象)并將其轉(zhuǎn)換為字節(jié)序列。同樣,在ObjectInputStream 中最重要的方法是:

public final Object readObject() throws IOException, ClassNotFoundException;

此方法可以讀取字節(jié)流并將其轉(zhuǎn)換回 Java 對象。然后我們可以再使用類型轉(zhuǎn)換(Type Cast)將其轉(zhuǎn)換回原始的類型對象。

下面我們使用文章示例里的Person類再給大家演示一下 Java 的序列化代碼。

public class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    static String country = "ITALY";
    private int age;
    private String name;
    transient int height;
    // 省略 getter 和 setter
}

這里要注意一下, static 修飾的靜態(tài)屬性是類屬性,并不屬于對象,所以在序列化對象時(shí)不會(huì)把類中的靜態(tài)屬性序列化了,另外我們也可以使用 transient關(guān)鍵字修飾那些我們想在序列化過程中忽略調(diào)的對象屬性。

@Test 
public void serializingAndDeserializing_ThenObjectIsTheSame() () 
  throws IOException, ClassNotFoundException { 
    Person person = new Person();
    person.setAge(20);
    person.setName("Joe");
    // 用指定文件路徑--當(dāng)前目錄的 test_serialization.txt 文件創(chuàng)建 FileOutputStream。
    // 在寫入 FileOutputStream 時(shí), FileOutputStream 會(huì)在在項(xiàng)目目錄中創(chuàng)建文件
    // “test_serialization.txt”
    FileOutputStream fileOutputStream
      = new FileOutputStream("./test_serialization.txt");
    // 以 FileOutputStream 為底層輸出流創(chuàng)建對象輸出流 ObjectOutputStream
    ObjectOutputStream objectOutputStream 
      = new ObjectOutputStream(fileOutputStream);
    // 向 ObjectOutputStream 中寫入 person 對象
    objectOutputStream.writeObject(person);
    // 把數(shù)據(jù)從流中刷到磁盤上
    objectOutputStream.flush();
    objectOutputStream.close();
    // 用上面的文件路徑,創(chuàng)建文件輸入流
    FileInputStream fileInputStream
      = new FileInputStream("./test_serialization.txt");
    // 以文件輸入流創(chuàng)建對象輸入流 ObjectInputStream
    ObjectInputStream objectInputStream
      = new ObjectInputStream(fileInputStream);
    // 用對象輸入流讀取到文件中保存的序列化對象,反序列化成 Java Object 再轉(zhuǎn)換成 Person 對象
    Person p2 = (Person) objectInputStream.readObject();
    objectInputStream.close(); 
    assertTrue(p2.getAge() == person.getAge());
    assertTrue(p2.getName().equals(person.getName()));
}

上面這個(gè)單元測試?yán)锏拇a演示了,怎么把 Person 類的對象進(jìn)行 Java 序列化保存到文件中,再從文件中讀取對象被序列化后的字節(jié)序列,然后還原成Person類的對象。

因?yàn)槲覀兊膶谶€沒有設(shè)計(jì)到 Java IO 這塊的內(nèi)容,所以各種輸入輸出流就不過多進(jìn)行講解了,為了方便大家閱讀時(shí)理解上面的程序,我在上面程序注釋里已經(jīng)詳細(xì)注釋了每一步完成的操作,這些輸入輸出流我們等到講到 Java IO 體系的時(shí)候再詳細(xì)進(jìn)行講解。

總結(jié)

今天給大家梳理了 Java Serializable 接口的一些必須要了解的知識(shí),Serializable 接口在我們用 Java 編程的時(shí)候經(jīng)常見,但是很多人并不了解它的作用,因?yàn)樗闹饕饔眠€是用于標(biāo)記類是否是可序列化類,這樣 Java 的 ObjectOutputStream 和 ObjectInputStream 才能對類的對象進(jìn)行序列化和反序列化。

下一篇我們分享 Iterable 和 Iterator 這兩個(gè)名字看起差不多的 Java 內(nèi)置接口,請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論