一、静态工厂方法与构造器不同的优势

1. 静态工厂方法有名称
import java.math.BigInteger;
import java.util.Random;

public class PrimeCreationDemo {
    public static void main(String[] args) {
        Random random = new Random();  // 创建随机数生成器
        int bitLength = 512;           // 定义素数的位长(如512位)

        // 方式1:使用构造器创建素数(不够清晰,需要额外参数和解释)
        // - 构造器 new BigInteger(int bitLength, int certainty, Random rnd) 需要指定"certainty"(确定性参数)
        // - certainty 值越高,生成素数的概率越大,但开发者需手动选择值(如100),增加理解负担
        // - 代码可读性差:参数含义不直观,且容易出错
        int certainty = 100;  // 手动指定确定性参数(常见值)
        BigInteger primeViaConstructor = new BigInteger(bitLength, certainty, random);

        // 方式2:使用静态工厂方法创建素数(更清晰、简洁)
        // - 方法 BigInteger.probablePrime(int bitLength, Random rnd) 封装了内部逻辑
        // - 无需关心额外参数(如 certainty),默认使用合理值,代码一目了然
        // - 命名直观:方法名"probablePrime"直接表达了目的
        BigInteger primeViaFactory = BigInteger.probablePrime(bitLength, random);

        // 输出结果以验证(可选)
        System.out.println("使用构造器创建的素数: " + primeViaConstructor);
        System.out.println("使用静态工厂创建的素数: " + primeViaFactory);
    }
}
 

ps:BigInteger类中certainty值主要用于素数的生成,但是随机大素数判断成本极高,使用的是一种算法,certainty用于2^(-certainty),2^(-certainty)表示不是素数的概率,因此certainty越大,是素数的概率越大。

2. 不必每次调用时都创建一个新对象
public class LessOccupy {
    public static void main(String[] args) {
        // 使用构造器创建对象 - 每次都会创建新实例
        Boolean b1 = new Boolean(true);
        Boolean b2 = new Boolean(true);
        System.out.println("构造器创建的对象是否相同: " + (b1 == b2));  // 输出 false

        // 使用静态工厂方法 - 复用现有实例
        Boolean b3 = Boolean.valueOf(true);
        Boolean b4 = Boolean.valueOf(true);
        System.out.println("静态工厂创建的对象是否相同: " + (b3 == b4));  // 输出 true

        // 验证内存地址差异
        System.out.println("构造器实例地址: " + System.identityHashCode(b1));
        System.out.println("构造器实例地址: " + System.identityHashCode(b2));
        System.out.println("静态工厂实例地址: " + System.identityHashCode(b3));

    }
}

其他体现该优势的现成技术:

  1. 包装类缓存机制

    1. Integer.valueOf(int):对-128~127范围内的整数复用对象
    2. Long.valueOf(long):对-128~127范围内的长整数复用对象
    3. Byte.valueOf(byte):所有字节值均被缓存复用
  2. 字符串常量池
  3. 枚举单例模式
  4. 连接池技术
  5. 享元模式实现
// 如java.awt.Color中的颜色缓存
Color red1 = Color.RED;
Color red2 = Color.RED;  // 相同引用
 
3. 可以返回原返回类型的任何子类
public class Sonable {
    public static void main(String[] args) {
        // 静态工厂方法返回List接口的不同子类实现
        List<String> emptyList = Collections.emptyList();
        List<String> singletonList = Collections.singletonList("Java");
        List<String> unmodifiableList = Collections.unmodifiableList(Arrays.asList("A", "B"));

        System.out.println("emptyList 实际类型: " + emptyList.getClass().getName());
        System.out.println("singletonList 实际类型: " + singletonList.getClass().getName());
        System.out.println("unmodifiableList 实际类型: " + unmodifiableList.getClass().getName());
    }
}

4. 根据传入的枚举类参数决定返回对象的实际类型
public class DiffParaDiffNew {

    // 定义小规模枚举(<=64个元素)
    enum SmallSizeEnum { A, B, C }

    // 定义大规模枚举(>64个元素)
    enum LargeSizeEnum {
        E01, E02, E03, E04, E05, E06, E07, E08, E09, E10,
        E11, E12, E13, E14, E15, E16, E17, E18, E19, E20,
        E21, E22, E23, E24, E25, E26, E27, E28, E29, E30,
        E31, E32, E33, E34, E35, E36, E37, E38, E39, E40,
        E41, E42, E43, E44, E45, E46, E47, E48, E49, E50,
        E51, E52, E53, E54, E55, E56, E57, E58, E59, E60,
        E61, E62, E63, E64, E65  // 65个元素
    }

    public static void main(String[] args) {
        // 静态工厂方法根据参数返回不同实现类
        Set<SmallSizeEnum> smallSet = EnumSet.noneOf(SmallSizeEnum.class);
        Set<LargeSizeEnum> largeSet = EnumSet.noneOf(LargeSizeEnum.class);

        // 验证实际返回的类名
        System.out.println("小枚举集合实现类: " + smallSet.getClass().getName());
        System.out.println("大枚举集合实现类: " + largeSet.getClass().getName());

        smallSet.add(SmallSizeEnum.A);
        largeSet.add(LargeSizeEnum.E01);

        System.out.println(smallSet);
        System.out.println(largeSet);
    }
}

进一步优势在于:

1. 用户只知道会返回EnumSet的子类,但是并不用考虑有多少个子类、使用哪一个子类。

2. 服务侧在对EnumSet更新迭代时,不管是添加还是删除多少子类,客户端无感知。

5. 根据传入的类名动态创建对象实例
public class DynamicObjectFactory {
    /**
     * 静态工厂方法:根据传入的类名动态创建对象实例
     * @param className 目标类的全限定名
     * @return 新创建的实例(实际类型在编译期未知)
     * @throws Exception 处理反射相关异常
     */
    public static Object createInstance(String className) throws Exception {
        // 动态加载类(该类在编译工厂代码时可能不存在)
        Class<?> targetClass = Class.forName(className);

        // 获取无参构造器
        Constructor<?> constructor = targetClass.getDeclaredConstructor();

        // 创建新实例
        return constructor.newInstance();
    }

    // 测试用例
    public static void main(String[] args) throws Exception {

        // 使用场景0:创建编译时已知的类
        Object obj0 = createInstance("org.learn.chapter1.static_factory_method.Name");
        System.out.println("创建对象类型: " + obj0.getClass().getName());

        // 使用场景1:创建编译时未知的类
        Object obj1 = createInstance("com.example.DynamicService");
        System.out.println("创建对象类型: " + obj1.getClass().getName());

        // 使用场景2:运行时动态切换实现类
        Object obj2 = createInstance("com.example.AlternativeServiceImpl");
        System.out.println("创建对象类型: " + obj2.getClass().getName());
    }
}

可以动态创建编译时未知的类——"com.example.DynamicService"

经典用法如:JDBC,一个服务有多个服务商的实现。

二、静态工厂方法的主要缺点

1. 若类不含公有 / 受保护构造器,仅通过静态工厂创建,则该类无法被继承(如 java.util.Collections 的内部子类)
2. 程序员很难发现它们。

需要规范接口命名和注释

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