Arrays類
String,StringBuffer與StringBuilder的區別??
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.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()方法分四個方面打印出當前異常信息
- 打印出當前異常的詳細信息
- 打印出異常堆棧中的棧幀信息
- 打印出support異常信息
- 遞歸打印出引起當前異常的異常信息
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的私有方法。