1. 程式人生 > 其它 >java基礎_Arrays類_String,StringBuffer與StringBuilder的區別_淺拷貝與深拷貝_Throwable異常_序列化

java基礎_Arrays類_String,StringBuffer與StringBuilder的區別_淺拷貝與深拷貝_Throwable異常_序列化

Arrays類

String,StringBuffer與StringBuilder的區別??

String 字串常量
StringBuffer 字串變數(執行緒安全)
StringBuilder 字串變數(非執行緒安全)
簡要的說, String 型別和 StringBuffer 型別的主要效能區別其實在於 String 是不可變的物件, 因此在每次對 String 型別進行改變的時候其實都等同於生成了一個新的 String 物件然後將指標指向新的 String 物件,所以經常改變內容的字串最好不要用 String ,因為每次生成物件都會對系統性能產生影響,特別當記憶體中無引用物件多了以後, JVM 的 GC 就會開始工作,那速度是一定會相當慢的。

而如果是使用 StringBuffer 類則結果就不一樣了,每次結果都會對 StringBuffer 物件本身進行操作,而不是生成新的物件,再改變物件引用。所以在一般情況下我們推薦使用 StringBuffer ,特別是字串物件經常改變的情況下。而在某些特別情況下, String 物件的字串拼接其實是被 JVM 解釋成了 StringBuffer 物件的拼接(字串常量進行拼接),所以這些時候 String 物件的速度並不會比 StringBuffer 物件慢,而特別是以下的字串物件生成中, String 效率是遠要比 StringBuffer 快的
String S1 = “This is only a” + “ simple” + “ test”;

StringBuffer Sb = new StringBuilder(“This is only a”).append(“ simple”).append(“ test”);
你會很驚訝的發現,生成 String S1 物件的速度簡直太快了,而這個時候 StringBuffer 居然速度上根本一點都不佔優勢。其實這是 JVM 的一個把戲,在 JVM 眼裡,這個
String S1 = “This is only a” + “ simple” + “test”; 其實就是:
String S1 = “This is only a simple test”; 所以當然不需要太多的時間了。但大家這裡要注意的是,如果你的字串是來自另外的 String 物件的話,速度就沒那麼快了,譬如:
String S2 = “This is only a”;
String S3 = “ simple”;
String S4 = “ test”;
String S1 = S2 +S3 + S4;

這時候 JVM 會規規矩矩的按照原來的方式去做

在大部分情況下 StringBuffer > String
StringBuffer
Java.lang.StringBuffer執行緒安全的可變字元序列。一個類似於 String 的字串緩衝區,但不能修改。雖然在任意時間點上它都包含某種特定的字元序列,但通過某些方法呼叫可以改變該序列的長度和內容。
可將字串緩衝區安全地用於多個執行緒。可以在必要時對這些方法進行同步,因此任意特定例項上的所有操作就好像是以序列順序發生的,該順序與所涉及的每個執行緒進行的方法呼叫順序一致。
StringBuffer 上的主要操作是 append 和 insert 方法,可過載這些方法,以接受任意型別的資料。每個方法都能有效地將給定的資料轉換成字串,然後將該字串的字元追加或插入到字串緩衝區中。append 方法始終將這些字元新增到緩衝區的末端;而 insert 方法則在指定的點新增字元。
例如,如果 z 引用一個當前內容是“start”的字串緩衝區物件,則此方法呼叫 z.append("le") 會使字串緩衝區包含“startle”,而 z.insert(4, "le") 將更改字串緩衝區,使之包含“starlet”。
在大部分情況下 StringBuilder > StringBuffer
java.lang.StringBuilde
java.lang.StringBuilder一個可變的字元序列是5.0新增的。此類提供一個與 StringBuffer 相容的 API,但不保證同步。該類被設計用作 StringBuffer 的一個簡易替換,用在字串緩衝區被單個執行緒使用的時候(這種情況很普遍)。如果可能,建議優先採用該類,因為在大多數實現中,它比 StringBuffer 要快。兩者的方法基本相同。

淺拷貝與深拷貝

