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的私有方法。

 

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