創建型設計模式

創建型設計模式成員

  • 工廠方法模式
  • 抽象工廠模式
  • 建造者模式
  • 原型模式
  • 單例模式

1. 簡單工廠模式

1.1 定義

簡單工廠模式(Simple Factory Pattern):它屬於類創建型模式。在簡單工廠模式中,可以根據參數的不同返回不同類的實例。簡單工廠模式專門定義一個類來負責創建其他類的實例,被創建的實例通常都具有共同的父類

1.2 角色組成

  • Factory:工廠角色
  • Product:抽象產品角色
  • ConcreteProduct:具體產品角色

1.2.1 工廠角色Factory

SimpleFactory提供getMilk(String name)方法, 只需要告訴簡單工廠牛奶品牌名稱, 就能獲得對應的牛奶


package com.zhunongyun.spring.springpattern.factory.simple;

import com.zhunongyun.spring.springpattern.factory.Mengniu;
import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Telunsu;
import com.zhunongyun.spring.springpattern.factory.Yili;

public class SimpleFactory {

public Milk getMilk(String name){
    Milk milk = null;
    switch (name) {
        case "特侖蘇":
            milk = new Telunsu();
            break;
        case "伊利":
            milk = new Yili();
            break;
        case "蒙牛":
            milk = new Mengniu();
            break;
        default:
            System.out.println("不能生產您所需的產品");
            break;
    }
    return milk;
}

}


### 1.2.2 抽象產品角色Product
> 抽象產品類Milk, 定義產品的屬性, 具體產品都需要實現其屬性

package com.zhunongyun.spring.springpattern.factory;

public interface Milk {
/**

  • 獲取一個標準產品
  • @return
    */
    public String getName();
    }

1.2.3 具體產品角色ConcreteProduct

具體產品類SanLu, YiLi, Telunsu是具體的產品, 需要實現抽象產品類Milk


YiLi:
package com.zhunongyun.spring.springpattern.factory;

public class Yili implements Milk {br/>@Override
public String getName() {
return "伊利";
}
}

Telunsu:
package com.zhunongyun.spring.springpattern.factory;

public class Telunsu implements Milk {br/>@Override
public String getName() {
return "特侖蘇";
}
}

SanLu:
package com.zhunongyun.spring.springpattern.factory;

public class Sanlu implements Milk{br/>@Override
public String getName() {
return "三鹿";
}
}


### 1.2.4 測試

package com.zhunongyun.spring.springpattern.factory.simple;

public class SimpleFactoryTest {

public static void main(String[] args) {
    SimpleFactory factory = new SimpleFactory();

    //把用戶的需求告訴工廠,需要用戶提供具體產品的名稱等信息,用戶把產品創建交給工廠處理
    System.out.println(factory.getMilk("AAA"));
    System.out.println(factory.getMilk("蒙牛"));
}

}
輸出:
不能生產您所需的產品
null
com.zhunongyun.spring.springpattern.factory.Mengniu@129a8472


## 1.3 總結
由工廠, 抽象產品, 具體產品三個角色組成,用戶需要向工廠提供產品名稱信息,創建其中的一款產品, 新增產品需要增加新的具體產品,工廠也需要修改  

# 2. 工廠方法模式
## 2.1 定義
工廠模式方法(factory method),定義一個用於創建對象的接口,讓子類決定實例化哪一個類。工廠方法使一個類的實例化延遲到子類  

## 2.2 角色組成
* 抽象工廠
* 具體工廠
* 抽象產品
* 具體產品

### 2.2.1 抽象工廠

package com.zhunongyun.spring.springpattern.factory.func;

import com.zhunongyun.spring.springpattern.factory.Milk;

public interface Factory {
//工廠必然具有生產產品技能,統一的產品出口
Milk getMilk();
}


### 2.2.2 具體工廠

MengniuFactory:

package com.zhunongyun.spring.springpattern.factory.func;

import com.zhunongyun.spring.springpattern.factory.Mengniu;
import com.zhunongyun.spring.springpattern.factory.Milk;

public class MengniuFactory implements Factory {br/>@Override
public Milk getMilk() {
return new Mengniu();
}
}

SanluFactory:

package com.zhunongyun.spring.springpattern.factory.func;

import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Sanlu;

public class SanluFactory implements Factory {br/>@Override
public Milk getMilk() {
return new Sanlu();
}
}

TelunsuFactory:

package com.zhunongyun.spring.springpattern.factory.func;

