java-Transient關鍵字、Volatile關鍵字介紹和序列化、反序列化機制、單例類序列化

 - Transient關鍵字

Java的serialization提供了一種持久化對象實例的機制。當持久化對象時,可能有一個特殊的對象數據成員,我們不想
用serialization機制來保存它。爲了在一個特定對象的一個域上關閉serialization,可以在這個域前加上關鍵字transient。
transient是Java語言的關鍵字,用來表示一個域不是該對象串行化的一部分。當一個對象被串行化的時候,
transient型變量的值不包括在串行化的表示中,然而非transient型的變量是被包括進去的。
注意static變量也是可以串行化的
同時,通過反序列化得到的對象是不同的對象,而且得到的對象不是通過構造器得到的,
也就是說反序列化得到的對象不執行構造器。

下面進行測試:
新建一個javabean類,代碼:

import java.util.Date;
public class LoggingInfo implements java.io.Serializable   
{   
    private static Date loggingDate = new Date();   
    private String uid;   
    private transient String pwd;   

    LoggingInfo(String user, String password)   
    {   
        uid = user;   
        pwd = password;   
    }   
    public String toString()   
    {   
        String password=null;   
        if(pwd == null)   
        {   
        password = "NOT SET";   
        }   
        else  
        {   
            password = pwd;   
        }   
        return "logon info: \n   " + "user: " + uid +   
            "\n   logging date : " + loggingDate.toString() +   
            "\n   password: " + password;   
    }   
}   