一:什麼是淺拷貝和深拷貝
淺拷貝:原型物件的成員變數是值型別,將複製一份給克隆物件;如果原型物件的成員變數是引用型別,則將引用物件的地址複製一份給克隆物件,也就是說原型物件和克隆物件的成員變數指向相同的記憶體地址。也就是說:在淺拷貝中,當物件被複制時只複製它本身和其中包含的值型別的成員變數,而引用型別的成員物件(賦值的只是物件的地址)並沒有複製。
深拷貝:無論原型物件的成員變數是值型別還是引用型別,都將複製一份給克隆物件,深克隆將原型物件的所有引用物件也複製一份給克隆物件。也就是說:在深克隆中,除了物件本身被複制外,物件所包含的所有成員變數也將複製。
實現物件克隆有兩種方式:

實現Cloneable介面並重寫Object類中的clone()方法。
實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深度克隆。
二:淺拷貝
淺拷貝只需要實現Cloneable,在需要克隆的地方呼叫clone方法即可,實現起來比較簡單。

public class Inner implements Cloneable {

}
public class Outer implements Cloneable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) {
Inner inner = new Inner();
Outer outer = new Outer(inner);
try {
Outer cloneOuter = (Outer) outer.clone();
System.out.println("Outer:" + outer + " cloneOuter:" + cloneOuter);
System.out.println("Inner:" + outer.getInner() + " cloneInner:" + cloneOuter.getInner());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

輸出:

Outer:Outer@4554617c cloneOuter:Outer@74a14482
Inner:Inner@1540e19d cloneInner:Inner@1540e19d

從輸出結果可以看出,淺拷貝只是對當前物件Outer建立了一個新的物件,裡面的引用型別Inner還是原物件的地址,並沒有重新建立一個物件。

三:深拷貝
在Java語言中,如果需要實現深克隆,可以通過覆蓋Object類的clone()方法實現,也可以通過序列化(Serialization)等方式來實現。

深拷貝實現方式一:通過覆蓋Object的clone方法實現

此種方法通過重寫Object中的clone方法,並在其內部又對引用型別拷貝來實現的深拷貝,如果引用型別裡面還包含很多引用型別,或者內層引用型別的類裡面又包含引用型別,使用clone方法就會很麻煩。

public class Inner implements Cloneable {
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}

public class Outer implements Cloneable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

@Override
protected Object clone() throws CloneNotSupportedException {
Outer outer = (Outer) super.clone();
outer.inner = (Inner) outer.inner.clone();
return outer;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) {
Inner inner = new Inner();
Outer outer = new Outer(inner);
try {
Outer cloneOuter = (Outer) outer.clone();
System.out.println("Outer:" + outer + " cloneOuter:" + cloneOuter);
System.out.println("Inner:" + outer.getInner() + " cloneInner:" + cloneOuter.getInner());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
}

執行輸出:

Outer:Outer@4554617c cloneOuter:Outer@74a14482
Inner:Inner@1540e19d cloneInner:Inner@677327b6

深拷貝實現方式二:通過序列化方式實現

如果引用型別裡面還包含很多引用型別,或者內層引用型別的類裡面又包含引用型別,使用clone方法就會很麻煩。這時我們可以用序列化的方式來實現物件的深克隆。

public class Inner implements Serializable {

}

public class Outer implements Serializable {
private Inner inner;

public Outer(Inner inner) {
this.inner = inner;
}

public Inner getInner() {
return inner;
}
}
public class MyTest {

public static void main(String[] args) throws IOException, ClassNotFoundException {
Inner inner = new Inner();
Outer outer = new Outer(inner);

ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(outer);

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
Outer streamOuter = (Outer) objectInputStream.readObject();
System.out.println("Outer:" + outer + " streamOuter:" + streamOuter);
System.out.println("Inner:" + outer.getInner() + " streamInner:" + streamOuter.getInner());

}
}

執行輸出:

Outer:Outer@6d6f6e28 streamOuter:Outer@4b67cf4d
Inner:Inner@135fbaa4 streamInner:Inner@7ea987ac

淺拷貝只要實現clone介面,深拷貝則是實現clone介面並且重寫clone方法,或者實現序列化介面實現。

Throwable異常

一:關於異常
JAVA異常是在java程式執行的時候遇到非正常的情況而建立的對,它封裝了異常資訊。java異常的根類為java.lang.Throwable,整個類有兩個直接子類java.lang.Error和java.lang.Exception。

Error是程式本身無法恢復的嚴重錯誤,一般是虛擬機器或者系統執行出現錯誤,和程式無關。Exception則表示可以被程式捕獲並處理的異常錯誤。

JVM用方法呼叫棧來跟蹤每個執行緒中一系列的方法呼叫過程,棧是執行緒私有的,每一個執行緒都有一個獨立的方法呼叫棧,該棧儲存了每個呼叫方法的資訊。當一個新方法被呼叫的時候,JVM會把描述該方法的棧結構置入棧頂,位於棧頂的方法為正在執行的方法。當一個JAVA方法正常執行完畢,JVM會從呼叫棧中彈出該方法的棧結構,然後繼續處理前一個方法。如果java方法在執行程式碼的過程中丟擲異常,JVM必須找到能捕獲異常的catch塊程式碼,它首先檢視當前方法是否存在這樣的catch程式碼塊,如果存在就執行該 catch程式碼塊,否則JVM會呼叫棧中彈處該方法的棧結構,繼續到前一個方法中查詢合適的catch程式碼塊。最後如果JVM向上追到了當前執行緒呼叫的第一個方法(如果是主執行緒就是main方法),仍然沒有找到該異常處理的程式碼塊,該執行緒就會異常終止。如果該執行緒是主執行緒,應用程式也隨之終止,此時JVM將把異常直接拋給使用者,在使用者終端上會看到原始的異常資訊。

Throwable
Java異常體系中根類,有兩個重要的子類:Exception(異常)和 Error(錯誤),二者都是 Java 異常處理的重要子類,各自都包含大量子類。

Error(錯誤)
是程式無法處理的錯誤,表示執行應用程式中較嚴重問題。大多數錯誤與程式碼編寫者執行的操作無關,而表示程式碼執行時 JVM(Java 虛擬機器)出現的問題。例如,Java虛擬機器執行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的記憶體資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機器(JVM)一般會選擇執行緒終止。

Exception(異常)
是由於程式本身引起的異常,分為受檢查異常和Runtime異常,RuntimeException直接繼承於Exception,本身以及其子類異常均表示提前無法預知的異常。除了RuntimeException及其子類,剩餘的都是受檢查異常,編譯器在編譯期強制我們新增try catch去捕獲,否則編譯不通過。

二、Throwable原始碼分析

1、成員變數

    private transient Object backtrace;
    //異常資訊
    private String detailMessage;
    //當前異常是由哪個Throwable所引起的
    private Throwable cause = this;
    //引起異常的堆疊跟蹤資訊
    private StackTraceElement[] stackTrace = UNASSIGNED_STACK;

backtrace:這個變數由native方法賦值,用來儲存棧資訊的軌跡
detailMessage:這個變數是描述異常資訊,比如new Myexception(“My Exception”),記錄的就是我們傳進去的描述此異常的描述資訊 My Exception。
cause :記錄當前異常是由哪個異常所引起的,預設是this,可通過構造器自定義。可以通過initCase方法進行修改

public synchronized Throwable initCause(Throwable cause) {
        if (this.cause != this)
            throw new IllegalStateException("Can't overwrite cause with " +
                                            Objects.toString(cause, "a null"), this);
        if (cause == this)
            throw new IllegalArgumentException("Self-causation not permitted", this);
        this.cause = cause;
        return this;
    }

可以看到case只能被修改一次,當發現當前case已經被修改,則會丟擲IllegalStateException。預設case=this,如果再次修改case為this也是不允許的。
case一般這樣使用

    try {
            Integer.valueOf("a");
        }catch (NumberFormatException e){
            throw new MyException(e);
        }
  • stackTrace 記錄當前異常堆疊資訊,陣列中每一個StackTraceElement表示當前當前方法呼叫的一個棧幀,表示一次方法呼叫。StackTraceElement中儲存的有當前方法的類名、方法名、檔名、行號資訊。
public final class StackTraceElement implements java.io.Serializable {
    private String declaringClass;
    private String methodName;
    private String fileName;
    private int lineNumber;
    }

2、建構函式

public Throwable() {
        fillInStackTrace();
    }
public Throwable(String message) {
        fillInStackTrace();
        detailMessage = message;
    }
public Throwable(String message, Throwable cause) {
        fillInStackTrace();
        detailMessage = message;
        this.cause = cause;
    }
public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }
protected Throwable(String message, Throwable cause,
                        boolean enableSuppression,
                        boolean writableStackTrace) {
        if (writableStackTrace) {
            fillInStackTrace();
        } else {
            stackTrace = null;
        }
        detailMessage = message;
        this.cause = cause;
        if (!enableSuppression)
            suppressedExceptions = null;
    }

Throwable提供了4個public構造器和1個protected構造器(該構造器由JDK1.7引入)。4個public構造器共同點就是都呼叫了fillInStackTrace方法。

3、fillInStackTrace()方法

public synchronized Throwable fillInStackTrace() {
        if (stackTrace != null ||
            backtrace != null /* Out of protocol state */ ) {
            fillInStackTrace(0);
            stackTrace = UNASSIGNED_STACK;
        }
        return this;
    }

private native Throwable fillInStackTrace(int dummy);

fillInStackTrace會首先判斷stackTrace是不是為null,如果不為null則會呼叫native方法fillInStackTrace將當前執行緒的棧幀資訊記錄到此Throwable中。那麼什麼時候為null呢,答案是上面的protected構造器可以指定writableStackTrace為false,這樣stackTrace就為null了,就不會呼叫fillInStackTrace獲取堆疊資訊。

fillInStackTrace將當前執行緒的棧幀資訊記錄到此Throwable中為了理解我們來看一個例子

正常情況下我們丟擲RuntimeException,異常列印是帶有異常堆疊資訊的

public class MyException extends RuntimeException {
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

執行結果:

method1
method2
method3
Exception in thread "main" MyException
    at MyException.method4(MyException.java:20)
    at MyException.method3(MyException.java:17)
    at MyException.method2(MyException.java:13)
    at MyException.method1(MyException.java:9)
    at MyException.main(MyException.java:24)

我們來重寫fillInStackTrace方法,來看一下執行結果

public class MyException extends RuntimeException {
    @Override
    public synchronized Throwable fillInStackTrace() {
        return this;
    }
    public static void method1(){
        System.out.println("method1");
        method2();
    }
    public static void method2(){
        System.out.println("method2");
        method3();
    }
    public static void method3(){
        System.out.println("method3");
        method4();
    }
    public static void method4(){
        throw new MyException();
    }
    public static void main(String[] args) {
        method1();
    }
}

輸出:

method1
method2
method3
Exception in thread "main" MyException

從例子可以看到fillInStackTrace作用是將當前執行緒的棧幀資訊記錄到此Throwable中。

4、addSuppressed()和getSuppressed()方法

public final synchronized void addSuppressed(Throwable exception) {
        if (exception == this)
            throw new IllegalArgumentException(SELF_SUPPRESSION_MESSAGE, exception);

        if (exception == null)
            throw new NullPointerException(NULL_CAUSE_MESSAGE);

        if (suppressedExceptions == null) // Suppressed exceptions not recorded
            return;

        if (suppressedExceptions == SUPPRESSED_SENTINEL)
            suppressedExceptions = new ArrayList<>(1);

        suppressedExceptions.add(exception);
    }
public final synchronized Throwable[] getSuppressed() {
        if (suppressedExceptions == SUPPRESSED_SENTINEL ||
            suppressedExceptions == null)
            return EMPTY_THROWABLE_ARRAY;
        else
            return suppressedExceptions.toArray(EMPTY_THROWABLE_ARRAY);
    }
如果try中丟擲了異常,在執行流程轉移到方法棧上一層之前,finally語句塊會執行,但是,如果在finally語句塊中又丟擲了一個異常,那麼這個異常會覆蓋掉之前丟擲的異常,這點很像finally中return的覆蓋。比如下面這個例子:
public class MyTest {
    public static void main(String[] args) {
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            throw new RuntimeException("One", e);
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                throw new RuntimeException("Two", e);
            }
        }
    }
}

輸出:

Exception in thread "main" java.lang.RuntimeException: Two
    at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "two"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at MyTest.main(MyTest.java:10)

Throwable物件提供了addSupperssed和getSupperssed方法,允許把finally語句塊中產生的異常通過addSupperssed方法新增到try語句產生的異常中。

public class MyTest {
    public static void main(String[] args) {
        RuntimeException exception1 = null;
        try {
            Integer.valueOf("one");
        } catch (NumberFormatException e) {
            exception1 = new RuntimeException("One", e);
            throw exception1;
        } finally {
            try {
                Integer.valueOf("two");
            } catch (NumberFormatException e) {
                RuntimeException exception2 = new RuntimeException("Two", e);
                exception1.addSuppressed(exception2);
                throw exception1;
            }
        }
    }
}

輸出:

Exception in thread "main" java.lang.RuntimeException: One
    at MyTest.main(MyTest.java:8)
    Suppressed: java.lang.RuntimeException: Two
        at MyTest.main(MyTest.java:14)
    Caused by: java.lang.NumberFormatException: For input string: "two"
        at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
        at java.lang.Integer.parseInt(Integer.java:580)
        at java.lang.Integer.valueOf(Integer.java:766)
        at MyTest.main(MyTest.java:12)
Caused by: java.lang.NumberFormatException: For input string: "one"
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:580)
    at java.lang.Integer.valueOf(Integer.java:766)
    at MyTest.main(MyTest.java:6)

