《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. 程序员很难发现它们。
需要规范接口命名和注释