话题:关于序列化的知识
1.Parcelable和Serializable有什么用?它们有什么差别?
2.自定义一个类让其实现Parcelable,大致流程是什么?
Parcelable和Serializable有什么用?它们有什么差别?
Serializable
package java.io;
/**
* Serializability of a class is enabled by the class implementing the
* java.io.Serializable interface. Classes that do not implement this
* interface will not have any of their state serialized or
* deserialized. All subtypes of a serializable class are themselves
* serializable. The serialization interface has no methods or fields
* and serves only to identify the semantics of being serializable. <p>
*
* To allow subtypes of non-serializable classes to be serialized, the
* subtype may assume responsibility for saving and restoring the
* state of the supertype's public, protected, and (if accessible)
* package fields. The subtype may assume this responsibility only if
* the class it extends has an accessible no-arg constructor to
* initialize the class's state. It is an error to declare a class
* Serializable if this is not the case. The error will be detected at
* runtime. <p>
*
* During deserialization, the fields of non-serializable classes will
* be initialized using the public or protected no-arg constructor of
* the class. A no-arg constructor must be accessible to the subclass
* that is serializable. The fields of serializable subclasses will
* be restored from the stream. <p>
*
* When traversing a graph, an object may be encountered that does not
* support the Serializable interface. In this case the
* NotSerializableException will be thrown and will identify the class
* of the non-serializable object. <p>
*
* Classes that require special handling during the serialization and
* deserialization process must implement special methods with these exact
* signatures:
*
* <PRE>
* private void writeObject(java.io.ObjectOutputStream out)
* throws IOException
* private void readObject(java.io.ObjectInputStream in)
* throws IOException, ClassNotFoundException;
* private void readObjectNoData()
* throws ObjectStreamException;
* </PRE>
*
* <p>The writeObject method is responsible for writing the state of the
* object for its particular class so that the corresponding
* readObject method can restore it. The default mechanism for saving
* the Object's fields can be invoked by calling
* out.defaultWriteObject. The method does not need to concern
* itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* <p>The readObject method is responsible for reading from the stream and
* restoring the classes fields. It may call in.defaultReadObject to invoke
* the default mechanism for restoring the object's non-static and
* non-transient fields. The defaultReadObject method uses information in
* the stream to assign the fields of the object saved in the stream with the
* correspondingly named fields in the current object. This handles the case
* when the class has evolved to add new fields. The method does not need to
* concern itself with the state belonging to its superclasses or subclasses.
* State is saved by writing the individual fields to the
* ObjectOutputStream using the writeObject method or by using the
* methods for primitive data types supported by DataOutput.
*
* <p>The readObjectNoData method is responsible for initializing the state of
* the object for its particular class in the event that the serialization
* stream does not list the given class as a superclass of the object being
* deserialized. This may occur in cases where the receiving party uses a
* different version of the deserialized instance's class than the sending
* party, and the receiver's version extends classes that are not extended by
* the sender's version. This may also occur if the serialization stream has
* been tampered; hence, readObjectNoData is useful for initializing
* deserialized objects properly despite a "hostile" or incomplete source
* stream.
*
* <p>Serializable classes that need to designate an alternative object to be
* used when writing an object to the stream should implement this
* special method with the exact signature:
*
* <PRE>
* ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
* </PRE><p>
*
* This writeReplace method is invoked by serialization if the method
* exists and it would be accessible from a method defined within the
* class of the object being serialized. Thus, the method can have private,
* protected and package-private access. Subclass access to this method
* follows java accessibility rules. <p>
*
* Classes that need to designate a replacement when an instance of it
* is read from the stream should implement this special method with the
* exact signature.
*
* <PRE>
* ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
* </PRE><p>
*
* This readResolve method follows the same invocation rules and
* accessibility rules as writeReplace.<p>
*
* The serialization runtime associates with each serializable class a version
* number, called a serialVersionUID, which is used during deserialization to
* verify that the sender and receiver of a serialized object have loaded
* classes for that object that are compatible with respect to serialization.
* If the receiver has loaded a class for the object that has a different
* serialVersionUID than that of the corresponding sender's class, then
* deserialization will result in an {@link InvalidClassException}. A
* serializable class can declare its own serialVersionUID explicitly by
* declaring a field named <code>"serialVersionUID"</code> that must be static,
* final, and of type <code>long</code>:
*
* <PRE>
* ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;
* </PRE>
*
* If a serializable class does not explicitly declare a serialVersionUID, then
* the serialization runtime will calculate a default serialVersionUID value
* for that class based on various aspects of the class, as described in the
* Java(TM) Object Serialization Specification. However, it is <em>strongly
* recommended</em> that all serializable classes explicitly declare
* serialVersionUID values, since the default serialVersionUID computation is
* highly sensitive to class details that may vary depending on compiler
* implementations, and can thus result in unexpected
* <code>InvalidClassException</code>s during deserialization. Therefore, to
* guarantee a consistent serialVersionUID value across different java compiler
* implementations, a serializable class must declare an explicit
* serialVersionUID value. It is also strongly advised that explicit
* serialVersionUID declarations use the <code>private</code> modifier where
* possible, since such declarations apply only to the immediately declaring
* class--serialVersionUID fields are not useful as inherited members. Array
* classes cannot declare an explicit serialVersionUID, so they always have
* the default computed value, but the requirement for matching
* serialVersionUID values is waived for array classes.
*
* Android implementation of serialVersionUID computation will change slightly
* for some classes if you're targeting android N. In order to preserve compatibility,
* this change is only enabled is the application target SDK version is set to
* 24 or higher. It is highly recommended to use an explicit serialVersionUID
* field to avoid compatibility issues.
*
* <h3>Implement Serializable Judiciously</h3>
* Refer to <i>Effective Java</i>'s chapter on serialization for thorough
* coverage of the serialization API. The book explains how to use this
* interface without harming your application's maintainability.
*
* <h3>Recommended Alternatives</h3>
* <strong>JSON</strong> is concise, human-readable and efficient. Android
* includes both a {@link android.util.JsonReader streaming API} and a {@link
* org.json.JSONObject tree API} to read and write JSON. Use a binding library
* like <a href="http://code.google.com/p/google-gson/">GSON</a> to read and
* write Java objects directly.
*
* @author unascribed
* @see java.io.ObjectOutputStream
* @see java.io.ObjectInputStream
* @see java.io.ObjectOutput
* @see java.io.ObjectInput
* @see java.io.Externalizable
* @since JDK1.1
*/
public interface Serializable {
}
类的可序列化通过实现java.io.Serializable接口启用。未实现此接口的类将不会将其任何状态序列化或反序列化。可序列化类的所有子类型本身也需要是可序列化的。
序列化接口没有方法或字段,仅用于标识可序列化的语义。
为了允许序列化非序列化类的子类,子类需要save、restore父类的public、protected、package fields,且其继承的类需要有一个无参的构造函数。否则将在以下位置检测到运行时错误。
在反序列化期间,非可序列化类的字段将使用public或protected no-arg构造函数初始化。子类必须可以访问no-arg构造函数。
遍历图形时,可能遇到有的对象不支持Serializable接口,在这种情况下将抛出NotSerializableException。
在序列化期间需要特殊处理的类在反序列化过程必须实现具有这些精确的特殊方法签名:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
writeObject方法负责为其特定类编写对象的状态以使其对应
readObject方法可以恢复它。可以通过调用out.defaultWriteObject来调用保存Object字段的默认机制。该方法不需要关注属于其超类或子类的状态。通过使用writeObject方法或使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
readObject方法负责从流中读取并恢复类字段。它可以调用in.defaultReadObject来调用恢复对象的非静态和非瞬态字段的默认机制。 defaultReadObject方法使用流中的信息来指定流中保存的对象的字段以及当前对象中相应命名的字段。这处理了子类添加新字段时的情况。该方法不需要关注属于其超类或子类的状态。
通过使用writeObject方法或使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
在将对象写入流时需要指定要使用的备用对象的可序列化类应该使用确切的签名实现此特殊方法:
Object writeReplace() throws ObjectStreamException;
如果方法存在,则可以通过序列化调用此writeReplace方法,并且可以从要序列化的对象的类中定义的方法访问该方法。因此,该方法可以具有private、protected、package访问。子类对此方法的访问遵循java可访问性规则。
从流中读取实例时需要指定替换的类应该使用精确签名实现此特殊方法。
ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
此readResolve方法遵循与writeReplace相同的调用规则和可访问性规则。
序列化运行时将每个可序列化类与版本号相关联,称为serialVersionUID,在反序列化期间使用该版本号来验证序列化对象的发送方和接收方是否已加载与该序列化兼容的该对象的类。
如果接收者为具有与相应发送者类的serialVersionUID不同的对象加载了一个类,则反序列化将导致{@link InvalidClassException}。可序列化类可以通过声明名为“serialVersionUID”的字段来显式声明其自己的serialVersionUID,该字段必须是static,final long类型:
static final long serialVersionUID = 42L;
如果可序列化类未显式声明serialVersionUID,则序列化运行时将基于类的各个方面计算该类的默认serialVersionUID值,如Java(TM)对象序列化规范中所述。但是,强烈建议所有可序列化类都显式声明serialVersionUID值,因为默认的serialVersionUID计算对类详细信息高度敏感,可能因编译器实现而异,因此可能导致意外的反序列化期间的InvalidClassException。因此,为了保证跨不同java编译器实现的一致的serialVersionUID值,可序列化类必须声明显式的serialVersionUID值。强烈建议显式serialVersionUID声明尽可能使用private修饰符,因为此类声明仅适用于立即声明的类 - serialVersionUID字段作为继承成员无用。数组类不能声明显式的serialVersionUID,因此它们始终具有默认的计算值,但是对于数组类,不需要匹配serialVersionUID值。
对于某些classe,serialVersionUID计算的Android实现会略有变化
Parcelable
可以在{@link Parcel}中写入和恢复其实例的类的接口。 实现Parcelable接口的类还必须具有一个名为CREATOR的非空静态字段,该字段实现{@link Parcelable.Creator}接口。
具体的使用方式参照下节。
Parcelable是android特有的序列化API,它的出现是为了解决Serializable在序列化的过程中消耗资源严重的问题,但是因为本身使用需要手动处理序列化和反序列化过程,会与具体的代码绑定,使用较为繁琐,一般只获取内存数据的时候使用。而Parcelable依赖于Parcel,Parcel的意思是包装,实现原理是在内存中建立一块共享数据块,序列化和反序列化均是操作这一块的数据,如此来实现。
自定义一个类让其实现Parcelable,大致流程是什么?
先给出一个Parcelable的例子,Bean中包含三个成员变量:String、int、Parcelable:
package com.mediavideo.dengg;
import android.os.Parcel;
import android.os.Parcelable;
/**
* @Description test Parcelable
*
* Created by deng on 2019/5/2.
*/
public class TestParcelableBean implements Parcelable {
public String stringValue;
public int intValue;
public TestParcelableBean parcelableValue;
protected TestParcelableBean(Parcel in) {
stringValue = in.readString();
intValue = in.readInt();
parcelableValue = in.readParcelable(TestParcelableBean.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(stringValue);
dest.writeInt(intValue);
dest.writeParcelable(parcelableValue, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
/**
* Describe the kinds of special objects contained in this Parcelable
* instance's marshaled representation. For example, if the object will
* include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
* the return value of this method must include the
* {@link #CONTENTS_FILE_DESCRIPTOR} bit.
*
* @return a bitmask indicating the set of special object types marshaled
* by this Parcelable object instance.
*/
@Override
public int describeContents() {
return 0;
}
public static final Creator<TestParcelableBean> CREATOR = new Creator<TestParcelableBean>() {
@Override
public TestParcelableBean createFromParcel(Parcel in) {
return new TestParcelableBean(in);
}
@Override
public TestParcelableBean[] newArray(int size) {
return new TestParcelableBean[size];
}
};
}
使Bean类实现Pacelable接口,需要实现writeToParcel()、构造函数(Parcel in)、describeContents()以及CREATOR变量。
1.在writeToParcel()中,使用wirteXXX()方法放入对应变量,在构造函数中需要按照相同的顺序取出。在writeParcelable时需要传入flag:
Parcelable Flags:
/**
* Flag for use with {@link #writeToParcel}: the object being written
* is a return value, that is the result of a function such as
* "<code>Parcelable someFunction()</code>",
* "<code>void someFunction(out Parcelable)</code>", or
* "<code>void someFunction(inout Parcelable)</code>". Some implementations
* may want to release resources at this point.
*/
public static final int PARCELABLE_WRITE_RETURN_VALUE = 0x0001;
/**
* Flag for use with {@link #writeToParcel}: a parent object will take
* care of managing duplicate state/data that is nominally replicated
* across its inner data members. This flag instructs the inner data
* types to omit that data during marshaling. Exact behavior may vary
* on a case by case basis.
* @hide
*/
public static final int PARCELABLE_ELIDE_DUPLICATES = 0x0002;
2.describeContents()的返回值为0,一般不去修改。返回值的作用是:
在Parcelable中定义了一个名为CONTENTS_FILE_DESCRIPTOR的常量,它用于在describeContents()中创建位掩码返回值。
API ref中CONTENTS_FILE_DESCRIPTOR的描述是:
与describeContents()一起使用的位掩码:每个位代表一种在编组时被认为具有潜在特殊意义的对象。这实际上意味着:如果你需要将FileDescriptor对象放入Parcelable,你应该/必须指定CONTENTS_FILE_DESCRIPTOR作为describeContents()的返回值,即通过“特殊对象”(在describeContents()的描述中)它们的真正含义:FileDescriptor。
整个Parcelable功能看起来未完成(阅读:设计不好)。文档中还有一个奇怪的事情:
实现Parcelable接口的类还必须有一个名为CREATOR的静态字段,它是一个实现Parcelable.Creator接口的对象
通过以人类可读形式定义的规则实现多重继承?似乎C ++程序员设计了Parceable,并且在某些时候他意识到:哦,该死的,Java中没有多重继承… ?