首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Java IO 序列化機制與裝飾器/適配器模式深度解析

     編輯:Java 綜合教學

Java IO 序列化機制與裝飾器/適配器模式深度解析

Java IO 體系不僅是流操作 API,更是設計模式的經典實戰場。本文深度解析序列化核心機制、serialVersionUID 作用、預設與自定義序列化策略,並從源碼層面剖析 Java IO 中裝飾器模式與適配器模式的巧妙應用。

序列化核心概念

概念說明
序列化將物件狀態轉為位元組序列 (持久化/網絡傳輸)
反序列化從位元組序列重建物件狀態
序列化協議Java 專有二進制格式,跨平台但非跨語言
適用場景RMI、HTTP Session 複製、分散式快取、檔案持久化

實現序列化的兩種方式

1. 實現 Serializable 標記介面 (預設機制)

public class User implements Serializable {
    private static final long serialVersionUID = 1L; // 強烈建議顯式聲明
    
    private String name;
    private int age;
    private transient String password; // 不序列化
    private static String company;     // 不序列化
    
    // 構造器、getter/setter...
}

2. 實現 Externalizable 介面 (完全控制)

public class UserV2 implements Externalizable {
    private String name;
    private int age;
    
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
        out.writeInt(age);
        // 完全自定義寫入邏輯、加密、壓縮等
    }
    
    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.name = (String) in.readObject();
        this.age = in.readInt();
    }
    
    // 必須有無參構造器!
    public UserV2() {}
}

serialVersionUID 關鍵作用

  • 版本標識:反序列化時驗證類版本一致性
  • 未聲明風險:JVM 自動生成基於類結構的雜湊值,任何修改 (增減欄位、方法) 導致 UID 變化,拋出 InvalidClassException
  • 最佳實踐:private static final long serialVersionUID = 1L; 手動維護,兼容性升級時保持不變
  • 兼容性規則:增欄位兼容、減欄位兼容、修改欄位類型不兼容

預設序列化機制深度剖析

遞歸圖遍歷

序列化物件時,會遞歸序列化其引用的所有物件 (物件圖)。若引用鏈包含大型集合、巢狀集合,開銷極大。

特殊欄位處理

修飾符序列化行為
transient跳過,反序列化後為默認值 (null/0/false)
static屬於類不屬於實例,跳過

不支援序列化的類型

  • Thread、Socket、FileDescriptor 等系統資源句柄
  • 原因:反序列化後無法重建底層資源 (檔案描述符、執行緒上下文)
  • 解決:標記為 transient,或實現 writeObject/readObject 自定義處理

自定義序列化:writeObject / readObject

public class ConnectionWrapper implements Serializable {
    private transient Socket socket; // 不直接序列化 Socket
    private String host;
    private int port;
    
    // 自定義寫入:只存 host/port
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject(); // 先執行預設邏寫入非 transient 欄位
        out.writeUTF(host);
        out.writeInt(port);
    }
    
    // 自定義讀取:重建 Socket 連接
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject(); // 先讀取非 transient 欄位
        this.host = in.readUTF();
        this.port = in.readInt();
        this.socket = new Socket(host, port); // 重建連接
    }
}

完整序列化實戰範例

import java.io.*;
import java.util.*;

public class SerializationDemo {
    private static final String FILE = "data.ser";
    
    public static void main(String[] args) {
        serialize();
        deserialize();
    }
    
    static void serialize() {
        try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(FILE))) {
            // 1. 一般物件
            User user = new User("Alice", 25, "secret123");
            out.writeObject(user);
            
            // 2. 基本類型
            out.writeBoolean(true);
            out.writeByte((byte) 65);
            out.writeChar('中');
            out.writeInt(20240101);
            out.writeFloat(3.14f);
            out.writeDouble(Math.PI);
            
            // 3. 集合 (支援序列化)
            Map<String, String> map = new HashMap<>();
            map.put("key1", "value1");
            map.put("key2", "value2");
            out.writeObject(map);
            
            System.out.println("序列化完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    static void deserialize() {
        try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(FILE))) {
            // 必須按寫入順序讀取!
            User user = (User) in.readObject();
            System.out.println("User: " + user);
            
            System.out.println("Boolean: " + in.readBoolean());
            System.out.println("Byte: " + (in.readByte() & 0xFF));
            System.out.println("Char: " + in.readChar());
            System.out.println("Int: " + in.readInt());
            System.out.println("Float: " + in.readFloat());
            System.out.println("Double: " + in.readDouble());
            
            @SuppressWarnings("unchecked")
            Map<String, String> map = (Map<String, String>) in.readObject();
            map.forEach((k, v) -> System.out.println(k + " -> " + v));
            
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class User implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private transient String password;
    private static String company = "TechCorp";
    
    public User(String name, int age, String password) {
        this.name = name; this.age = age; this.password = password;
    }
    
    @Override public String toString() {
        return "User{name='" + name + "', age=" + age + ", password=" + password + ", company=" + company + "}";
    }
}

