Effective Java学习笔记:用静态工厂方法代替构造器(第一条)
文章目录
- 第一条:用静态工厂方法代替构造器
- 优势
- 缺点:
- 静态工厂方法命名约定
第一条:用静态工厂方法代替构造器
它并不是设计模式中的“工厂模式”,而是一个类提供的一个返回类实例的静态方法。
// 构造方法 创建对象,java9后被标记为过时
Integer i1 = new Integer(1);
Integer i2 = new Integer("1");// 静态工厂方法 创建对象
Integer i3 = Integer.valueOf(1); // 与 Integer i3 = 1;等价
其实这个例子不太准确,两个方法还是有区别的,因为Integer中new会创建对象的,而valueOf在-128~127是返回的同一对象的缓存。
优势
- 名称可以不同
构造器的名字必须与类名相同,当有多个构造器时,只能通过参数列表来区分,这会让代码的可读性变差。
// 构造器:这两个有什么区别?不查文档根本不知道
Person p1 = new Person(70, 180);
Person p2 = new Person(180, 70);// 静态工厂方法:一目了然
Person fromWeightAndHeight = Person.fromWeightAndHeight(70, 180);
Person fromHeightAndWeight = Person.fromHeightAndWeight(180, 70);
- 不必每次创建新对象
Boolean.valueOf(true) 每次都返回同一个 TRUE 对象的引用,避免了创建不必要的对象,节省了内存和GC开销。Integer.valueOf 对于小整数(默认-128到127)也有缓存。 - 可以返回原返回类型的子类型对象
注意这里的“原返回类型”
List<Integer> list1 = Collections.singletonList(1);
List<Object> list2 = Collections.emptyList();
List<Integer> list3 = Collections.synchronizedList(new ArrayList<Integer>());
等等···········
Collections工具类中,可以创建一些特殊的List对象。这些不同的List实例实际上是Collections中的内部类,这些类间接实现List接口,所以可以使用接口List直接引用,因为接口定义了他的作用,我们也无需关注具体类是如何实现的就可以使用。
4. 根据参数返回不同的对象
public abstract class EnumSet<E extends Enum<E>> {// 静态工厂方法public static <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType) {Enum<?>[] universe = getUniverse(elementType);if (universe == null)throw new ClassCastException(elementType + " not an enum");// 关键决策点:根据枚举常量数量选择实现if (universe.length <= 64) {return new RegularEnumSet<>(elementType, universe);} else {return new JumboEnumSet<>(elementType, universe);}}
}
根据枚举常量数量选择不同的实现,因为不同范围下RegularEnumSet和JumboEnumSet性能优势不同。
5. 返回的类在编写时可以不存在
例如jdbc:
// 1. 定义服务接口
public interface Connection { ... }// 2. 定义服务访问API
public class DriverManager {public static Connection getConnection(String url) { ... }
}// 3. 定义服务提供者接口
public interface Driver { ... }
厂商实现:
// MySQL的实现
public class MySqlDriver implements Driver {public Connection connect(String url, Properties info) {return new MySqlConnection(url, info); // 返回具体的MySQL连接}
}// MySQL对Connection的实现
public class MySqlConnection implements Connection {// 具体的MySQL数据库连接实现
}
使用:
public class MyApplication {public void accessDatabase() {// 客户端代码 - 面向接口编程Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb");Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery("SELECT * FROM users");// ...}
}
通过ClassLoader机制自动发现和注册驱动,ClassLoader说白了不就是反射么。DriverManager.getConnection就可以根据不同的实现获取到不同的对象,通过接口引用。
缺点:
- 没有公有构造器的类不能被继承
例如上面Collections中的内部类,他们都是默认权限,包级私有的,无法继承。
不过也不算坏事,更安全和灵活,需要的时候自己实现,没必要去继承,如果真的需要,那么包装器模式进行委托就好了。 - 需要记忆
很多时候都需要查看源码才知道原来有这些方法,而构造方法却只要用到名字就可以使用。
静态工厂方法命名约定
-
from: 类型转换方法,接受单个参数并返回相应实例。
Date d = Date.from(instant); -
of: 聚合方法,接受多个参数并返回一个合并它们的实例。
Set faceCards = EnumSet.of(JACK, QUEEN, KING); -
valueOf: 比 from 和 of 更繁琐的替代方法。
Integer prime = Integer.valueOf(7); -
instance 或 getInstance: 返回一个实例,该实例可能是共享的,但不保证是新的。
StackWalker luke = StackWalker.getInstance(options); -
create 或 newInstance: 像 getInstance 一样,但保证每个调用都返回一个新实例。
Object newArray = Array.newInstance(classObject, arrayLen); -
getType: 像 getInstance 一样,但在不同的类中。Type 是工厂方法返回的对象类型。
FileStore fs = Files.getFileStore(path); -
newType: 像 newInstance 一样,但在不同的类中。
BufferedReader br = Files.newBufferedReader(path); -
type: getType 和 newType 的简洁替代。
List litany = Collections.list(legacyLitany);