1 设计模式引入
🅰️ 什么是设计模式?
👩 答:我们可以把设计模式看成一局棋盘,英雄联盟云顶之弈大家玩过吧?对不同的英雄组合,我们需要不同的羁绊、不同的装备去针对性对付,才可以取得游戏胜利。而设计模式,就相当于一个固定的模板,当你遇到相同的状况时,可以直接免去思考直接使用。即,设计模式作为静态方法和属性的经典使用,是在大量实践中总结和理论之后优选的代码结构、编程风格、以及解决问题的思考方式。
2 单例模式
2.1 什么是单例模式
✈️ 所谓类的单例设计模式,就是 采用一定的方法保证在整个软件系统中,对某一个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
😎 这么说,小伙伴可能听不太明白,举个生活中的例子:我们每个人只能有一个女朋友,即只能有一个对象, 但是一般创建对象的方法无法保证我们只能 new 一个对象,比如下面的代码:
那么我们如何实现单例模式呢?目前,单例模式有两种实现方式:(1)饿汉式;(2)懒汉式
2.2 饿汉式单例模式
🍑 饿汉式单例模式实现步骤如下:
先将构造器私有化;
解释: 避免用户直接在外部 new 新对象。
在类的内部 属性部分直接创建一个对象;
解释: 我们已经将构造器私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象。
向外 提供一个公共的静态方法,用于外部引用该对象。
解释: 创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。
🍉 细节处理: 在类的外部,我们无法使用 new 新建对象,也就是说,我们只能通过 类名.xxx 的方式来返回创建的对象,因此,获取对象的对外公共方法需要使用 static 关键字修饰。而该方法中,需要返回类中创建的对象,静态方法只能使用静态变量,因此,对象属性也应当设置为 static。
🍌 实现代码:
public class SingleTon { public static void main(String[] args) { // 创建对象 GirlFriend gf1 = GirlFriend.getInstance(); GirlFriend gf2 = GirlFriend.getInstance(); // 判断两对象是否为同一对象 System.out.println(gf1 == gf2); // true } } class GirlFriend{ private String name; // 在属性中直接创建对象, 为了静态方法能够返回该对象,因此需要设置为static private static GirlFriend gf = new GirlFriend("毛毛虫"); // 构造方法私有化 private GirlFriend(String name){ this.name = name; } // 对外提供获取该对象的方法 public static GirlFriend getInstance(){ return gf; } }
实现结果:
true
你可能会问,gf1 和 gf2 不是两个对象吗?那为啥是 true?这点可以回顾以下这篇博文(文章内的对象在内存中的存在形式部分):
❤️【JavaSE】深入理解类与对象 || 方法调用机制与方法的传参机制浅析
简单的解释一下,gf1 与 gf2 实际引用的是同一个堆空间,而这个堆空间在 new 的时候就确定了!具体见下图:(就是这么宠粉!🐕)
2.3 懒汉式单例模式
🍑 懒汉式单例模式实现步骤如下:
先将构造器私有化;
解释: 避免用户直接在外部 new 新对象。
在类的内部 声明一个对象引用,但是不创建,即先不 new;
向外 提供一个公共的静态方法,用于创建对象并返回对象引用。
解释: 需要在该方法中创建对象,并返回给外部。
🍉 细节处理: 在类的外部,我们无法使用 new 新建对象,也就是说,我们只能通过 类名.xxx 的方式来返回创建的对象,因此,获取对象的对外公共方法需要使用 static 关键字修饰。而该方法中,需要返回类中创建的对象,静态方法只能使用静态变量,因此,对象属性也应当设置为 static。(这点与饿汉式一致)
与饿汉式不同的是,我们需要在提供的公共方法中创建对象,需要在方法中确保创建的是同一对象,即当对象为 null 时创建,不为 null 时,直接返回上次创建的对象。
🍌 实现代码:
public class SingleTon { public static void main(String[] args) { // 创建对象 GirlFriend gf1 = GirlFriend.getInstance(); GirlFriend gf2 = GirlFriend.getInstance(); // 判断两对象是否为同一对象 System.out.println(gf1 == gf2); // true } } class GirlFriend{ private String name; // 在属性声明对象, 但是不创建 private static GirlFriend gf; // 构造方法私有化, 防止直接new private GirlFriend(String name){ this.name = name; } // 对外提供获取该对象的方法,如果对象还未创建,则先创建后返回 public static GirlFriend getInstance(){ if(gf == null){ gf = new GirlFriend("毛毛虫"); } return gf; } }
🍎 实现结果:
true
3 饿汉式与懒汉式的区别
Tips:文末有助记口令哦~~~
⭐️ Star 1: 两者最主要的区别是 在于创建对象的时机不同:饿汉式是在类加载的时候就创建了对象实例,而懒汉式是在使用时才创建(你不用我才懒得创建!)。
❤️类加载相关知识: 【JavaSE】学了这么久Java,你真的会用代码块吗?
(文章代码块使用细节中 star3 类何时被加载?)
Star 2: 饿汉式不存在线程安全问题,而 懒汉式存在线程安全的问题。
解释: 请看如下代码块,方法 getInstance() 用于懒汉式单例模式,在使用时创建并返回对象,如果存在,则直接返回创建的对象。但是如果遇到短时间内多进程问题。比如:两个进程同时进入该方法,需要返回给 g1 ,g2 对象实例。当 g1 进入该方法后,进行判断,不为 null,因此创建一个对象,而在创建对象的过程中,g2 刚好也进入了该方法,也被判断成了不为 null,此时,g1, g2就指向不同的对象了,所以它是线程不安全的。
// 对外提供获取该对象的方法,如果对象还未创建,则先创建后返回 public static GirlFriend getInstance(){ if(gf == null){ gf = new GirlFriend("毛毛虫"); } return gf; }
⭐️ Star 3: 饿汉式存在资源浪费的可能。 比如在程序中没有使用对象实例,那么饿汉式创建的对象就浪费了。请看如下代码:
public class SingleTon { public static void main(String[] args) { System.out.println(GirlFriend.age); } } class GirlFriend{ static int age = 20; private String name; // 在属性中直接创建对象, 为了静态方法能够返回该对象,因此需要设置为static private static GirlFriend gf = new GirlFriend("毛毛虫"); // 构造方法私有化 private GirlFriend(String name){ System.out.println("构造方法被调用,对象被创建"); this.name = name; } // 对外提供获取该对象的方法 public static GirlFriend getInstance(){ return gf; } }
🍎 在上述代码中,我们使用了 GirldFriend 类的静态成员 age,但是我们并不需要创建一个对象。但是在该测试样例中,对象创建的构造方法却被调用了! 实现结果如下:
构造方法被调用,对象被创建
20
⭐️ Star 4: 在 JavaSE 的标准类中,java.lang.Runtime就是经典的单例模式。感兴趣的小伙伴们可以去查阅相应的源码,这里就暂时不赘述啦!!