天天看點

《JAVA築基100例》「第35題」JAVA進階技術-序列化與對象克隆

#我在頭條搞創作第二期#

❤️作者簡介:大家好,我是小虛竹。Java領域優質創作者,CSDN部落格專家,華為雲享專家,掘金年度人氣作者,阿裡雲專家部落客,51CTO專家部落客

❤️技術活,該賞

❤️點贊 收藏 ⭐再看,養成習慣

零、前言

今天是學習 「JAVA語言」 打卡的第35天,我的學習政策很簡單,題海政策+ 費曼學習法。如果能把這100題都認認真真自己實作一遍,那意味着 「JAVA語言」 已經築基成功了。後面的進階學習,可以繼續跟着我,一起走向架構師之路。

一、題目描述

題目:對象的克隆是Java一項進階技術,可以根據給定的對象,獲得與其完全相同的另一個對象。

如果對象的成員變量包含可變引用類型,則需要使用深克隆技術。 你現在會發現使用clone方法來進行克隆,是很麻煩的事。是以還有另一種克隆的方式:序列化克隆

二、解題思路-序列化克隆

建立一個位址類Address

定義三個成員變量表示:國家,省和市。

使用構造方法對它們進行指派。

并提供對應的get方法和set方法。

重寫toString()方法,來輸出對象。

再建立一個員工類Employee

定義三個成員變量表示:員工名字,年齡和位址

使用構造方法對它們進行指派。

并提供對應的get方法和set方法。

重寫toString()方法和。

「通常情況下,克隆對象都需要使用深克隆。」 「把對象寫入本地檔案的方式完成序列化」

三、代碼詳解

位址類:

public class Address implements Serializable {
    private static final long serialVersionUID = 4983187287403615604L;
    private String state; // 表示員工所在的國家
    private String province; // 表示員工所在的省
    private String city; // 表示員工所在的市

    public Address(String state, String province, String city) {// 利用構造方法初始化各個域
        this.state = state;
        this.province = province;
        this.city = city;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public static long getSerialversionuid() {
        return serialVersionUID;
    }

    @Override
    public String toString() {// 使用位址屬性表示位址對象
        StringBuilder sb = new StringBuilder();
        sb.append("國家:" + state + ", ");
        sb.append("省:" + province + ", ");
        sb.append("市:" + city);
        return sb.toString();
    }

}


           

員工類:

public class Employee implements Serializable {
    private static final long serialVersionUID = 3049633059823371192L;
    private String name; // 表示員工的姓名
    private int age; // 表示員工的年齡
    private Address address;// 表示員工的位址

    public Employee(String name, int age, Address address) {// 利用構造方法初始化各個域
        this.name = name;
        this.age = age;
        this.address = address;
    }
    
     public Employee() {// 利用構造方法初始化各個域
        super();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {// 重寫toString()方法
        StringBuilder sb = new StringBuilder();
        sb.append("姓名:" + name + ", ");
        sb.append("年齡:" + age + "\n");
        sb.append("位址:" + address);
        return sb.toString();
    }

}

           

測試類

public class Test {
    public static void main(String[] args) {
        System.out.println("序列化之前:");
        Address address = new Address("中國", "吉林", "長春");// 建立address對象
        Employee employee1 = new Employee("小虛竹", 30, address);// 建立employee1對象
        System.out.println("員工1的資訊:");
        System.out.println(employee1);// 輸出employee1對象
        System.out.println("序列化之後:");
        ObjectOutputStream out = null;
        ObjectInputStream in = null;
        Employee employee2 = null;
        try {
            out = new ObjectOutputStream(new FileOutputStream("employee.dat"));
            out.writeObject(employee1);// 将對象寫入到本地檔案中
            in = new ObjectInputStream(new FileInputStream("employee.dat"));
            employee2 = (Employee) in.readObject();// 從本地檔案讀取對象
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();// 關閉輸入流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                try {
                    out.close();// 關閉輸出流
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        if (employee2 != null) {
            employee2.getAddress().setState("中國"); // 修改員工位址
            employee2.getAddress().setProvince("四川"); // 修改員工位址
            employee2.getAddress().setCity("成都"); // 修改員工位址
            employee2.setName("大虛竹"); // 修改員工名字
            employee2.setAge(24);// 修改員工年齡
            System.out.println("員工1的資訊:");
            System.out.println(employee1);// 輸出employee1對象
            System.out.println("員工2的資訊:");
            System.out.println(employee2);// 輸出employee2對象
        }

    }

}

           
《JAVA築基100例》「第35題」JAVA進階技術-序列化與對象克隆

解題思路二:把對象寫入記憶體,完成序列化

使用ByteArrayInputStream和ByteArrayOutputStream 兩個基于記憶體的流

注:

  • 調用ByteArrayInputStream或ByteArrayOutputStream對象的close方法沒有任何意義
  • 這兩個基于記憶體的流隻要垃圾回收器清理對象就能夠釋放資源,這一點不同于對外部資源(如檔案流)的釋放

代碼詳解

public class Test2 {
    public static void main(String[] args) {
        System.out.println("序列化之前:");
        Address address = new Address("中國", "吉林", "長春");// 建立address對象
        Employee employee1 = new Employee("小虛竹", 30, address);// 建立employee1對象
        System.out.println("員工1的資訊:");
        System.out.println(employee1);// 輸出employee1對象
        System.out.println("序列化之後:");
       
        Employee employee2 = null;
        try {
            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bout);
            oos.writeObject(employee1);
            ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bin);
            employee2 = (Employee) ois.readObject();// 從記憶體讀取對象
        } catch (Exception e) {
            e.printStackTrace();
        } 

        if (employee2 != null) {
            employee2.getAddress().setState("中國"); // 修改員工位址
            employee2.getAddress().setProvince("四川"); // 修改員工位址
            employee2.getAddress().setCity("成都"); // 修改員工位址
            employee2.setName("大虛竹"); // 修改員工名字
            employee2.setAge(24);// 修改員工年齡
            System.out.println("員工1的資訊:");
            System.out.println(employee1);// 輸出employee1對象
            System.out.println("員工2的資訊:");
            System.out.println(employee2);// 輸出employee2對象
        }

    }

}

           
《JAVA築基100例》「第35題」JAVA進階技術-序列化與對象克隆

解題思路三:引用springframework

springframework有一個工具方法,常用于對象的克隆

BeanUtils.copyProperties(employee1,employee2);

注意:「BeanUtils.copyProperties」 方法本質上隻是淺克隆,對于引用類型的參數是無法克隆的,「隻是複制引用,不是克隆值」 ,是以要額外處理

代碼詳解

pom引入

<properties>
        <org.springframework.version>4.3.25.RELEASE</org.springframework.version>   
    </properties>
    
    <dependencies>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${org.springframework.version}</version>
        </dependency>
    </dependencies>
           

測試類

public class Test3 {
    public static void main(String[] args) {
        System.out.println("序列化之前:");
        Address address = new Address("中國", "吉林", "長春");// 建立address對象
        Employee employee1 = new Employee("小虛竹", 30, address);// 建立employee1對象
        System.out.println("員工1的資訊:");
        System.out.println(employee1);// 輸出employee1對象
        System.out.println("序列化之後:");
       
        Employee employee2 = new Employee();
        BeanUtils.copyProperties(employee1,employee2);
        employee2.setAddress(new Address("中國","四川","成都"));
            employee2.setName("大虛竹"); // 修改員工名字
            //employee2.setAge(24);// 修改員工年齡
            System.out.println("員工1的資訊:");
            System.out.println(employee1);// 輸出employee1對象
            System.out.println("員工2的資訊:");
            System.out.println(employee2);// 輸出employee2對象
    }

}

           
《JAVA築基100例》「第35題」JAVA進階技術-序列化與對象克隆

如圖

解題思路四:引用使用kryo序列化

Kryo 序列化:基于Java的快速高效的對象序列化架構,旨在提供快速、高效和易用的API。

Kryo的優勢是序列化後size小且速度快,代碼簡單,号稱 Java 中最快的序列化架構。

建立一個SerializationKryoUtils工具類

提供serializationObject序列化對象方法

提供deserialization反序列化對象方法

支援從對象--》位元組--》對象的克隆。

代碼詳解

pom引入

<!--        使用kryo序列化-->
        <dependency>
            <groupId>com.esotericsoftware</groupId>
            <artifactId>kryo</artifactId>
            <version>4.0.2</version>
        </dependency>
        <dependency>
            <groupId>de.javakaffee</groupId>
            <artifactId>kryo-serializers</artifactId>
            <version>0.45</version>
        </dependency>
           

SerializationKryoUtils

package com.xiaoxuzhu;

import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;

import org.objenesis.strategy.StdInstantiatorStrategy;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;

/**
 * Description: 使用kryo序列化工具包
 *
 * @author xiaoxuzhu
 * @version 1.0
 *
 * <pre>
 * 修改記錄:
 * 修改後版本         修改人  修改日期   修改内容
 * 2022/4/19.1     xiaoxuzhu  2022/4/19      Create
 * </pre>
 * @date 2022/4/19
 */
public class SerializationKryoUtils {