測試類,調用上面的javabean類,進行序列化和反序列化,代碼如下:

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class Test{
    public static void main(String[] args){
        LoggingInfo loggingInfo = new LoggingInfo("longyin", "123");
        System.out.println("寫入:"+loggingInfo);
        ObjectOutputStream objectOutput = null;
        ObjectInputStream objectInput = null;
        try {
            objectOutput = new ObjectOutputStream(
                    new FileOutputStream("test.txt"));
            objectInput = new ObjectInputStream(
                    new FileInputStream("test.txt"));
            objectOutput.writeObject(loggingInfo);
            LoggingInfo info = (LoggingInfo) objectInput.readObject();
            System.out.println("讀取:"+info);
            System.out.println("是否相等:"+(info==loggingInfo));
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }finally{
            if (objectInput != null) {
                try {
                    objectInput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (objectOutput != null) {
                try {
                    objectOutput.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

執行結果:
這裏寫圖片描述

通過執行結果,可以對照上面的分析,說明上面的分析是正確的。

  • 下面說說Volatile關鍵字

Java 語言提供了一種稍弱的同步機制,即 volatile 變量.用來確保將變量的更新操作通知到其他線程,保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新. 當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的.
volatile 變量對所有線程是立即可見的,對 volatile 變量所有的寫操作都能立即反應到其他線程之中,換句話說:volatile 變量在各個線程中是一致的,所以基於 volatile 變量的運算是線程安全的.
對於以上的說法,我沒有想到如何用實例進行驗證。
下面只是個人的理解:
1。如果在類中使用volatile修飾一個變量,並且是static的類型,那麼該變量屬於類,是類變量,那麼即使多個線程訪問該變量訪問的也是同一個,哪個線程改變它的話,其他線程在訪問它的時候就是最新的值。不存在不同步的問題。

2。如果在類中使用volatile修飾的變量沒有使用static修飾,那就屬於成員變量,那麼多個線程在訪問的時候,訪問同一個對象下的該成員變量也不存在不同步的問題。對於同一個對象,該成員變量就一個!線程無論何時訪問都是最新的。

所以能用到volatile關鍵字解決多線程的不同步問題相當少了。

  • 序列化和反序列化

正常情況下,一個類實現java序列化很簡單,只需要implements Serializable接口即可,之後該類在跨jvm的傳輸過程中會遵照默認java序列化規則序列化和反序列化;不同jvm版本之間序列化方式稍有不同,但基本上都是兼容的。
在某些特殊情況下,可能需要自定義序列化和反序列化的行爲,看下面例子:

 class AbstractSerializeDemo {     
    private int x, y;     

    public void init(int x, int y) {     
        this.x = x;     
        this.y = y;     
    }     

    public int getX() {     
        return x;     
    }     

    public int getY() {     
        return y;     
    }     

    public void printXY() {     
        System.out.println("x:" + x + ";y:" + y);     
    }     
}     

public class SerializeDemo extends AbstractSerializeDemo implements Serializable {     
    private int z;     

    public SerializeDemo() {     
        super.init(10, 50);     
        z = 100;     
    }     

    public void printZ() {     
        super.printXY();     
        System.out.println("z:" + z);     
    }     

    public static void main(String[] args) throws IOException, ClassNotFoundException {     
        ByteArrayOutputStream bos = new ByteArrayOutputStream();     
        ObjectOutputStream out = new ObjectOutputStream(bos);     
        SerializeDemo sd = new SerializeDemo();     
        sd.printZ();     
        out.writeObject(sd);     
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));     
        SerializeDemo sd2 = (SerializeDemo) in.readObject();     
        sd2.printZ();     
    }     
}  

這段程序表示了一個可序列化的類繼承自一個非序列化的有狀態超類,期望的結果是,子類序列化以後傳輸並反序列化回來,原先的值域包括超類的值域都保持不變。

但是輸出是:
x:10;y:50
z:100
x:0;y:0
z:100
結果和期望不符,子類的值域保留下來了,但是超類的值域丟失了,這對jvm來說是正常的,因爲超類不可序列化;

爲了解決這個問題,只能自定義序列化行爲,具體做法是在SerializeDemo里加入以下代碼:

private void writeObject(ObjectOutputStream os) throws IOException {     
      os.defaultWriteObject();//java對象序列化默認操作     
     os.writeInt(getX());     
      os.writeInt(getY());     
  }     

  private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {     
      is.defaultReadObject();//java對象反序列化默認操作     
      int x=is.readInt();     
      int y=is.readInt();     
      super.init(x,y);     
  }   

writeObject和readObject方法爲JVM會在序列化和反序列化java對象時會分別調用的兩個方法,修飾符都是private,沒錯。

我們在序列化的默認動作之後將超類裏的兩個值域x和y也寫入object流;與之對應在反序列化的默認操作之後讀入x和y兩個值,然後調用超類的初始化方法。

再次執行程序之後的輸出爲:

x:10;y:50
z:100
x:10;y:50
z:100
另外還有兩個自定義序列化方法writeReplace和readResolve,分別用來在序列化之前替換序列化對象 和 在反序列化之後的對返回對象的處理。一般可以用來避免singleTon對象跨jvm序列化和反序列化時產生多個對象實例,事實上singleTon的對象一旦可序列化,它就不能保證singleTon了。JVM的Enum實現裏就是重寫了readResolve方法,由JVM保證Enum的值都是singleTon的,所以建議多使用Enum代替使用writeReplace和readResolve方法。

private Object readResolve()     
    {     
        return INSTANCE;     
    }     

    private Object writeReplace(){     
        return INSTANCE;     
    }    

注:writeReplace調用在writeObject前執行;readResolve調用在readObject之後執行。
(以上序列化反序列化機制部分摘自:http://developer.51cto.com/art/201104/257839.htm)

上面的INSTANCE是單例類的實例。通過上面的代碼可以是單例類在序列化和反序列化後得到同一個對象!!
還有需要注意的是,上面的兩個方法簽名就是那樣的方法簽名,記住就可以了。如果非要問爲什麼?那應該從源碼的角度看看對象的序列化和反序列化的過程。

  • 使用java.io.Externalizable進行序列化和反序列化

序列化和反序列化還有一種方法就是實現上面的接口,實現上面的接口需要實現兩個方法:

@Override
public void writeExternal(ObjectOutput out) throws IOException {
    // TODO Auto-generated method stub
}
@Override
public void readExternal(ObjectInput in) throws IOException,
        ClassNotFoundException {
    // TODO Auto-generated method stub
}

上面的兩個方式是實現Externalizable接口必須實現的方法。通過這兩個方法的名字我們也該知道,它所實現的功能和

private void writeObject(ObjectOutputStream os) throws IOException {     
     //......   
  }     

  private void readObject(ObjectInputStream is) throws IOException, ClassNotFoundException {     
      //......   
  }   

和這兩個方法實現功能一樣,都是自定義序列化和反序列化。
不同的是:
1、writeObject、readObject兩個方法的實現並不是強制的,實現一個或者兩個方法都實現都是可以的。而方法writeExternal、readExternal是實現接口是必須實現的方法。
2、writeObject、readObject兩個方法的實現是實現序列化和反序列化時程序自己調用的。也就是說在程序如下:

out = new ObjectOutputStream(new          
            FileOutputStream("test2.txt"));
            System.out.println("寫入:"+sigleCls);
            out.writeObject(sigleCls);

上面的程序使用ObjectOutputStream的write方法序列化對象sigleCls的時候,會自動調用上面的writeObject、readObject方法,如果sigleCls類實現了這兩個方法的話。不用顯式調用。

而writeExternal、readExternal也不用顯式調用,這一點同上面的一樣的。不同的是,實現這兩個方法進行序列化的時候,必須在實現類中有公共的無參數的構造器!!!否則拋出異常!!
3、如果實現了Externalizable接口,同時實現private Object readResolve(){} 、private Object writeReplace(){ } 方法,也是生效的。
注:writeReplace調用在writeExternal前執行;readResolve調用在readExternal之後執行。
4、在此writeExternal 和readExternal 的作用與writeObject和readObject 一樣,當我們同時實現了兩個interface的時候,JVM只運行Externalizable 接口裏面的writeExternal 和readExternal 方法對序列化內容進行處理。

最後給出一個實例代碼:
單例類:

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class SigleCls implements Serializable,Externalizable
{

    private static final long serialVersionUID = 1L;
    private static SigleCls sigleCls;
    public SigleCls(){}
    public static SigleCls getInstance(){
        if (sigleCls == null) {
            sigleCls = new SigleCls();
        }
        return sigleCls;
    }
    private String name;
    private transient String psw;

    public void setName(String name) {
        this.name = name;
    }

    public void setPsw(String psw) {
        this.psw = psw;
    }

    private Object readResolve()
    {
        System.out.println("SigleCls.readResolve()");
           return sigleCls;
    }

   private Object writeReplace(){
        System.out.println("SigleCls.writeReplace()");
         return sigleCls;
   }


@Override
public String toString() {
    return "name="+name+",pwd="+psw;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
    System.out.println("SigleCls.writeExternal()");
    out.writeObject(sigleCls);

}

private void writeObject(ObjectOutputStream out) throws IOException{
    System.out.println("LoggingInfo.writeObject()");
    out.defaultWriteObject();
    out.writeInt(4);
}
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
    System.out.println("LoggingInfo.readObject()");
    in.defaultReadObject();
    System.out.println("整數="+in.readInt());
}

@Override
public void readExternal(ObjectInput in) throws IOException,
        ClassNotFoundException {
    // TODO Auto-generated method stub
    System.out.println("SigleCls.readExternal()");
    in.readObject();
}
}
/**
 * 注意實現Externalizable接口的類,在發序列化時,將會執行構造函數,
 * 因爲對於流操作而言,此對象是有明確類型的(Serializable接口是個標記接口).
 * 而且,如果實現了writeExternal和readExternal,
 * 將不會在執行readObject和writeObject,
 * 因爲此時這兩個方法已經被"擦除".
 */

測試類:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Test2 {

    public static void main(String[] args) {
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        SigleCls sigleCls = SigleCls.getInstance();
        sigleCls.setName("longyin");
        sigleCls.setPsw("23456");
        try {
            out = new ObjectOutputStream(new FileOutputStream("test2.txt"));
            System.out.println("寫入:"+sigleCls);
            out.writeObject(sigleCls);
            out.flush();
            in = new ObjectInputStream(new FileInputStream("test2.txt"));
            SigleCls sig = (SigleCls) in.readObject();
            System.out.println("讀取:"+sig);
            System.out.println("相等與否:"+(sig==sigleCls));
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

結果:
這裏寫圖片描述
大家可以通過結果,驗證上面的理論部分是否正常。應該說結果證明了上面的理論部分!!!

https://www.ibm.com/developerworks/cn/java/j-lo-serial/
這篇文章是一個博士所寫!非常好!值得好好看看!!

以上博文參考內容比較多,如果有什麼不對的地方,請批評指正!謝謝~~【握手~】

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章