一、构造方法私有 = 杜绝外部 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 不建议全设为静态,因为其业务方法多,全静态会导致类加载时大量占用内存,且静态方法无法重写,扩展性差