5、printStackTrace()方法

printStackTrace()方法分四個方面打印出當前異常資訊

  1. 打印出當前異常的詳細資訊
  2. 打印出異常堆疊中的棧幀資訊
  3. 打印出support異常資訊
  4. 遞迴打印出引起當前異常的異常資訊
public void printStackTrace() {
        printStackTrace(System.err);
    }
private void printStackTrace(PrintStreamOrWriter s) {
        // Guard against malicious overrides of Throwable.equals by
        // using a Set with identity equality semantics.
        Set<Throwable> dejaVu =
            Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
        dejaVu.add(this);

        synchronized (s.lock()) {
            // 列印當前異常的詳細資訊
            s.println(this);
            // 列印當前堆疊中的棧幀資訊
            StackTraceElement[] trace = getOurStackTrace();
            for (StackTraceElement traceElement : trace)
                s.println("\tat " + traceElement);

            // 列印suppressed exceptions
            for (Throwable se : getSuppressed())
                se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);

            // 遞迴打印出引起當前異常的異常資訊
            Throwable ourCause = getCause();
            if (ourCause != null)
                ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
        }
    }

序列化

一:什麼是序列化
序列化是將Java物件相關的類資訊、屬性、屬性值等資訊以一定的格式轉換為位元組流,反序列化時再將位元組流表示的資訊來構建出Java物件。過程中涉及到其它物件的引用物件也要參與序列化。

