EffectiveJava——第二章 创建和销毁对象
Posted lilpig
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了EffectiveJava——第二章 创建和销毁对象相关的知识,希望对你有一定的参考价值。
前言
本篇笔记是《Effective Java》一书的笔记。
创建和销毁对象
用静态工厂代替构造器
如果你在设计一个类,你不知道该给你的类的使用者使用构造器还是静态工厂方法,大部分时候,静态工厂方法要比构造器更正确。
静态工厂方法能够带来很多好处。
静态工厂有名称
Java中的构造器被强制设计成与类同名,这可以理解,因为构造方法代表你正在创建一个类的实例,所以理应使用new 类名()
的方式,这本没什么问题。但是你一定遇到过这样的问题,就是你正在设计某个用户类:
class User{
private int id;
private int age;
private String name;
}
你的业务逻辑决定你有时需要使用age,name
创建用户对象,有时使用id,name
创建用户对象,这时构造器就无法满足需求了。
public User(int id, String name){/* do something... */}
// 这个构造器和上面的有一样的方法签名,也就是参数类型,所以无法定义
public User(int age, String name){/* do something... */}
这时,一般我们会提供一个User(int id,int age,String name)
的构造器,但是这样你就必须忍受每次使用age,name
时必须也填入一个用于占位的id
参数,对于id,name
也是。一些人还会巧妙地进行如下设计:
public User(int id, String name){/* do something... */}
// 调换该构造器的参数顺序
public User(String name, int age){/* do something... */}
这样确实可以通过编译,而且也能解决每次传入一个多余参数的问题,但随之而来的是不清晰的定义,哪个构造器代表什么?我调用了这个构造器后会得到一个什么样的对象?如果你有很多需要这样处理的参数,指定会乱套。如果你是一个有洁癖的编码人员(实际上99%的编码人员对于代码都有洁癖),那你绝对忍受不了这样的API。
取而代之,使用静态工厂方法,你可以为它指定特别的名字:
public static User userWithIdAndName(int id,String name){/* do something... */}
public static User userWithAgeAndName(int age,String name){/* do something... */}
享元设计
这是在java.lang.Boolean
中的一段代码
public static final Boolean TRUE = new Boolean(true);
public static final Boolean FALSE = new Boolean(false);
//...
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
public static Boolean valueOf(String s) {
return parseBoolean(s) ? TRUE : FALSE;
}
使用静态工厂方法,你可以只返回你创建好的对象,而不需要每次重新创建对象,这种设计模式叫享元模式
,有点像单例模式。使用这种模式,你还可以让本类的实例受控在一定范围内,比如数据库连接池,只有200个连接对象,不会多也不会少。
这样做可以减少无用对象的创建,并且你可以设计这些对象是不可变的(Immutable)、单例的(Singleton)等,总的来说,你对它们的控制更强了。
可以返回原返回类型的任何子类型
使用Collections
中的一个静态方法来说明:
// Collections.emptySortedSet
public static <E> SortedSet<E> emptySortedSet() {
return (SortedSet<E>) UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET;
}
// UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET
private static final NavigableSet<?> EMPTY_NAVIGABLE_SET =
new EmptyNavigableSet<>();
如你所见,Collections
中的emptySortedSet
方法的API返回一个SortedSet
,这是一个接口,我们从库的使用者的角度来说,我们不知道实际返回的是什么类型,但只知道库肯定会返回一个实现类给我们,直接用就好了。实际上,它返回了UnmodifiableNavigableSet.EMPTY_NAVIGABLE_SET
。
当然,这是一个老式的规约,因为在1.8以下的Java版本中,不支持在接口中创建静态方法,所以当时的约定是对于一系列Type
类型的数据,使用一个不可实例化的Types
类来提供一些静态工厂方法创建那些数据的对象,就像Collections
一样。当然,现在我们没必要这么写了,因为接口中已经能创建静态方法了,但这种设计仍然有用武之地。
返回的对象可以随着每次调用发生变化
我举个例子,我们知到在排序数据量不大时,使用插入排序会比快速排序快,这是因为我们在分析算法复杂度时经常忽略掉常数系数。我们完全可以使用一个静态工厂方法来动态选择这两种排序。
public static <T> Sorter<T> getSorter(List<T> list){
return list.size() < 200
? new InsertSorter<T>(list) : new QuickSorter<T>(list);
}
同样,对于使用者来说也不知道这些细节,它们只知道返回了一个Sorter
,可以调用Sorter
有的方法。
方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
用JDBC来举例吧,JDBC只提供了一套接口,定义了一套使用Java访问数据库的标准,对应的实现由数据库厂商来自行根据这套标准设计。
如果你足够细心,应该发现了,Driver
、Connection
、Statement
和ResultSet
这四个我们最常用的东西,它们都是接口,当我们使用它们的时候,你就会发现,我们使用的其实是厂商的实现类:
public static void main(String[] args) throws Exception {
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/mysql","root","root");
System.out.println(conn);
conn.close();
}
这个实现类在JDBC标准设计之初并未发布,但在这里我们却能通过注册厂商驱动,然后使用一个静态工厂方法来获得它,这给我们的程序带来了很多灵活性,让我们能随意替换一些实现。
下面是getConnection方法的部分代码,选择性观看
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
// ...
// 这里遍历所有已注册的驱动程序
for(DriverInfo aDriver : registeredDrivers) {
// 如果驱动程序能够处理这次连接
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
// 连接
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
// 返回connection
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// ...
}
更多:桥接模式、ServiceLoader、服务提供者框架
懒得写了
...未完...
以上是关于EffectiveJava——第二章 创建和销毁对象的主要内容,如果未能解决你的问题,请参考以下文章