创建型模式
单例模式
单例其实就是相对于系统来说唯一的一个存在。这里引用百度百科中对单例的解释:“是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。” 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
单例模式的设计规则
由定义我们可以很清晰的抽象出: 实现Java单例模式类有哪些通用设计规则?
(1)私有化类构造器。
(2)定义静态私有的类对象。
(3)提供公共静态的获取该私有类对象的方法。
了解了单例模式的概念,以及单例模式的通用设计规则,对于如何实现一个Java单例,应该是没什么阻碍了。这里我们还是要思考下单例模式的优点,或者说有啥好处,使用场景是什么?带着这些问题我们就能更好的设计单例模式。
为什么使用单例
1.Java单例模式解决了什么问题?
答:Java的单例模式主要解决了多线程并发访问共享资源的线程安全问题。
2.Java单例模式主要应用场景有哪些?
答:1.共享资源的访问与操作场景,如Windows系统的资源管理器,Windows系统的回收站, 显卡的驱动程序,系统的配置文件,工厂本身(类模板),应用程序的日志对象等。
2.控制资源访问与操作的场景,如数据库的连接池,Java的线程池等。
单例模式的设计
单例模式的命名
单例的命名通常包含 singleton(以 singleton 开头或结尾) 或能按名称实际意义区分出在应用中唯一。
了解了Java单例模式出现的缘由以及出现的场合,那么,Java的单例究竟是以怎样的方式出现在这些场合中呢? Java中单例模式的常用实现方式有哪些?
实现
我们将创建一个 SingleObject 类。SingleObject 类有它的私有构造函数和本身的一个静态实例。
SingleObject 类提供了一个静态方法,供外界获取它的静态实例。SingletonPatternDemo,我们的演示类使用 SingleObject 类来获取 SingleObject 对象。
饿汉式
饿汉式,顾名思义,就是指在JVM首次访问到该单例类时,就会把该单例类对象创建出来,并保存在内存中,不管后续是否会使用到这个单例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class HungrySingleton {
private HungrySingleton() {}
private static HungrySingleton instance = new HungrySingleton();
public static HungrySingleton getInstance() { return instance; }
}
|
懒汉式(4种)
懒汉式,相比于饿汉式,它是指JVM首次访问到该单例类时,并不会实例化该单例类对象,只有等到后续被外部调用时,才会实例化该单例类对象。 饿汉式的写法比较固定,懒汉式由于延时加载的特性,写法上有一些变化,一般来说,懒汉式存在4个变种。
1、懒汉式1——无锁懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class LazySingletonWithoutSync {
private LazySingletonWithoutSync() {}
private static LazySingletonWithoutSync instance;
public static LazySingletonWithoutSync getInstance() { if (instance == null) { instance = new LazySingletonWithoutSync(); }
return instance; }
}
|
2、懒汉式2——Sync同步锁懒汉式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class LazySingletonWithSync {
private LazySingletonWithSync() {}
private static LazySingletonWithSync instance;
public static synchronized LazySingletonWithSync getInstance() { if (instance == null) { instance = new LazySingletonWithSync(); }
return instance; }
}
|
3、懒汉式3——双重锁检查
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public class LazySingletonWithDoubleCheck {
private LazySingletonWithDoubleCheck() {}
private static volatile LazySingletonWithDoubleCheck instance;
public static LazySingletonWithDoubleCheck getInstance() { if (instance == null) { synchronized (LazySingletonWithDoubleCheck.class) { if (instance == null) { instance = new LazySingletonWithDoubleCheck(); } } }
return instance; }
}
|
4、懒汉式4——静态内部类
静态内部类和非静态内部类一样,都是在被调用时才会被加载,以此来实现懒汉模式。所用环境,在单例对象占用资源大,需要延时加载的情况下优选。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class LazySingletonWithInnerClass {
private LazySingletonWithInnerClass() {}
private static class LazyHolder { private static final LazySingletonWithInnerClass INSTANCE = new LazySingletonWithInnerClass(); }
public static LazySingletonWithInnerClass getInstance() { return LazyHolder.INSTANCE; }
}
|
注册登记式
每使用一次,都往一个固定的容器中去注册并将使用过的对象进行缓存,下次去取对象的时候,就直接从缓存中取值,以保证每次获取的都是同一个对象。Spring IOC中的单例模式,就是典型的注册登记式单例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
public class RegisterSingletonFromMap {
private RegisterSingletonFromMap() {}
private static Map<String, RegisterSingletonFromMap> map = new ConcurrentHashMap<String, RegisterSingletonFromMap>();
public static RegisterSingletonFromMap getInstance() { String className = RegisterSingletonFromMap.class.getName(); synchronized (RegisterSingletonFromMap.class) { if (!map.containsKey(className)) { map.put(className, new RegisterSingletonFromMap()); } }
return map.get(className); }
}
|
枚举型单例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public class RegisterSingletonFromEnum {
private RegisterSingletonFromEnum() {}
private enum Singleton { INSTANCE;
private RegisterSingletonFromEnum instance;
Singleton() { instance = new RegisterSingletonFromEnum(); } }
public static RegisterSingletonFromEnum getInstance() { return Singleton.INSTANCE; }
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
|
public class SingletonTest08 { public static void main(String[] args) { Singleton instance = Singleton.INSTANCE; Singleton instance2 = Singleton.INSTANCE; System.out.println(instance == instance2); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode());
instance.sayOK(); } }
enum Singleton { INSTANCE; Singleton(){ } public void sayOK() { System.out.println("ok~"); } }
|
序列化与反序列化式
序列化与反序列化式单例,需要重写readResolve()方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class SerializableSingleton implements Serializable {
private SerializableSingleton() {}
private static final SerializableSingleton singleton = new SerializableSingleton();
public static SerializableSingleton getInstance() { return singleton; }
private Object readResolve() throws ObjectStreamException { return singleton; }
}
|
单例模式要点总结
Singleton 模式中的实例构造器可以设置为 protected 以允许子类派生。
Singleton 模式一般不要实现 Clone 接口,因为这有可能导致多个对象实例,与 Singleton 模式的初衷违背。
如何实现多线程环境下安全的 Singleton? 需注意对双检查锁的正确实现。
工厂模式
工厂方法模式是对简单工厂的一个衍生,解决了许多简单工厂模式的问题。首先完全实现‘开-闭 原则’,实现了可扩展。其次更复杂的层次结构,可以应用于产品结果复杂的场合。 说了这么多我们先来看看什么情况下使用工厂模式。
应用场景
第一种情况是对于某个产品,调用者清楚地知道应该使用哪个具体工厂服务,实例化该具体工厂,生产出具体的产品来。Java Collection中的iterator() 方法即属于这种情况。
第二种情况,只是需要一种产品,而不想知道也不需要知道究竟是哪个工厂为生产的,即最终选用哪个具体工厂的决定权在生产者一方,它们根据当前系统的情况来实例化一个具体的工厂返回给使用者,而这个决策过程这对于使用者来说是透明的。
工厂方法模式是在简单工厂模式的衍生,所以我们先来了解下什么是简单工厂。
简单工厂模式
1、首先先创建一个car的抽象类定义一个run方法
1 2 3 4
| public interface Car {
public void run(); }
|
2、创建实现类 具体的两个产品
宝马车实现类
1 2 3 4 5 6 7
| public class BMWCar implements Car {
public void run() { System.out.println("bao ma car is running"); } }
|
路虎车实现类
1 2 3 4 5 6 7
| public class LandRoverCar implements Car {
public void run() {
System.out.println("LandRover car is runnning "); } }
|
3、创建汽车工厂
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class CarFactory {
public static Car createBMCar() { return new BMWCar();
}
public static Car createLandRoverCar() { return new LandRoverCar();
} }
|
4、测试
1 2 3 4 5 6
| public class Test { public static void main(String[] args) { Car car = CarFactory.createBMCar(); car.run(); } }
|
测试结果 : bao ma car is running
总体来说,工厂模式适合:凡是出现了大量的产品需要创建,并且具有共同的接口时,可以通过工厂方法模式进行创建。但是同时简单工厂存在于这几个缺点。
缺点:
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
工厂方法模式
解决了什么问题
简单工厂模式存在着不易扩展,违背开闭原则的致命缺点。所以工厂方法就是在简单工厂的模式上解决它不易于扩展的缺点。
工厂方法的实现
1 2 3 4
| public interface Car {
public void run(); }
|
宝马车实现类
1 2 3 4 5 6
| public class BMWCar implements Car {
public void run() { System.out.println("bao ma car is running"); } }
|
路虎车实现类
1 2 3 4 5 6
| public class LandRoverCar implements Car {
public void run() { System.out.println("LandRover car is runnning "); } }
|
1 2 3 4
| public interface CarFactory {
public Car createCar(); }
|
宝马工厂
1 2 3 4 5 6
| public class BMWCarFactory implements CarFactory {
public Car createCar() { return new BMWCar(); } }
|
路虎工厂
1 2 3 4 5 6
| public class LandRoverCarFactory implements CarFactory {
public Car createCar() { return new LandRoverCar(); } }
|
1 2 3 4 5 6 7 8
| public class Consumer { public static void main(String[] args) { Car car1 = new BMWCarFactory().createCar(); Car car2 = new LandRoverCarFactory().createCar(); car1.run(); car2.run(); } }
|
总结:
这里这样设计的好处,就是如果需要加新产品,只需要新增工厂实现类就行了,不需要去改原来的代码了。这就易于扩展,解决了简单工厂的缺点。缺点就是代码量也变多了。
抽象工厂模式
抽象工厂模式(Abstract Factory Pattern)是围绕一个超级工厂创建其他工厂。该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。抽象工厂的源头是一个超级大厂,维护着若干个小厂。就算需求变了这边只需要改具体小厂的制作方法就行了。
1、产品类接口
手机类
1 2 3 4 5 6
| public interface PhoneProducton { void start(); void shutdown(); void call(); void sendMSS(); }
|
路由器类
1 2 3 4 5
| public interface RouterProduction { void start(); void shutdown(); void openWiFi(); }
|
2、具体品牌产品实现类
小米产品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class XiaoMiPhone implements PhoneProducton { @Override public void start() { System.out.println("小米手机开启"); }
@Override public void shutdown() { System.out.println("小米手机关闭"); }
@Override public void call() { System.out.println("小米手机打电话"); }
@Override public void sendMSS() { System.out.println("小米手机发短信"); } }
public class XiaoMiRouter implements RouterProduction{ @Override public void start() { System.out.println("小米路由器开启"); }
@Override public void shutdown() { System.out.println("小米路由器关闭"); }
@Override public void openWiFi() { System.out.println("小米WiFi开启"); } }
|
华为产品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class HuaWeiPhone implements PhoneProducton { @Override public void start() { System.out.println("华为手机开启"); }
@Override public void shutdown() { System.out.println("华为手机关闭"); }
@Override public void call() { System.out.println("华为手机打电话"); }
@Override public void sendMSS() { System.out.println("华为手机发短信"); } }
public class HuaWeiRouter implements RouterProduction{ @Override public void start() { System.out.println("华为路由器开启"); }
@Override public void shutdown() { System.out.println("华为路由器关闭"); }
@Override public void openWiFi() { System.out.println("华为WiFi开启"); } }
|
3、抽象工厂,生产抽象产品
1 2 3 4 5 6 7
| public interface Factory { PhoneProducton createPhone();
RouterProduction createRouter(); }
|
4、具体品牌工厂实现类,生产各自品牌产品
小米工厂
1 2 3 4 5 6 7 8 9 10 11
| public class XiaoMiFactory implements Factory{ @Override public PhoneProducton createPhone() { return new XiaoMiPhone(); }
@Override public RouterProduction createRouter() { return new XiaoMiRouter(); } }
|
华为工厂
1 2 3 4 5 6 7 8 9 10 11
| public class HuaWeiFactory implements Factory{ @Override public PhoneProducton createPhone() { return new HuaWeiPhone(); }
@Override public RouterProduction createRouter() { return new HuaWeiRouter(); } }
|
5、消费者测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Consumer { public static void main(String[] args) { System.out.println("---------小米产品----------"); PhoneProducton phone = new XiaoMiFactory().createPhone(); RouterProduction router = new XiaoMiFactory().createRouter(); phone.call(); router.openWiFi(); System.out.println("---------华为产品----------"); phone = new HuaWeiFactory().createPhone(); router = new HuaWeiFactory().createRouter(); phone.call(); router.openWiFi(); } }
|
大概逻辑图
从代码可以看出,如果产品增加,我们就需要加实现类就行了。但是这样创建产品的过程就非常复杂了,需要增加很多代码。所以抽象工厂是真的工厂,工厂方法模式就相当于一条生产线。
建造者模式
建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
一个 Builder 类会一步一步构造最终的对象。该 Builder 类是独立于其他对象的。
何时使用:一些基本部件不会变,而其组合经常变化的时候。
如何解决:将变与不变分离开。
应用实例: 1、去肯德基,汉堡、可乐、薯条、炸鸡翅等是不变的,而其组合是经常变化的,生成出所谓的”套餐”。 2、JAVA 中的 StringBuilder。
优点: 1、建造者独立,易扩展。 2、便于控制细节风险。
缺点: 1、产品必须有共同点,范围有限制。 2、如内部变化复杂,会有很多的建造类。
使用场景: 1、需要生成的对象具有复杂的内部结构。 2、需要生成的对象内部属性本身相互依赖。
注意事项:与工厂模式的区别是:建造者模式更加关注与零件装配的顺序。
建造者模式将复杂产品的构建过程封装在不同的方法中,使得创建过程非常清晰,能够让我们更加精确的控制复杂产品对象的创建过程,同时它隔离了复杂产品对象的创建和使用,使得相同的创建过程能够创建不同的产品。用一个指挥官去指定需要创建哪个对象,指定了创建顺序。把具体的创建过程隐藏了起来。如果需要创建不同种类的对象只需要传入不同的实现类就行了。这就是建造者模式。
但是如果某个产品的内部结构过于复杂,将会导致整个系统变得非常庞大,不利于控制,同时若干个产品之间存在较大的差异,则不适用建造者模式,毕竟这个世界上存在相同点大的两个产品不多,所以它的使用范围有限。
实现
我们假设一个快餐店的商业案例,其中,一个典型的套餐可以是一个汉堡(Burger)和一杯冷饮(Cold drink)。汉堡(Burger)可以是素食汉堡(Veg Burger)或鸡肉汉堡(Chicken Burger),它们是包在纸盒中。冷饮(Cold drink)可以是可口可乐(coke)或百事可乐(pepsi),它们是装在瓶子中。
我们将创建一个表示食物条目(比如汉堡和冷饮)的 Item 接口和实现 Item 接口的实体类,以及一个表示食物包装的 Packing 接口和实现 Packing 接口的实体类,汉堡是包在纸盒中,冷饮是装在瓶子中。
然后我们创建一个 Meal 类,带有 Item 的 ArrayList 和一个通过结合 Item 来创建不同类型的 Meal 对象的 MealBuilder。BuilderPatternDemo,我们的演示类使用 MealBuilder 来创建一个 Meal。
1、创建一个表示食物条目和食物包装的接口
食物接口 Item.java
1 2 3 4 5
| public interface Item { public String name(); public Packing packing(); public float price(); }
|
包装接口 Packing.java
1 2 3
| public interface Packing { public String pack(); }
|
2、创建实现 Packing 接口的实体类
纸盒 Wrapper.java
1 2 3 4 5 6 7
| public class Wrapper implements Packing { @Override public String pack() { return "Wrapper"; } }
|
瓶子 Bottle.java
1 2 3 4 5 6 7
| public class Bottle implements Packing { @Override public String pack() { return "Bottle"; } }
|
3、创建实现 Item 接口的抽象类,该类提供了默认的功能
汉堡抽象类 Burger.java
1 2 3 4 5 6 7 8 9 10
| public abstract class Burger implements Item { @Override public Packing packing() { return new Wrapper(); } @Override public abstract float price(); }
|
冷饮抽象类 ColdDrink.java
1 2 3 4 5 6 7 8 9 10
| public abstract class ColdDrink implements Item { @Override public Packing packing() { return new Bottle(); } @Override public abstract float price(); }
|
4、创建扩展了 Burger 和 ColdDrink 的实体类。
素食汉堡 VegBurger.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class VegBurger extends Burger { @Override public float price() { return 25.0f; } @Override public String name() { return "Veg Burger"; } }
|
鸡肉汉堡 ChickenBurger.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class ChickenBurger extends Burger { @Override public float price() { return 50.5f; } @Override public String name() { return "Chicken Burger"; } }
|
可乐 Coke.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class Coke extends ColdDrink { @Override public float price() { return 30.0f; } @Override public String name() { return "Coke"; } }
|
雪碧 Pepsi.java
1 2 3 4 5 6 7 8 9 10 11 12
| public class Pepsi extends ColdDrink { @Override public float price() { return 35.0f; } @Override public String name() { return "Pepsi"; } }
|
5、创建一个 Meal 类,带有上面定义的 Item 对象。
套餐 Meal.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| import java.util.ArrayList; import java.util.List; public class Meal { private List<Item> items = new ArrayList<Item>(); public void addItem(Item item){ items.add(item); } public float getCost(){ float cost = 0.0f; for (Item item : items) { cost += item.price(); } return cost; } public void showItems(){ for (Item item : items) { System.out.print("Item : "+item.name()); System.out.print(", Packing : "+item.packing().pack()); System.out.println(", Price : "+item.price()); } } }
|
6、创建一个 MealBuilder 类,实际的 builder 类负责创建 Meal 对象。
各种套餐组合 MealBuilder.java(指挥官,核心类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class MealBuilder { public Meal prepareVegMeal (){ Meal meal = new Meal(); meal.addItem(new VegBurger()); meal.addItem(new Coke()); return meal; } public Meal prepareNonVegMeal (){ Meal meal = new Meal(); meal.addItem(new ChickenBurger()); meal.addItem(new Pepsi()); return meal; } }
|
7、BuiderPatternDemo 使用 MealBuider 来演示建造者模式(Builder Pattern)。
顾客点套餐 BuilderPatternDemo.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class BuilderPatternDemo { public static void main(String[] args) { MealBuilder mealBuilder = new MealBuilder(); Meal vegMeal = mealBuilder.prepareVegMeal(); System.out.println("Veg Meal"); vegMeal.showItems(); System.out.println("Total Cost: " +vegMeal.getCost()); Meal nonVegMeal = mealBuilder.prepareNonVegMeal(); System.out.println("\n\nNon-Veg Meal"); nonVegMeal.showItems(); System.out.println("Total Cost: " +nonVegMeal.getCost()); } }
|
执行程序,输出结果:
1 2 3 4 5 6 7 8 9 10
| Veg Meal Item : Veg Burger, Packing : Wrapper, Price : 25.0 Item : Coke, Packing : Bottle, Price : 30.0 Total Cost: 55.0
Non-Veg Meal Item : Chicken Burger, Packing : Wrapper, Price : 50.5 Item : Pepsi, Packing : Bottle, Price : 35.0 Total Cost: 85.5
|
原型模式
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。例如,一个对象需要在一个高代价的数据库操作之后被创建。我们可以缓存该对象,在下一个请求时返回它的克隆,在需要的时候更新数据库,以此来减少数据库调用。
介绍
意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
主要解决:在运行期建立和删除原型。
优点: 1、性能提高。 2、逃避构造函数的约束。
缺点: 1、配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。 2、必须实现 Cloneable 接口。
使用场景: 1、资源优化场景。 2、类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。 3、性能和安全要求的场景。 4、通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式。 5、一个对象多个修改者的场景。 6、一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。 7、在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。原型模式已经与 Java 融为浑然一体,大家可以随手拿来使用。
什么是浅复制,深复制?
熟悉java的朋友都知道,java自带一个克隆方法(clone)。这是一个native的方法,这个方法不做任何改变的话默认是浅复制的。
被复制对象的基本数据类型的值跟原来相同,引用对象类型的还是指向原对象的引用对象,也就是指向地址一样。
被复制对象的基本数据类型的值跟原来相同,引用对象类型的是指向新对象的,相当于创建了一个新的被引用对象,跟被克隆的原对象不是同一个引用对象。
默认浅复制
数据类 Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public class Person implements Cloneable {
private int age; private String name;
private Address address;
public Person(int age, String name, Address address) { this.age = age; this.name = name; this.address = address; } public Address getAddress() { return address; }
@Override protected Person clone() throws CloneNotSupportedException { return (Person)super.clone(); } }
|
数据类 Address
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Address {
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Address(String name) { this.name=name; }
}
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("北京"); Person p = new Person(19, "李四", address);
System.out.println("浅复制测试");
Person p1 = p.clone();
System.out.println(p == p1);
p1.getAddress().setName("深圳"); System.out.println(p);
System.out.println(p1); }
|
输出结果
1 2 3 4 5
| false
Person{age=19, name='李四', address=Address{name='深圳'}}
Person{age=19, name='李四', address=Address{name='深圳'}}
|
结论:java默认的clone方法是浅复制,复制后的对象address跟原来的对象指向是一样的。这时大家心里可能会有疑问,如果要使用java默认的clone方法实现深复制。实现深复制的话这边需要改一下原来方法的逻辑,代码如下
使用原生的clone方法实现深复制
数据类 Person 重写clone方法的逻辑
1 2 3 4 5 6 7
| @Override protected Person clone() throws CloneNotSupportedException { Person clone = (Person) super.clone(); Object o = clone.getAddress().clone(); clone.setAddress((Address) o); return clone; }
|
数据类 Address 需要实现 Cloneable 接口,并且重写Clone()方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class Address implements Cloneable{ private String name;
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public Address(String name) { this.name = name; }
@Override public String toString() { return "Address{" + "name='" + name + '\'' + '}'; }
@Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } }
|
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Test {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("深圳"); Person p = new Person(19, "李四", address);
System.out.println("深复制测试"); Person p2 = p.clone();
System.out.println(p == p2);
p2.getAddress().setName("北京"); System.out.println(p); System.out.println(p2); } }
|
根据我们测试用例在来跑一次
输出结果
1 2 3
| false Person{age=19, name='李四', address=Address{name='深圳'}} Person{age=19, name='李四', address=Address{name='北京'}}
|
深复制总结:
① 如果有一个非原生成员,如自定义对象的成员,那么就需要深复制: 该成员实现Cloneable接口并覆盖clone()方法,不要忘记提升为protected可继承。 同时,修改被复制类的clone()方法,增加成员的克隆逻辑。
② 如果被复制对象不是直接继承Object,中间还有其它继承层次,每一层super类都需要实现Cloneable接口并覆盖clone()方法。与对象成员不同,继承关系中的clone不需要被复制类的clone()做修改。
一句话来说,如果实现完整的深拷贝,需要被复制对象的继承链、引用链上的每一个对象都实现克隆机制。
前面的实例还可以接受,如果有N个对象成员,有M层继承关系,就会很麻烦。
使用序列化的方式实现深复制
这个方式可以理解为,把对象转化成二进制流的形式,然后再把流序列化成对象。 具体实现方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
public Person deepCopy() {
ByteArrayOutputStream bos = null; ObjectOutputStream oos = null; ByteArrayInputStream bis = null; ObjectInputStream ois = null; try { bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); oos.writeObject(this); bis = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bis); return (Person) ois.readObject(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { bos.close(); oos.close(); bis.close(); ois.close(); } return null; }
|
测试
1 2 3 4 5 6 7 8 9
| System.out.println("深复制测试二进制流"); Person p3 = p.deepCopy();
System.out.println(p == p3);
p3.getAddress().setName("上海"); System.out.println(p); System.out.println(p3);
|
输出结果
1 2 3
| false Person{age=19, name='李四', address=Address{name='深圳'}} Person{age=19, name='李四', address=Address{name='上海'}}
|
序列化总结: 这种方式被复制对象的继承链、引用链上的每一个对象都实现java.io.Serializable接口。这个比较简单,不需要实现任何方法,serialVersionID的要求不强制,对深拷贝来说没毛病。这种是最常用,而且比较简单的方法。
总结
所谓原型模式,就是通过复制对象来创建对象。与通过使用构造函数来比,就是通过复制的对象带有原来对象的一些状态。尤其是在一些场景对象只存在细微差别。这时就可以使用原型模式去创建对象。