Java IO 設計模式深度解析

裝飾器模式 - 核心架構

角色對應:

角色InputStream 體系OutputStream 體系Reader 體系Writer 體系
Component (抽象構件)InputStreamOutputStreamReaderWriter
ConcreteComponent (具體構件)FileInputStream, ByteArrayInputStream, PipedInputStreamFileOutputStream, ByteArrayOutputStream, PipedOutputStreamFileReader, StringReader, CharArrayReaderFileWriter, StringWriter, CharArrayWriter
Decorator (抽象裝飾)FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
ConcreteDecorator (具體裝飾)BufferedInputStream, DataInputStream, LineNumberInputStream, PushbackInputStreamBufferedOutputStream, DataOutputStream, PrintStreamBufferedReader, LineNumberReader, PushbackReaderBufferedWriter, PrintWriter

裝飾器模式關鍵代碼 (FilterInputStream)

public class FilterInputStream extends InputStream {
    protected volatile InputStream in; // 持有被裝飾對象引用
    
    protected FilterInputStream(InputStream in) {
        this.in = in;
    }
    
    // 核心:委派給被裝飾對象,可在前後增強
    @Override
    public int read() throws IOException {
        return in.read(); // 透明轉發
    }
    
    @Override
    public int read(byte[] b) throws IOException {
        return in.read(b);
    }
    
    // 具體裝飾類擴展功能
    // BufferedInputStream: 內部緩衝區,批量讀取減少系統調用
    // DataInputStream: 增加 readInt/readUTF 等類型化讀取
    // PushbackInputStream: 增加 unread() 退回字節能力
}

裝飾器鏈式組合範例


適配器模式 - 介面轉換

核心思想:將一個類的介面轉換為客戶端期望的另一個介面。

Java IO 中適配器實例

適配器類適配目標 (Target)被適配者 (Adaptee)說明
InputStreamReaderReader (字符流)InputStream (字節流)字節→字符,指定 Charset 解碼
OutputStreamWriterWriter (字符流)OutputStream (字節流)字符→字節,指定 Charset 編碼
ByteArrayInputStreamInputStreambyte[]位元組陣列 → 流介面
ByteArrayOutputStreamOutputStreambyte[] (內部緩衝)流介面 → 位元組陣列
StringReaderReaderString字符串 → 字符流
StringWriterWriterStringBuffer (內部)字符流 → 字符串
FileInputStreamInputStreamFileDescriptor檔案描述符 → 流介面

適配器代碼範例 (InputStreamReader 簡化版)

public class InputStreamReader extends Reader {
    private final StreamDecoder sd; // 內部解碼器
    
    public InputStreamReader(InputStream in) {
        this(in, Charset.defaultCharset());
    }
    
    public InputStreamReader(InputStream in, Charset cs) {
        this.sd = StreamDecoder.forInputStreamReader(in, cs, -1);
    }
    
    @Override
    public int read(char[] cbuf, int offset, int length) throws IOException {
        return sd.read(cbuf, offset, length); // 委派給解碼器
    }
    
    @Override
    public void close() throws IOException {
        sd.close();
    }
}

裝飾器 vs 適配器 對比

維度裝飾器模式適配器模式
目的動態增加職責 (增強功能)介面轉換 (兼容介面)
介面關係裝飾者與被裝飾者介面相同適配器實現目標介面,內部持有不同介面的被適配者
透明性客戶端無感知 (完全透明)客戶端面向目標介面,知曉適配存在
組合方式遞歸嵌套:Dec(Dec(Comp))單層包裝:Adapter(Adaptee)
Java IO 體現BufferedInputStream, DataInputStreamInputStreamReader, ByteArrayInputStream

關鍵洞察:Java IO 模糊邊界

許多類同時扮演兩種角色:

  • BufferedInputStream:裝飾器 (增加緩衝) + 適配器 (將 byte[] 緩衝區適配為 InputStream 介面)
  • DataInputStream:裝飾器 (增加類型化讀取) + 適配器 (將基本類型讀取適配為流操作)
  • Java IO 設計優先實用性,非教條式模式純度

總結

  1. 序列化:實現 Serializable、顯式聲明 serialVersionUID、transient 控制敏感/非序列化欄位、自定義 writeObject/readObject 處理資源重建
  2. 裝飾器模式:Java IO 核心架構,FilterInputStream/OutputStream 為抽象裝飾,鏈式組合實現功能疊加 (緩衝、類型化、回退)
  3. 適配器模式:橋接字節/字符、陣列/流、檔案/流等異構介面,InputStreamReader、ByteArrayInputStream 為典型代表
  4. 模式融合:現實框架設計中模式邊界模糊,理解委派機制與介面契約比死記模式名稱更重要

深入理解 Java IO 設計模式,不僅能正確使用 API,更能在設計自定義流、封裝第三方庫、排查流關閉/編碼問題時遊刃有餘。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved