简介

在 Java 中,创建一个类实例最简单的方法就是使用 new 关键字,通过构造函数来实现对象的创建。

People people = new People();

实际开发中,还有另一种创建类实例的方法,通过静态工厂方法在类中添加一个公有静态方法来返回一个实例

class People {
    String name;
    int age;
    int weight;

    public static People getPeople(){
        return new People();
    }
}

然后通过getPeople这个静态方法来获取一个实例:

People people = People.getPeople();

静态工厂方法的特点:

优势

1.静态工厂方法有名称

Java构造函数必须与类名相同,重载只能通过参数类型/数量区分。当多个构造器参数类型相同时,开发者可通过调整参数顺序实现重载,单参构造器无法实现。而用静态工厂方法代替构造器,用不同的方法名实现不同初始化逻辑,从而创建不同属性的对象,可以解决此问题。

举个例子 🌰
假设有一个 Person 类,需要根据相同参数类型(比如 String)初始化不同属性(如 name 或 id),但构造器无法区分这两种情况:

public class Person {
    private String name;
    private String id;

    // ❌ 无法通过参数类型区分两个构造器(都是String)
    public Person(String name) { this.name = name; }
    public Person(String id) { this.id = id; } // 编译报错:重复构造器
}

静态工厂方法的解决方式:

public class Person {
  private String name;
  private String id;

  // 私有构造器,限制外部直接new
  private Person() {}

  // ✅ 静态工厂方法:通过方法名区分逻辑
  public static Person createWithName(String name) {
      Person p = new Person();
      p.name = name;
      return p;
  }

  public static Person createWithId(String id) {
      Person p = new Person();
      p.id = id;
      return p;
  }
}

使用示例:

Person person1 = Person.createWithName("张三"); // 初始化name属性
Person person2 = Person.createWithId("ID-123"); // 初始化id属性

2.静态工厂方法不用在每次调用的时候都创建一个新的对象

举个例子 🌰
假设我们有一个 Color 类,表示 RGB 颜色。通过静态工厂方法 fromRGB 创建颜色对象时,复用已存在的相同颜色对象,而不是每次都新建:

public class Color {
  private final int r, g, b;
  private static final Map<String, Color> CACHE = new HashMap<>(); // 缓存池

  // 私有构造器,禁止外部直接 new
  private Color(int r, int g, int b) {
      this.r = r;
      this.g = g;
      this.b = b;
  }

  // ✅ 静态工厂方法:复用已有对象
  public static Color fromRGB(int r, int g, int b) {
      String key = r + "," + g + "," + b;
      // 检查缓存中是否存在相同颜色
      Color cachedColor = CACHE.get(key);
      if (cachedColor == null) {
          cachedColor = new Color(r, g, b);
          CACHE.put(key, cachedColor); // 存入缓存
      }
      return cachedColor;
  }
}

使用示例:

Color red1 = Color.fromRGB(255, 0, 0);
Color red2 = Color.fromRGB(255, 0, 0);

System.out.println(red1 == red2); // 输出 true ✅ 两个引用指向同一个对象

第一次调用 fromRGB(255,0,0):创建新对象,存入缓存。
第二次调用 fromRGB(255,0,0):直接返回缓存中的对象,不再新建。

对比构造器的行为:
如果直接通过构造器 new Color(255,0,0),每次都会创建新对象:

Color red3 = new Color(255, 0, 0); // ❌ 假设构造器是公开的
Color red4 = new Color(255, 0, 0);
System.out.println(red3 == red4); // 输出 false ❌ 两个不同对象

实际应用场景:

不可变类的复用
例如 Java 的 Integer.valueOf(int) 会缓存 -128~127 的 Integer 对象:

Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true ✅

单例模式
通过静态工厂方法返回唯一的实例:

public class Singleton {
    private static final Singleton INSTANCE = new Singleton();
    
    private Singleton() {}
    
    public static Singleton getInstance() {
        return INSTANCE; // 始终返回同一个对象
    }
}

避免重复创建相同对象,对象创建和垃圾回收成本较高时,复用对象提高性能。

3.静态工厂方法可以返回原返回类型的任何子类型对象

静态工厂方法的返回类型可以声明为父类或接口,而实际返回其子类对象。这种灵活性可以隐藏具体实现细节,使代码更简洁且易于扩展
举个例子 🌰
假设有一个 Payment 接口,它有两个实现类:CreditCardPayment 和 PayPalPayment。
我们可以用静态工厂方法决定返回哪种支付方式,而客户端代码只需依赖 Payment 接口。

// 定义支付接口(而非抽象类)
public interface Payment {
    void pay(int amount); // 抽象方法

    // ✅ 接口的静态方法作为工厂方法(Java 8+ 支持)
    static Payment create(String type) {
        switch (type.toLowerCase()) {
            case "creditcard":
                return new CreditCardPayment(); // 返回接口的实现类
            case "alipay":
                return new AlipayPayment();     // 返回另一个实现类
            default:
                throw new IllegalArgumentException("未知支付类型");
        }
    }
}

// 实现类 1:信用卡支付
class CreditCardPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println("信用卡支付:" + amount + "元");
    }
}

// 实现类 2:移动支付
class AlipayPayment implements Payment {
    @Override
    public void pay(int amount) {
        System.out.println("移动支付:" + amount + "元");
    }
}

使用示例:

// 通过接口的静态工厂方法获取实现类对象
Payment payment1 = Payment.create("creditcard");
Payment payment2 = Payment.create("alipay");

payment1.pay(100); // 输出:信用卡支付:100元
payment2.pay(200); // 输出:移动支付:200元

4.静态工厂方法所返回的对象可以随着每次调用而发生变化,这取决于参数值

可以根据不同的参数值返回不同类型的对象,只要这些对象都是声明的返回类型的子类
举个例子 🌰

class People {
    String name;
    int age;
    public static People createPerson(int age) {
        if(age >= 18){
            return new Adult();
        } else {
            return new Child();
        }
    }
}

class Child extends People{}
class Adult extends People{}

使用示例:

People p1 = People.create(20);  // 实际是 Adult 对象
People p2 = People.create(10);  // 实际是 Child 对象

客户端永远不知道也不关心他们从这个静态工厂方法中得到的对象的类,它们只关心这个对象的类是People的某个子类。灵活扩展,新增子类时,只需修改工厂方法,客户端代码无需改动。条件化创建对象,根据参数值动态选择对象类型(例如根据配置决定使用哪种数据库连接)。

5.静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在

静态工厂方法返回的对象所属的类,在编写工厂类时可以不存在,这是通过 延迟加载 或 动态类加载 实现的。这种设计常见于解耦框架(如 JDBC、日志库)或插件化系统中。
举个例子 🌰
使用场景:
假设工厂类 MessageServiceFactory 已编译发布,但具体实现类在工厂类编写时尚未存在

// 日志接口(工厂类编写时已知)
public interface Logger {
    void info(String message);
    void error(String message);
}

// 包含静态工厂方法的类(编写时不知道具体实现)
public class LogFactory {
    // ✅ 静态工厂方法:返回具体实现类(编译时无需存在)
    public static Logger getLog(Class<?> clazz) {
        try {
            // 动态查找实现类(如 "com.example.LogbackLogger")
            String implClassName = readConfig(); // 从配置读取实现类名
            Class<?> loggerClass = Class.forName(implClassName);
            return (Logger) loggerClass.getConstructor().newInstance();
        } catch (Exception e) {
            return new DefaultLogger(); // 默认实现(如空日志)
        }
    }

    private static String readConfig() {
        // 模拟读取配置文件(如返回 "com.example.LogbackLogger")
        return System.getProperty("logger.implementation");
    }
}

后续开发者可以按需添加实现类,例如:

// 实现类1:Logback 的 Logger(后续开发)
public class LogbackLogger implements Logger {
    @Override
    public void info(String message) {
        System.out.println("[Logback INFO] " + message);
    }

    @Override
    public void error(String message) {
        System.out.println("[Logback ERROR] " + message);
    }
}

// 实现类2:Log4j2 的 Logger(后续开发)
public class Log4j2Logger implements Logger {
    @Override
    public void info(String message) {
        System.out.println("[Log4j2 INFO] " + message);
    }

    @Override
    public void error(String message) {
        System.out.println("[Log4j2 ERROR] " + message);
    }
}

使用示例:

// 配置实现类名 
// 在系统属性中设置具体实现类:
System.setProperty("logger.implementation", "com.example.LogbackLogger");
// 客户端调用工厂方法
public class MyApp {
    private static final Logger log = LogFactory.getLog(MyApp.class);

    public static void main(String[] args) {
        log.info("程序启动"); // 输出: [Logback INFO] 程序启动
        log.error("发生错误"); // 输出: [Logback ERROR] 发生错误
    }
}
// 切换日志实现 
// 无需修改代码,只需更改配置中的类名:
System.setProperty("logger.implementation", "com.example.Log4j2Logger");
输出变为:
[Log4j2 INFO] 程序启动
[Log4j2 ERROR] 发生错误

通过 Class.forName(className) 在运行时加载类,无需在编译时知道具体实现。

缺点

1.类如果不含公有的或者受保护的构造器,就不能被子类化

父类没有 public 或 protected 构造器,就无法被继承

2.程序员很难发现静态工厂方法

因为静态工厂方法是自定义的,它们没有像构造器那样被明确规定如何实例化,因此程序员往往很难查明如何通过静态工厂方法实例化一个类,因此我们便需要遵守一些静态工厂方法的惯用名称。
以下列出一小部分:

①from——类型转换方法,只有单个参数。返回该类型的一个相对应实例,例如:

Date d = Date.from(instant);

②of——聚合方法,有多个参数,返回该类型的一个实例,把它们合并起来,例如:

Set<Rank> facecards = Enumset.of(JACK, QUEEN, KING);

③valueOf——比from和of更烦琐的一种替代方法,例如:

BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

④ instance或者getInstance——返回的实例是通过方法的(如有)参数来描述的,但是不能说与参数具有同样的值,例如:

Stackwalker luke = Stackwalker.getInstance(options);

⑤create或者 newInstance——像 instance或者 getInstance一样,但 create或者 newInstance能够确保每次调用都返回一个新的实例,例如:

Object newArray = Array.newInstance(classObject, arrayLen);

⑥getType——像 getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型,例如:

FileStore fs = Files.getFileStore(path);

⑦newtype——像newInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型,例如:

BufferedReader br = Files.newBufferedReader(path);

⑧type——getType和 newType的简版,例如:

List<Complaint> litany = Collections.list(legacyLitany);

总结

总而言之,静态工厂方法和公有构造器都各有用处,我们需要理解它们各自的长处。
往往静态工厂方法会更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。

参考《Effective Java》

我爱吃炒煤
1 声望0 粉丝