創建型設計模式成員
- 工廠方法模式
- 抽象工廠模式
- 建造者模式
- 原型模式
- 單例模式
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;
}
}
運行結果:
下面我們修改一下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;
}
}
執行反序列操作,這次就可以反序列成功了,如下所示:
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)。 這就叫做引用拷貝。
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
結果分析:由輸出結果可以看出,它們的地址是不同的,也就是說創建了新的對象, 而不是把原對象的地址賦給了一個新的引用變量,這就叫做對象拷貝。
深拷貝和淺拷貝都是對象拷貝
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引用指向的是同一個對象,所以說明是淺拷貝。
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更改前:
teacher姓名Jam更改後:
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
結果分析:說明用序列化的方式實現了對象的深拷貝