import com.zhunongyun.spring.springpattern.factory.Milk;
import com.zhunongyun.spring.springpattern.factory.Telunsu;

public class TelunsuFactory implements Factory {

@Override
public Milk getMilk() {
    return new Telunsu();
}

}


### 2.2.3 抽象產品

package com.zhunongyun.spring.springpattern.factory;

public interface Milk {
/**

  • 獲取一個標準產品
  • @return
    */
    public String getName();
    }

2.2.4 具體產品

YiLi:
package com.zhunongyun.spring.springpattern.factory;

public class Yili implements Milk {
    @Override
    public String getName() {
        return "伊利";
    }
}

Telunsu:
package com.zhunongyun.spring.springpattern.factory;

public class Telunsu implements Milk {
    @Override
    public String getName() {
        return "特侖蘇";
    }
}

SanLu:
package com.zhunongyun.spring.springpattern.factory;

public class Sanlu implements  Milk{
    @Override
    public String getName() {
        return "三鹿";
    }
}

2.2.5 測試

package com.zhunongyun.spring.springpattern.factory.func;

public class FactoryTest {
    public static void main(String[] args) {
        Factory factory = new SanluFactory();
        System.out.println(factory.getMilk());
    }
}
輸出:
com.zhunongyun.spring.springpattern.factory.Sanlu@1b0375b3

2.3 總結

在工廠方法模式下,如果要增加產品,只需要擴展對應的具體工廠(ConcreteCreator)和具體產品(ConcreteProduct)即可,原有源碼無需改變,遵循了“開閉原則”
用戶需要某款產品只需要實例化對應的產品,此時沒有創建產品,只要當需要使用時纔會創建產品,用戶在使用時都是以方法的事情操作產品

3. 抽象工廠模式

3.1 定義

抽象工廠模式(Abstract Factory Pattern)是圍繞一個超級工廠創建其他工廠。該超級工廠又稱爲其他工廠的工廠。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式

3.2 角色組成

  • 抽象工廠
  • 具體工廠
  • 抽象產品
  • 具體產品

3.2.1 抽象工廠

package com.zhunongyun.spring.springpattern.factory.abstr;

import com.zhunongyun.spring.springpattern.factory.Milk;

public abstract class AbstractFactory {
    //公共的邏輯
    //方便於統一管理

    /**
     * 獲得一個蒙牛品牌的牛奶
     * @return
     */
    public  abstract Milk getMengniu();

    /**
     * 獲得一個伊利品牌的牛奶
     * @return
     */
    public abstract  Milk getYili();

    /**
     * 獲得一個特侖蘇品牌的牛奶
     * @return
     */
    public  abstract  Milk getTelunsu();

    public abstract Milk getSanlu();

}

3.2.2 具體工廠

package com.zhunongyun.spring.springpattern.factory.abstr;

import com.zhunongyun.spring.springpattern.factory.*;

public class MilkFactory extends  AbstractFactory {
    @Override
    public Milk getMengniu() {
        return new Mengniu();
    }

    @Override
    public Milk getYili() {
        return new Yili();
    }

    @Override
    public Milk getTelunsu() {
        return new Telunsu();
    }

    @Override
    public Milk getSanlu() {
        return new Sanlu();
    }
}

3.3.3 測試

package com.zhunongyun.spring.springpattern.factory.abstr;

public class AbstractFactoryTest {
    public static void main(String[] args) {
        MilkFactory factory = new MilkFactory();

        //對於用戶而言,更加簡單了
        //用戶只有選擇的權利了,保證了程序的健壯性
        System.out.println(factory.getSanlu());
    }
}
輸出:
com.zhunongyun.spring.springpattern.factory.Sanlu@1b0375b3

3.4 總結

抽象工廠模式,定義了一個抽象工廠,抽象工廠中定義了創建對象的接口,由實現類實現對象創建,用戶在使用時,只需要實例化抽象工廠,就能通過接口獲取對象

4. 單例模式

4.1 定義

這種模式涉及到一個單一的類,該類負責創建自己的對象,同時確保只有單個對象被創建。保證一個類僅有一個實例,並提供一個訪問它的全局訪問點

4.2 單例模式的幾種實現方式

4.2.1 懶漢式

這種方式是lazy初始化,第一次調用才初始化,避免內存浪費, 多線程時爲了保證線程安全需要加鎖

線程不安全:

package com.zhunongyun.spring.springpattern.singleton;

public class Lazy {
    private static Lazy instance;
    private Lazy (){}

