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

简介: 文章目录1 设计模式引入2 单例模式2.1 什么是单例模式2.2 饿汉式单例模式2.3 懒汉式单例模式3 饿汉式与懒汉式的区别写在最后

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就是经典的单例模式。感兴趣的小伙伴们可以去查阅相应的源码,这里就暂时不赘述啦!!


相关文章
|
1月前
|
设计模式 安全 Java
Java编程中的单例模式:理解与实践
【10月更文挑战第31天】在Java的世界里,单例模式是一种优雅的解决方案,它确保一个类只有一个实例,并提供一个全局访问点。本文将深入探讨单例模式的实现方式、使用场景及其优缺点,同时提供代码示例以加深理解。无论你是Java新手还是有经验的开发者,掌握单例模式都将是你技能库中的宝贵财富。
39 2
|
2月前
|
设计模式 安全 Java
Java编程中的单例模式深入剖析
【10月更文挑战第21天】在Java的世界里,单例模式是设计模式中一个常见而又强大的存在。它确保了一个类只有一个实例,并提供一个全局访问点。本文将深入探讨如何正确实现单例模式,包括常见的实现方式、优缺点分析以及最佳实践,同时也会通过实际代码示例来加深理解。无论你是Java新手还是资深开发者,这篇文章都将为你提供宝贵的见解和技巧。
99 65
|
1月前
|
设计模式 安全 Java
Java编程中的单例模式深入解析
【10月更文挑战第31天】在编程世界中,设计模式就像是建筑中的蓝图,它们定义了解决常见问题的最佳实践。本文将通过浅显易懂的语言带你深入了解Java中广泛应用的单例模式,并展示如何实现它。
|
1月前
|
设计模式 SQL 安全
Java编程中的单例模式深入解析
【10月更文挑战第24天】在软件工程中,单例模式是设计模式的一种,它确保一个类只有一个实例,并提供一个全局访问点。本文将探讨如何在Java中使用单例模式,并分析其优缺点以及适用场景。
17 0
|
2月前
|
SQL 设计模式 Java
[Java]单例模式
本文介绍了单例模式的概念及其实现方式,包括饿汉式和懒汉式两种形式,并详细探讨了懒汉式中可能出现的线程安全问题及其解决方案,如锁方法、锁代码块和双重检查锁(DCL)。文章通过示例代码帮助读者更好地理解和应用单例模式。
34 0
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
39 1
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
28 3