《Effective Java》第一条:用静态工厂方法代替构造器
·
一、静态工厂方法与构造器不同的优势
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));
}
}

其他体现该优势的现成技术:
-
包装类缓存机制
Integer.valueOf(int):对-128~127范围内的整数复用对象Long.valueOf(long):对-128~127范围内的长整数复用对象Byte.valueOf(byte):所有字节值均被缓存复用
- 字符串常量池
- 枚举单例模式
- 连接池技术
- 享元模式实现
// 如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. 程序员很难发现它们。
需要规范接口命名和注释
更多推荐



所有评论(0)