    public static Lazy getInstance() {
        if (instance == null) {
            instance = new Lazy();
        }
        return instance;
    }
}

線程安全:
package com.zhunongyun.spring.springpattern.singleton;

public class Lazy {
    private static Lazy instance;
    private Lazy (){}

    public static synchronized Lazy getInstance() {
        if (instance == null) {
            instance = new Lazy();
        }
        return instance;
    }
}

4.2.2 餓漢式

這種方式,類加載時就初始化,浪費內存,基於classloader機制避免了多線程的同步問題, 多線程下是安全的

package com.zhunongyun.spring.springpattern.singleton;

public class Hungry {
    private static Hungry instance = new Hungry();
    private Hungry(){}
    public static Hungry getInstance() {
        return instance;
    }  
}

4.2.3 雙重校驗鎖

這種方式採用雙鎖機制,安全且在多線程情況下能保持高性能

package com.zhunongyun.spring.springpattern.singleton;

public class DoubleCheckedLocking {
    private volatile static DoubleCheckedLocking singleton;

    private DoubleCheckedLocking() {
    }

    public static DoubleCheckedLocking getSingleton() {
        if (singleton == null) {
            synchronized (DoubleCheckedLocking.class) {
                if (singleton == null) {
                    singleton = new DoubleCheckedLocking();
                }
            }
        }
        return singleton;
    }
}

4.2.4 登記式/靜態內部類

這種方式能達到雙檢鎖方式一樣的功效,但實現更簡單。對靜態域使用延遲初始化,應使用這種方式而不是雙檢鎖方式。這種方式只適用於靜態域的情況,雙檢鎖方式可在實例域需要延遲初始化時使用
這種方式同樣利用了classloader機制來保證初始化instance時只有一個線程,SingletonHolder類沒有被主動使用,只有通過顯式調用getInstance方法時,纔會顯式裝載SingletonHolder類,從而實例化instance

package com.zhunongyun.spring.springpattern.singleton;

public class Register {
    private static class SingletonHolder {
        private static final Register INSTANCE = new Register();
    }

    private Register() {
    }

    public static final Register getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

4.2.5 枚舉

這種實現方式還沒有被廣泛採用,但這是實現單例模式的最佳方法。它更簡潔,自動支持序列化機制,絕對防止多次實例化,它不僅能避免多線程同步問題,而且還自動支持序列化機制,防止反序列化重新創建新的對象,絕對防止多次實例化

package com.zhunongyun.spring.springpattern.singleton;

/**
 * 數據庫連接
 */
public class DBConnection {

}

package com.zhunongyun.spring.springpattern.singleton;

public enum DataSourceEnum {
    DATASOURCE;
    private DBConnection connection = null;
    private DataSourceEnum() {
        connection = new DBConnection();
    }
    public DBConnection getConnection() {
        return connection;
    }
}

package com.zhunongyun.spring.springpattern.singleton;

public class DataSourceEnumTest {
    public static void main(String[] args) {
        DBConnection con1 = DataSourceEnum.DATASOURCE.getConnection();
        DBConnection con2 = DataSourceEnum.DATASOURCE.getConnection();

        System.out.println(con1 == con2);
    }
}
輸出:
true

結果表明兩次獲取返回了相同的實例

Java規範中規定,每一個枚舉類型極其定義的枚舉變量在JVM中都是唯一的,因此在枚舉類型的序列化和反序列化上,Java做了特殊的規定
在序列化的時候Java僅僅是將枚舉對象的name屬性輸出到結果中,反序列化的時候則是通過 java.lang.Enum 的valueOf()方法來根據名字查找枚舉對象
也就是說,以下面枚舉爲例,序列化的時候只將DATASOURCE這個名稱輸出,反序列化的時候再通過這個名稱,查找對於的枚舉類型,因此反序列化後的實例也會和之前被序列化的對象實例相同

枚舉會是線程安全、序列化與反序列化是相同的實例

4.3 總結

一般情況下,不建議使用4.2.1懶漢方式,建議使用4.2.2餓漢方式。只有在要明確實現lazy loading效果時,纔會使用4.2.4登記方式。如果涉及到反序列化創建對象時,可以嘗試使用4.2.5枚舉方式。如果有其他特殊的需求,可以考慮使用4.2.3雙檢鎖方式

5. 原型模式

5.1 定義

原型模式(Prototype Pattern)是用於創建重複的對象,同時又能保證性能。這種類型的設計模式屬於創建型模式,它提供了一種創建對象的最佳方式。

這種模式是實現了一個原型接口,該接口用於創建當前對象的克隆。當直接創建對象的代價比較大時,則採用這種模式。例如,一個對象需要在一個高代價的數據庫操作之後被創建。我們可以緩存該對象,在下一個請求時返回它的克隆,在需要的時候更新數據庫,以此來減少數據庫調用

5.2 序列化和反序列化

5.2.1 序列化和反序列化的概念