二:序列化的應用場景
永久性儲存物件,儲存物件的位元組序列到本地檔案或者資料庫中。
通過序列化以位元組流的形式使物件在網路中進行傳遞和接收。
通過序列化在程序間傳遞物件。
三:序列化的實現方式
Java中實現序列化的方式有兩種:1、實現Serializable介面。2、實現Externalizable介面。

1、實現Serializable介面

public class People implements Serializable {
    private String name;
    private int age;
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

執行輸出:name:bobo age:26

2、實現Externalizable介面

用Externalizable介面實現序列化時需要注意兩點:

必須要提供公有的無參建構函式,否則會報InvalidClassException。
必須要在writeExternal和readExternal中自己去實現序列化過程。

public class People implements Externalizable {
    private String name;
    private int age;
    public People() {

    }
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
    }
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = (String) in.readObject();
        age = in.readInt();
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

執行輸出:name:bobo age:26

四:序列化核心點
1、serialVersionUID的作用

在序列化操作時,經常會看到實現了Serializable介面的類會存在一個serialVersionUID屬性,並且它是一個固定數值的靜態變數。它主要用於驗證版本的一致性。每個實現Serializable介面的類都擁有這麼一個ID,在序列化的時候會一起被寫入流中。在反序列化的時候就會被拿出來跟當前類的serialVersionUID值進行比較,兩者相同則說明版本一致,可以反序列化成功,如果不通則反序列化失敗。
兩種serialVersionUID方式:

自己定義,比如比如:private static final long serialVersionUID = 1234567L。
如果沒定義,JDK會幫我們生成,生成規則是利用類名、類修飾符、介面名、欄位、靜態初始化資訊、建構函式資訊、方法名、方法修飾符、方法簽名等組成的資訊,經過SHA演算法生成serialVersionUID 值。
2、Transient 關鍵字作用

Transient 關鍵字的作用是控制變數的序列化,在變數宣告前加上該關鍵字,可以阻止該變數被序列化到流中,在被反序列化後,transient 變數的值被設為初始值,如 int 型的是 0,物件型的是 null。

public class People implements Serializable {
    private transient String name;
    private transient int age;
    public People(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
    }
}

執行輸出:name:null age:0

3、靜態變數不會被序列化

這個也很好理解,我們序列化是針對物件的,而靜態變數是屬於類的。下面看一個例子:

public class People implements Serializable {
    private static int age = 10;
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        age = 26;//改變靜態變數的值
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println(" age:" + age);
    }
}

