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()方法分四個方面打印出當前異常資訊
- 打印出當前異常的詳細資訊
- 打印出異常堆疊中的棧幀資訊
- 打印出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的私有方法。
作者:你的雷哥 出處:https://www.cnblogs.com/henuliulei/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連線,否則保留追究法律責任的權利。