并发编程(二十四):单例模式(三):构造方法私有:单例模式的 “第一道防线”

一、构造方法私有 = 杜绝外部 new 对象

Java 中创建对象的核心方式:new XXX() → 本质是调用 XXX 的构造方法

如果把构造方法设为 private:外部类(比如 Test 类)无法访问这个构造方法,自然就写不了 new ServletSingleton()

从根源上杜绝了 "通过 new 随便创建多个对象" 的可能,这是单例模式的基础前提。

公有构造 vs 私有构造

构造方法修饰符 外部能否 new 对象 能否保证单例
public(公有) 可以(new ServletSingleton()) 不能(想创建多少就创建多少)
private(私有) 不可以(编译报错) 能(外部无法通过 new 创建)

反例(公有构造,无法单例):

java 复制代码
public class BadSingleton {
    // 公有构造:外部可以随便 new
    public BadSingleton() {}

    public static void main(String[] args) {
        BadSingleton s1 = new BadSingleton();
        BadSingleton s2 = new BadSingleton();
        System.out.println(s1 == s2); // false,两个不同对象
    }
}

正例(私有构造,杜绝外部 new):

java 复制代码
public class GoodSingleton {
    // 私有构造:外部无法访问
    private GoodSingleton() {}

    public static void main(String[] args) {
        // 编译报错:GoodSingleton() has private access in GoodSingleton
        GoodSingleton s1 = new GoodSingleton(); 
    }
}

二、"为什么单例模式要把构造方法私有?"

单例模式的目标是「一个类只能创建一个对象」,而 Java 中创建对象的核心方式是调用构造方法

如果构造方法是公有的(public),外部代码可以通过 new 类名() 无限创建对象,完全违背单例的核心要求

将构造方法设为私有(private),可以禁止外部类访问构造方法,从根源上杜绝 "通过 new 创建多个对象" 的可能,这是实现单例的基础前提

三、私有构造也有 "漏洞"

虽然私有构造杜绝了外部 new,但通过反射仍能调用私有构造创建对象,所以生产级单例需要加防护:

java 复制代码
private GoodSingleton() {
    // 反射防护:如果已有实例,直接抛异常
    if (INSTANCE != null) {
        throw new RuntimeException("禁止通过反射创建多个单例实例!");
    }
}

构造方法私有是单例模式的基础,可以杜绝外部通过 new 创建多个对象


四、"构造方法私有后,为什么必须写静态方法?"

构造方法私有 → 外部无法 new 对象 → 无法通过对象调用非静态方法 → 必须提供静态方法作为"入口" → 静态方法内部创建/返回单例对象 → 间接调用非静态方法逐段拆解

1. 构造私有 → 外部无法 new 对象

私有构造只对类内部开放,外部代码写 new 类名() 会编译报错

没有对象,就无法调用非静态方法(非静态方法必须依托对象:对象.方法())

2. 为什么必须写 public static 方法?

方法类型 是否依托对象 外部能否调用 (构造私有) 作用
非静态方法 必须依托对象 不能(外部拿不到对象) 单例的核心业务方法(如 doGet/doPost)
静态方法 不依托对象(类级) 能(类名.方法()) 作为外部唯一入口,创建 / 返回单例对象

静态方法是外部访问单例类的唯一通道------ 它不依托对象,能直接通过类名调用

且在类内部,静态方法可以访问私有构造,创建唯一的单例对象

3. 静态方法的 "特殊权限":类内可调用私有构造

java 复制代码
public class ServletSingleton {
    private volatile static ServletSingleton INSTANCE;

    // 1. 私有构造:外部不能new,类内部可以!
    private ServletSingleton() {}

    // 2. 静态方法:外部唯一入口(类名.getInstance())
    public static ServletSingleton getInstance() {
        if (INSTANCE == null) {
            synchronized (ServletSingleton.class) {
                if (INSTANCE == null) {
                    // 静态方法内部:可以调用私有构造(类内权限)
                    INSTANCE = new ServletSingleton(); 
                }
            }
        }
        return INSTANCE; // 返回单例对象
    }

    // 3. 非静态业务方法(外部只能通过单例对象调用)
    public void doGet() {
        System.out.println("处理GET请求");
    }
}

// 外部调用示例(唯一正确方式)
public class Test {
    public static void main(String[] args) {
        // 第一步:通过静态方法拿到单例对象(构造私有,只能这么拿)
        ServletSingleton singleton = ServletSingleton.getInstance();
        // 第二步:通过对象调用非静态方法
        singleton.doGet();
    }
}

五、为什么 Servlet/Controller 不建议全设为静态?

场景 单例类的静态方法(getInstance) Servlet/Controller 的静态方法
静态方法数量 只有 1 个(getInstance) 大量(doGet/doPost/ 各种业务方法)
内存占用 仅 1 个静态方法占内存,可忽略 所有静态方法类加载时就占内存,浪费严重
灵活性 仅入口是静态,核心方法是非静态(可重写 / 继承) 全静态方法无法重写,扩展性差
设计目的 只为提供 "拿对象的入口" 提供业务处理能力,需要灵活扩展

单例类的静态方法只有一个(getInstance),占内存极少,是 "必要之恶"

但 Servlet/Controller 有大量业务方法(doGet/doPost/ 查询 / 新增等),如果全设为静态:

  • 类加载时所有静态方法都占内存,浪费严重
  • 静态方法不能重写,后续扩展(比如子类重写 doGet)完全做不了

六、要点总结

构造方法私有 → 外部无法 new 对象 → 无法通过对象调用非静态方法

静态方法不依托对象,可通过 类名.方法() 直接调用,是外部访问单例类的唯一入口

静态方法在类内部可访问私有构造,能创建并返回唯一的单例对象,间接让外部调用到非静态业务方法

而 Servlet/Controller 不建议全设为静态,因为其业务方法多,全静态会导致类加载时大量占用内存,且静态方法无法重写,扩展性差

相关推荐
myloveasuka2 小时前
[Java]包装类
java·开发语言
myloveasuka2 小时前
时间相关类
java·开发语言
青火coding2 小时前
Embedding是什么?从文本转向量
java·机器学习·ai·embedding
6+h2 小时前
【java IO】转换流 + 对象流 + 序列化详解
java·开发语言
一棵树73512 小时前
Springboot项目常用工具对比总结
java·spring boot·后端
IT痴者2 小时前
Kotlin 开发注意事项(Android Java 开发者转型指南)
android·java·kotlin
wuqingshun3141592 小时前
产生死锁的四个必要条件
java·jvm
青槿吖2 小时前
第二篇:Spring MVC进阶:注解、返回值与参数接收的花式玩法
java·开发语言·后端·mysql·spring·mvc·mybatis
共享家95272 小时前
Java入门(抽象类 与 接口)
java·开发语言