執行輸出:age:26,age的值改變了,證明age是沒有被序列化的。

4、父類的序列化

如果一個子類實現了Serializable 介面而父類沒有實現該介面,則在序列化子類時,子類的屬性狀態會被寫入而父類的屬性狀態不會被寫入。所以如果想要父類屬性狀態也一起參與序列化,就要讓它也實現Serializable 介面。

如果父類未實現Serializable 介面則反序列化生成的物件會再次呼叫父類的建構函式,以此來完成對父類的初始化,所以父類的屬性初始值一般都是型別的預設值。

public class Animal {
    protected int num;
    protected String color;
    public int getNum() {
        return num;
    }
    public String getColor() {
        return color;
    }
}

public class People extends Animal implements Serializable {
    private String name;
    private int age;
    public People(String name, int age, int num, String color) {
        this.name = name;
        this.age = age;
        this.num = num;
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26,10,"red");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge());
        System.out.println("num:" + serialPeople.getNum() + " color:" + serialPeople.getColor());
    }
}

執行輸出:

name:bobo age:26
num:0 color:null

5、被引用的類沒有實現Serializable 介面則序列化不成功

序列化物件裡面包含的任何引用型別的物件的類都要實現Serializable 介面,否則丟擲java.io.NotSerializableException

public class Brother {
    protected int num;
    protected String color;
    public Brother(int num, String color) {
        this.num = num;
        this.color = color;
    }
    public int getNum() {
        return num;
    }
    public String getColor() {
        return color;
    }
}
public class People implements Serializable {
    private String name;
    private int age;
    private Brother brother;
    public People(String name, int age, Brother brother) {
        this.name = name;
        this.age = age;
        this.brother = brother;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Brother brother = new Brother(2, "red");
        People people = new People("bobo", 26, brother);
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " brother:" + brother);
    }
}