  • 序列化: 把對象轉換爲字節序列的過程稱爲對象的序列化
  • 反序列化: 把字節序列恢復爲對象的過程稱爲對象的反序列化

對象的序列化主要有兩種用途:

  • 1 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;
  • 2 在網絡上傳送對象的字節序列。

當兩個進程在進行遠程通信時,彼此可以發送各種類型的數據。無論是何種類型的數據,都會以二進制序列的形式在網絡上傳送。發送方需要把這個Java對象轉換爲字節序列,才能在網絡上傳送;接收方則需要把字節序列再恢復爲Java對象

5.2.2 Serializable接口類

在JDK庫中只有實現了Serializable和Externalizable接口的類的對象才能被序列化。Externalizable接口繼承自Serializable接口,實現Externalizable接口的類完全由自身來控制序列化的行爲,而僅實現Serializable接口的類可以採用默認的序列化方式

package com.zhunongyun.spring.prototype;

import lombok.Data;
import java.io.Serializable;

/**
 * 測試對象序列化和反序列化
 */
@Data
public class Person implements Serializable {

    /**
     * 序列化ID
     */
    private static final long serialVersionUID = -5809782578272943999L;
    private int age;
    private String name;
    private String sex;
}

5.2.3 serialVersionUID的作用

serialVersionUID字面意思上是序列化的版本號,凡是實現Serializable接口的類都有一個表示序列化版本標識符的靜態變量

private static final long serialVersionUID = -5809782578272943999L;

兩種生成方式:

  • 1 default serial version ID
  • 2 generated serial version ID
// default serial version ID
private static final long serialVersionUID = 1L;

// generated serial version ID
private static final long serialVersionUID = 4603642343377807741L;

那麼serialVersionUID(序列化版本號)到底有什麼用呢,我們用如下的例子來說明一下serialVersionUID的作用,看下面的代碼:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class TestSerialversionUID {

    public static void main(String[] args) throws Exception {
        SerializeCustomer();// 序列化Customer對象
        Customer customer = DeserializeCustomer();// 反序列Customer對象
        System.out.println(customer);
    }

    /**
     * MethodName: SerializeCustomer 
     * Description: 序列化Customer對象
     * @author xudp
     * @throws FileNotFoundException
     * @throws IOException
     */
    private static void SerializeCustomer() throws FileNotFoundException,
            IOException {
        Customer customer = new Customer("gacl",25);
        // ObjectOutputStream 對象輸出流
        ObjectOutputStream oo = new ObjectOutputStream(new FileOutputStream(
                new File("E:/Customer.txt")));
        oo.writeObject(customer);
        System.out.println("Customer對象序列化成功!");
        oo.close();
    }

    /**
     * MethodName: DeserializeCustomer 
     * Description: 反序列Customer對象
     * @author xudp
     * @return
     * @throws Exception
     * @throws IOException
     */
    private static Customer DeserializeCustomer() throws Exception, IOException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
                new File("E:/Customer.txt")));
        Customer customer = (Customer) ois.readObject();
        System.out.println("Customer對象反序列化成功!");
        return customer;
    }
}

/**
 * <p>ClassName: Customer<p>
 * <p>Description: Customer實現了Serializable接口,可以被序列化<p>
 * @author xudp
 * @version 1.0 V
 * @createTime 2014-6-9 下午04:20:17
 */
class Customer implements Serializable {
    //Customer類中沒有定義serialVersionUID
    private String name;
    private int age;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*
     * @MethodName toString
     * @Description 重寫Object類的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

運行結果:

image

下面我們修改一下Customer類,添加多一個sex屬性,如下:

class Customer implements Serializable {
    //Customer類中沒有定義serialVersionUID
    private String name;
    private int age;