    private static final ThreadLocal<Kryo> kryoThreadLocal = new ThreadLocal<Kryo>() {
        @Override
        protected Kryo initialValue() {
            Kryo kryo = new Kryo();
            UnmodifiableCollectionsSerializer.registerSerializers(kryo);
            kryo.setRegistrationRequired(false);
            kryo.setInstantiatorStrategy(new Kryo.DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
            return kryo;
        }
    };

    public static Kryo getInstance() {
        return kryoThreadLocal.get();
    }


    /**
     * 序列化對象
     *
     * @param source 對象
     * @param <T>
     * @return
     */
    public static <T extends Serializable> byte[] serializationObject(T source) {
        Kryo kryo = getInstance();
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        Output output = new Output(byteArrayOutputStream);
        try {
            kryo.writeClassAndObject(output, source);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            kryoThreadLocal.remove();
        }
        output.flush();
        output.close();
        byte[] bytes = byteArrayOutputStream.toByteArray();
        try {
            byteArrayOutputStream.flush();
            byteArrayOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;

    }

    /**
     * 反序列化對象
     *
     * @param source
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T deserializationObject(byte[] source) {
        Kryo kryo = getInstance();
        try {
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(source);
            Input input = new Input(byteArrayInputStream);
            return (T) kryo.readClassAndObject(input);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            kryoThreadLocal.remove();
        }
        return null;
    }


}

           

測試類:

package com.xiaoxuzhu;

/**
 * Description: 測試類
 *
 * @author xiaoxuzhu
 * @version 1.0
 *
 * <pre>
 * 修改記錄:
 * 修改後版本         修改人  修改日期   修改内容
 * 2022/4/19.1     xiaoxuzhu  2022/4/19      Create
 * </pre>
 * @date 2022/4/19
 */
public class Test4 {
    public static void main(String[] args) {
        System.out.println("序列化之前:");
        Address address = new Address("中國", "吉林", "長春");// 建立address對象
        Employee employee1 = new Employee("小虛竹", 30, address);// 建立employee1對象
        System.out.println("員工1的資訊:");
        System.out.println(employee1);// 輸出employee1對象
        System.out.println("序列化之後:");
       
        Employee employee2 =null;
        byte[] bytes =  SerializationKryoUtils.serializationObject(employee1);
        employee2 =  SerializationKryoUtils.deserializationObject(bytes);
        employee2.setAddress(new Address("中國","四川","成都"));
            employee2.setName("大虛竹"); // 修改員工名字
            //employee2.setAge(24);// 修改員工年齡
            System.out.println("員工1的資訊:");
            System.out.println(employee1);// 輸出employee1對象
            System.out.println("員工2的資訊:");
            System.out.println(employee2);// 輸出employee2對象
    }

}

           
《JAVA築基100例》「第35題」JAVA進階技術-序列化與對象克隆

我是虛竹哥,我們下一題見~

繼續閱讀