java-Transient关键字、Volatile关键字介绍和序列化、反序列化机制、单例类序列化

来源:互联网 发布:mac口红免税店价格 编辑:程序博客网 时间:2024/06/06 09:13
 - 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进行序列化和反序列化

序列化和反序列化还有一种方法就是实现上面的接口,实现上面的接口需要实现两个方法:

@Overridepublic void writeExternal(ObjectOutput out) throws IOException {    // TODO Auto-generated method stub}@Overridepublic 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;   }@Overridepublic String toString() {    return "name="+name+",pwd="+psw;}@Overridepublic 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());}@Overridepublic 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/
这篇文章是一个博士所写!非常好!值得好好看看!!

以上博文参考内容比较多,如果有什么不对的地方,请批评指正!谢谢~~【握手~】

0 0