    //新添加的sex屬性
    private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /*
     * @MethodName toString
     * @Description 重寫Object類的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

然後執行反序列操作,此時就會拋出如下的異常信息:

Exception in thread "main" java.io.InvalidClassException: Customer; 
local class incompatible: 
stream classdesc serialVersionUID = -88175599799432325, 
local class serialVersionUID = -5182532647273106745

意思就是說,文件流中的class和classpath中的class,也就是修改過後的class,不兼容了,處於安全機制考慮,程序拋出了錯誤,並且拒絕載入。那麼如果我們真的有需求要在序列化後添加一個字段或者方法呢?應該怎麼辦?那就是自己去指定serialVersionUID。在TestSerialversionUID例子中,沒有指定Customer類的serialVersionUID的,那麼java編譯器會自動給這個class進行一個摘要算法,類似於指紋算法,只要這個文件多一個空格,得到的UID就會截然不同的,可以保證在這麼多類中,這個編號是唯一的。所以,添加了一個字段後,由於沒有顯指定serialVersionUID,編譯器又爲我們生成了一個UID,當然和前面保存在文件中的那個不會一樣了,於是就出現了2個序列化版本號不一致的錯誤。因此,只要我們自己指定了serialVersionUID,就可以在序列化後,去添加一個字段,或者方法,而不會影響到後期的還原,還原後的對象照樣可以使用,而且還多了方法或者屬性可以用。

下面繼續修改Customer類,給Customer指定一個serialVersionUID,修改後的代碼如下:

class Customer implements Serializable {
    /**
     * Customer類中定義的serialVersionUID(序列化版本號)
     */
    private static final long serialVersionUID = -5182532647273106745L;
    private String name;
    private int age;

    //新添加的sex屬性
    //private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /*public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }*/

    /*
     * @MethodName toString
     * @Description 重寫Object類的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

重新執行序列化操作,將Customer對象序列化到本地硬盤的Customer.txt文件存儲,然後修改Customer類,添加sex屬性,修改後的Customer類代碼如下:

class Customer implements Serializable {
    /**
     * Customer類中定義的serialVersionUID(序列化版本號)
     */
    private static final long serialVersionUID = -5182532647273106745L;
    private String name;
    private int age;

    //新添加的sex屬性
    private String sex;

    public Customer(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Customer(String name, int age,String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    /*
     * @MethodName toString
     * @Description 重寫Object類的toString()方法
     * @author xudp
     * @return string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "name=" + name + ", age=" + age;
    }
}

執行反序列操作,這次就可以反序列成功了,如下所示:
image

5.2.4 serialVersionUID的取值

serialVersionUID的取值是Java運行時環境根據類的內部細節自動生成的。如果對類的源代碼作了修改,再重新編譯,新生成的類文件的serialVersionUID的取值有可能也會發生變化。

類的serialVersionUID的默認值完全依賴於Java編譯器的實現,對於同一個類,用不同的Java編譯器編譯,有可能會導致不同的serialVersionUID,也有可能相同。爲了提高serialVersionUID的獨立性和確定性,強烈建議在一個可序列化類中顯示的定義serialVersionUID,爲它賦予明確的值。

顯式地定義serialVersionUID有兩種用途:
1、 在某些場合,希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有相同的serialVersionUID;
2、 在某些場合,不希望類的不同版本對序列化兼容,因此需要確保類的不同版本具有不同的serialVersionUID。

5.3 深複製與淺複製

5.3.1 拷貝的引入

5.3.1.1 引用拷貝

創建一個指向對象的引用變量的拷貝。

Teacher teacher = new Teacher("Taylor",26);
Teacher otherteacher = teacher;
System.out.println(teacher);
System.out.println(otherteacher);

輸出結果:
blog.Teacher@355da254
blog.Teacher@355da254

結果分析:由輸出結果可以看出,它們的地址值是相同的,那麼它們肯定是同一個對象。teacher和otherteacher的只是引用而已,他們都指向了一個相同的對象Teacher(“Taylor”,26)。 這就叫做引用拷貝。

image

5.3.1.2 對象拷貝

創建對象本身的一個副本。

Teacher teacher = new Teacher("Swift",26);
Teacher otherteacher = (Teacher)teacher.clone();
System.out.println(teacher);
System.out.println(otherteacher);

輸出結果:
blog.Teacher@355da254
blog.Teacher@4dc63996

結果分析:由輸出結果可以看出,它們的地址是不同的,也就是說創建了新的對象, 而不是把原對象的地址賦給了一個新的引用變量,這就叫做對象拷貝。

image

深拷貝和淺拷貝都是對象拷貝

5.3.2 淺拷貝

5.3.2.1 定義:

被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。即對象的淺拷貝會對“主”對象進行拷貝,但不會複製主對象裏面的對象。”裏面的對象“會在原來的對象和它的副本之間共享。

簡而言之,淺拷貝僅僅複製所考慮的對象,而不復制它所引用的對象

5.3.2.2 淺拷貝實例

package blog;

/**
 * Created by 白夜行 on 2017/5/8.
 */
public class ShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Teacher teacher = new Teacher();
        teacher.setName("Delacey");
        teacher.setAge(29);

        Student2 student1 = new Student2();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        Student2 student2 = (Student2) student1.clone();
        System.out.println("拷貝後");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("修改老師的信息後-------------");

        // 修改老師的信息
        teacher.setName("Jam");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
    }

}

class Teacher implements Cloneable
{
    private String name;
    private int age;

    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;
    }

}

class Student2 implements Cloneable
{
    private String name;
    private int age;
    private Teacher teacher;

    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 Teacher getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher teacher)
    {
        this.teacher = teacher;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        Object object = super.clone();
        return object;
    }
}