執行輸出:

Exception in thread "main" java.io.NotSerializableException: Brother
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1548)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1509)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at People.main(People.java:23)

6、自定義序列化過程

如果預設的序列化過程不能滿足需求,我們也可以自定義整個序列化過程。這時候我們只需要在需要序列化的類中定義私有的writeObject方法和readObject方法即可。

public class People implements Serializable {
    private String name;
    private int age;
    private transient String color;
    public People(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public String getColor() {
        return color;
    }
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();//預設序列化過程
        color = (String) s.readObject();
    }
    private void writeObject(ObjectOutputStream s) throws IOException {
        s.defaultWriteObject();//預設序列化過程
        s.writeObject(color);
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        People people = new People("bobo", 26, "red");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(people);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        People serialPeople = (People) objectInputStream.readObject();
        System.out.println("name:" + serialPeople.getName() + " age:" + serialPeople.getAge() + " color:" + serialPeople.getColor());
    }
}

執行輸出:

name:bobo age:26 color:red

在color屬性前加了transient 關鍵字,意思是不讓color實現序列化,但是下面又自定義序列化過程在writeObject和readObject裡面實現color的序列化,所以color屬性是實現了序列化的。

7、為什麼實現readObject()方法和writeObject()方法就可以自定義序列化過程?

readObject()和writeObject() 既不存在於java.lang.Object,也沒有在Serializable中宣告。那麼ObjectOutputStream如何使用它們的呢?原來,ObjectOutputStream使用了反射來尋找是否聲明瞭這兩個方法。因為ObjectOutputStream使用getPrivateMethod,所以這些方法不得不被宣告為private以至於供ObjectOutputStream來使用。

下面我們以ObjectInputStream來原始碼分析一下:

ObjectInputStream的readObject()方法—>呼叫到readObject0(boolean unshared)方法—>readOrdinaryObject(boolean unshared)方法—>readSerialData(Object obj, ObjectStreamClass desc)方法---->ObjectStreamClass類的invokeReadObject(Object obj, ObjectInputStream in)方法:

void invokeReadObject(Object obj, ObjectInputStream in)
        throws ClassNotFoundException, IOException,
               UnsupportedOperationException
    {
        if (readObjectMethod != null) {
            try {
                readObjectMethod.invoke(obj, new Object[]{ in });
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ClassNotFoundException) {
                    throw (ClassNotFoundException) th;
                } else if (th instanceof IOException) {
                    throw (IOException) th;
                } else {
                    throwMiscException(th);
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

執行readObjectMethod.invoke(obj, new Object[]{ in }),通過反射的方式呼叫我們類中定義的readObject的私有方法。

作者:你的雷哥 出處:https://www.cnblogs.com/henuliulei/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連線,否則保留追究法律責任的權利。