【JavaSE】Java设计模式详解(一)单例模式(饿汉式与懒汉式实现)

本篇学习目标:

  • ⭐️ 了解什么是单例模式;
  • ⭐️ 掌握饿汉式单例模式的使用;
  • ⭐️ 掌握懒汉式单例模式的使用;
  • ⭐️ 了解饿汉式与懒汉式单例模式的区别与优缺点。

本文来自专栏:JavaSE系列专题知识及项目 欢迎点击支持订阅专栏 ❤️
在这里插入图片描述


1 设计模式引入

🅰️ 什么是设计模式?
👩 答:我们可以把设计模式看成一局棋盘,英雄联盟云顶之弈大家玩过吧?对不同的英雄组合,我们需要不同的羁绊、不同的装备去针对性对付,才可以取得游戏胜利。而设计模式,就相当于一个固定的模板,当你遇到相同的状况时,可以直接免去思考直接使用。即,设计模式作为静态方法和属性的经典使用,是在大量实践中总结和理论之后优选的代码结构、编程风格、以及解决问题的思考方式。


2 单例模式

2.1 什么是单例模式

✈️ 所谓类的单例设计模式,就是 采用一定的方法保证在整个软件系统中,对某一个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
😎 这么说,小伙伴可能听不太明白,举个生活中的例子:我们每个人只能有一个女朋友,即只能有一个对象, 但是一般创建对象的方法无法保证我们只能 new 一个对象,比如下面的代码:
示例
那么我们如何实现单例模式呢?目前,单例模式有两种实现方式:(1)饿汉式;(2)懒汉式

2.2 饿汉式单例模式

🍑 饿汉式单例模式实现步骤如下:

  1. 先将构造器私有化;
    解释: 避免用户直接在外部 new 新对象。
  2. 在类的内部 属性部分直接创建一个对象;
    解释: 我们已经将构造器私有化了,因此在外部没有办法 new 新对象,所以只能在类的内部创建对象。
  3. 向外 提供一个公共的静态方法,用于外部引用该对象。
    解释: 创建好了对象后,如果对外不提供公共方法,我们就没有办法使用这个被创建的对象。

🍉 细节处理: 在类的外部,我们无法使用 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

你可能会问,gf1gf2 不是两个对象吗?那为啥是 true?这点可以回顾以下这篇博文(文章内的对象在内存中的存在形式部分):
❤️【JavaSE】深入理解类与对象 || 方法调用机制与方法的传参机制浅析

简单的解释一下,gf1gf2 实际引用的是同一个堆空间,而这个堆空间在 new 的时候就确定了!具体见下图:(就是这么宠粉!🐕)
在这里插入图片描述

2.3 懒汉式单例模式

🍑 懒汉式单例模式实现步骤如下:

  1. 先将构造器私有化;
    解释: 避免用户直接在外部 new 新对象。
  2. 在类的内部 声明一个对象引用,但是不创建,即先不 new;
  3. 向外 提供一个公共的静态方法,用于创建对象并返回对象引用。
    解释: 需要在该方法中创建对象,并返回给外部。

🍉 细节处理: 在类的外部,我们无法使用 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() 用于懒汉式单例模式,在使用时创建并返回对象,如果存在,则直接返回创建的对象。但是如果遇到短时间内多进程问题。比如:两个进程同时进入该方法,需要返回给 g1g2 对象实例。当 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就是经典的单例模式。感兴趣的小伙伴们可以去查阅相应的源码,这里就暂时不赘述啦!!

📝 助记懒汉不关门,所以不安全;饿汉肚子空,吃饭废粮食!


写在最后

🌟以上便是本文的全部内容啦,后续内容将会持续免费更新,如果文章对你有所帮助,麻烦动动小手点个赞 + 关注,非常感谢 ❤️ ❤️ ❤️ !
如果有问题,欢迎私信或者评论区!
在这里插入图片描述

共勉:“你间歇性的努力和蒙混过日子,都是对之前努力的清零。”
在这里插入图片描述

  • 40
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 31
    评论
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

兴趣使然黄小黄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值