輸出結果:
拷貝後
Dream
18
Delacey
29
修改老師的信息後-------------
Jam
Jam

結果分析: 兩個引用student1和student2指向不同的兩個對象,但是兩個引用student1和student2中的兩個teacher引用指向的是同一個對象,所以說明是淺拷貝。

image

5.3.3 .深拷貝

5.3.3.1 定義

深拷貝是一個整個獨立的對象拷貝,深拷貝會拷貝所有的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一起拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢並且花銷較大。

簡而言之,深拷貝把要複製的對象所引用的對象都複製了一遍。

5.3.3.2 實現深拷貝(實例1)

package blog;

/**
 * Created by 白夜行 on 2017/5/8.
 */

public class DeepCopy {
    public static void main(String[] args) throws Exception
    {
        Teacher2 teacher = new Teacher2();
        teacher.setName("Delacey");
        teacher.setAge(29);

        Student3 student1 = new Student3();
        student1.setName("Dream");
        student1.setAge(18);
        student1.setTeacher(teacher);

        Student3 student2 = (Student3) student1.clone();
        System.out.println("拷貝後");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("修改老師的信息後-------------");

        // 修改老師的信息
        teacher.setName("Jam");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());
    }
}

class Teacher2 implements Cloneable {
    private String name;
    private int age;

    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;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }

}

class Student3 implements Cloneable {
    private String name;
    private int age;
    private Teacher2 teacher;

    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 Teacher2 getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher2 teacher)
    {
        this.teacher = teacher;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        // 淺複製時:
        // Object object = super.clone();
        // return object;

        // 改爲深複製:
        Student3 student = (Student3) super.clone();
        // 本來是淺複製,現在將Teacher對象複製一份並重新set進來
        student.setTeacher((Teacher2) student.getTeacher().clone());
        return student;
    }

}

輸出結果:
拷貝後
Dream
18
Delacey
29
修改老師的信息後-------------
Jam
Delacey

結果分析:
兩個引用student1和student2指向不同的兩個對象,兩個引用student1和student2中的兩個teacher引用指向的是兩個對象,但對teacher對象的修改只能影響student1對象,所以說是深拷貝

teacher姓名Delacey更改前:
image

teacher姓名Jam更改後:
image

5.3.3.3 利用序列化實現深拷貝

package blog;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Created by 白夜行 on 2017/5/13.
 */
public class DeepCopyServiable {
    public static void main(String[] args) throws Exception {
        Teacher3 t = new Teacher3();
        t.setName("Taylor");
        t.setAge(28);

        Student3 s1 = new Student3();
        s1.setAge(20);
        s1.setName("blank space");
        s1.setTeacher(t);

        Student3 s2 = (Student3) s1.deepClone();

        System.out.println("拷貝後:");
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getTeacher().getName());
        System.out.println(s2.getTeacher().getAge());
        System.out.println("---------------------------");

        t.setName("swift");

        System.out.println("修改後:");
        System.out.println(s1.getTeacher().getName());
        System.out.println(s2.getTeacher().getName());
    }

}

class Teacher3 implements Serializable
{
    private String name;
    private int age;

    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;
    }

}

class Student3 implements Serializable
{
    private String name;
    private int age;
    private Teacher3 teacher;

    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 Teacher3 getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher3 teacher)
    {
        this.teacher = teacher;
    }

    public Object deepClone() throws Exception
    {
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }

}

輸出結果:
拷貝後:
blank space
20
Taylor
28
---------------------------
修改後:
swift
Taylor

結果分析:說明用序列化的方式實現了對象的深拷貝

6. 建造者模式

6.1 定義

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