核心概念
当一个类的构造函数被声明为 private 时,意味着这个类只能在它自己的内部被实例化,任何其他类都无法通过 new 关键字来创建这个类的实例。

这听起来可能有些违反直觉,因为我们通常创建类就是为了在别处使用它。private 构造函数是一种非常重要的设计模式,主要用于实现单例模式和静态工具类。
主要用途
实现单例模式
这是 private 构造函数最经典和最常见的用途,单例模式确保一个类只有一个实例,并提供一个全局访问点来访问这个实例。
为什么需要 private 构造函数?
- 防止外部实例化:如果构造函数是
public的,任何人都可以通过new Singleton()创建新的实例,这样就破坏了“只有一个实例”的规则。 - 防止继承:
private构造函数也使得该类不能被继承,因为子类在实例化时需要调用父类的构造函数,而父类的构造函数是private的,子类无法访问,从而阻止了继承。
实现方式:

有两种主流的单例模式实现:饿汉式 和 懒汉式。
a. 饿汉式
这种方式在类加载时就创建好了实例,所以是“饿”的,一上来就准备好。
public class Singleton {
// 1. 在类内部创建一个唯一的、私有的静态实例
// 注意:这个实例在类加载时就初始化了
private static final Singleton INSTANCE = new Singleton();
// 2. 将构造函数私有化,防止外部通过 new 创建实例
private Singleton() {
// 可以做一些初始化操作
}
// 3. 提供一个公共的静态方法,让外部可以获取到这个唯一的实例
public static Singleton getInstance() {
return INSTANCE;
}
}
优点:
- 实现简单。
- 线程安全,因为 JVM 在类加载时就保证了实例的唯一性。
缺点:

- 如果这个实例从未被使用过,会造成内存的浪费。
b. 懒汉式
这种方式只有在第一次调用 getInstance() 方法时才创建实例,所以是“懒”的,按需创建。
线程不安全的实现(不推荐用于多线程环境):
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 多个线程可能同时在这里判断为 true
instance = new Singleton(); // 导致创建多个实例
}
return instance;
}
}
线程安全的实现(推荐使用):
可以使用 synchronized 关键字来保证线程安全。
public class Singleton {
private static Singleton instance;
private Singleton() {}
// 在方法上添加 synchronized 关键字,确保同一时间只有一个线程能进入此方法
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
更优化的线程安全实现(双重检查锁定 - DCL):
synchronized 会影响性能,我们可以使用双重检查锁定来优化。
public class Singleton {
// 使用 volatile 关键字,确保 instance 变量在多线程环境下的可见性和有序性
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(无锁,快速判断)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(在同步块内,确保只有一个线程能创建实例)
instance = new Singleton();
}
}
}
return instance;
}
}
创建静态工具类
当一个类只包含静态方法和静态字段时,我们不希望它被实例化。java.lang.Math 或 java.util.Collections。
为什么需要 private 构造函数?
- 防止程序员不小心创建该类的实例,
new Math(),由于所有方法都是静态的,创建实例没有任何意义,反而会浪费内存。
实现方式:
public final class MathUtils { // 加上 final 确保不能被继承
// 私有构造函数,防止外部实例化
private MathUtils() {
// 抛出异常,防止通过反射调用私有构造函数
throw new AssertionError("Cannot instantiate this utility class");
}
public static int add(int a, int b) {
return a + b;
}
public static double power(double base, int exponent) {
// ... 计算幂 ...
return Math.pow(base, exponent);
}
}
注意:在私有构造函数中抛出 AssertionError 是一个很好的实践,这可以防止有人通过反射(setAccessible(true))来绕过访问限制并创建实例,如果真的发生了,程序会直接报错,而不是静默地创建一个无用的实例。
如何绕过 private 构造函数?(以及如何防范)
了解如何绕过可以帮助我们更好地理解其原理和防范措施。
反射
可以通过反射机制调用 private 构造函数。
// 假设 Singleton 类是上面饿汉式的单例
Singleton singleton1 = Singleton.getInstance();
// 使用反射尝试创建第二个实例
try {
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true); // 解除私有访问限制
Singleton singleton2 = constructor.newInstance();
System.out.println(singleton1 == singleton2); // 输出 false,破坏了单例!
} (Exception e) {
e.printStackTrace();
}
如何防范?
可以在单例类的私有构造函数中加入一个“标志位”,在第一次创建实例后将其设为 true,反射再次创建时检查这个标志位,如果为 true 就抛出异常。
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
// 添加一个标志位
private static boolean isCreated = false;
private Singleton() {
if (isCreated) {
throw new IllegalStateException("Singleton instance already created. Use getInstance() method.");
}
isCreated = true;
}
// ... 其他代码 ...
}
序列化与反序列化
如果单例类实现了 Serializable 接口,通过序列化和反序列化可以创建出新的实例,从而破坏单例。
// 假设 Singleton 实现了 Serializable
Singleton instance1 = Singleton.getInstance();
// 将 instance1 序列化到文件
// ... (序列化代码)
// 从文件反序列化
Singleton instance2 = (Singleton) new ObjectInputStream(new FileInputStream("singleton.ser")).readObject();
System.out.println(instance1 == instance2); // 输出 false,破坏了单例!
如何防范?
可以实现 readResolve() 方法,在反序列化时,JVM 会检查这个方法,如果存在,它会调用这个方法并返回其结果,而不是创建新的实例。
import java.io.Serializable;
public class Singleton implements Serializable {
// ... 其他代码 ...
// 防止反序列化破坏单例
protected Object readResolve() {
return getInstance();
}
}
| 特性/用途 | 描述 |
|---|---|
| 核心作用 | 限制类的实例化,只能在类内部创建对象。 |
| 主要用途 | 实现单例模式:确保全局只有一个实例。 创建静态工具类:防止无意义的实例化。 |
| 如何实现 | 将构造函数声明为 private,并提供一个 public static 方法来获取预创建的实例(单例)或完全不提供实例化方法(工具类)。 |
| 如何绕过 | 反射:使用 setAccessible(true) 调用私有构造器。序列化/反序列化:可以创建出与原实例无关的新对象。 |
| 如何防范 | 反射:在构造函数中检查实例是否已存在,存在则抛出异常。 序列化:实现 readResolve() 方法,返回已存在的单例实例。 |
| 其他优点 | - 防止继承:private 构造函数隐式地阻止了类的继承。- API 清晰性:对于工具类,明确的私有构造函数向其他开发者传达了“这是一个工具类,请勿实例化”的意图。 |
private 构造函数是 Java 中一个强大而基础的语言特性,是实现特定设计模式(尤其是单例模式)和编写健壮的工具类的关键工具,理解它的工作原理以及如何绕过和防范它,对于编写高质量的 Java 代码至关重要。
