一、前言
《Effective Java》读书笔记系列
第二章 第1条:创建和销毁对象
二、介绍
通常情况下客户端想要获取一个类A
的实例,最传统的方式就是让A
提供一个public
的构造函数。实际上除此之外还有一种方式,那就是提供一个public
的静态工厂方法
三、举例
以Integer
为例子,Integer
里面除了提供构造函数外,还提供了对应的静态工厂方法
:
public final class Integer extends Number implements Comparable<Integer> {
//构造函数1
public Integer(int value) {
this.value = value;
}
//构造函数2
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
//静态工厂1
public static Integer valueOf(String s, int radix)
throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
//静态工厂2
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
//静态工厂3
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
}
四、优缺点
1、优点
1.1、方法名可控
静态工厂方法
的第一个优势是方法名可以由开发者自行决定
,而没有强制要求像构造函数那样必须跟类名一致
1.2、可以不必在每次调用都生成一个新对象
这一点基本上只适用于单例
、不可变类
以及部分有缓存机制的类
,这里以Handler机制
中的Message
为例
public final class Message implements Parcelable {
public Message() {
}
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0;
sPoolSize--;
return m;
}
}
return new Message();
}
Message
提供了一个public
的构造函数,但同时也提供了一个静态工厂方法obtain
,这个方法会产生两种结果:1、通过构造函数创建新的Message
;2、复用缓存池
中的Message
1.3、可以返回原返回类型的子类型
这一点主要体现在当我们面向接口编程
时,根据里氏代换原则
,我们编写的子类需要可以直接替换父类使用,而不造成任何错误
public abstract class Test {
//共有构造函数
public Test() {
}
//抽象方法
protected abstract void p();
//静态工厂方法
public static Test newInstance(){
if (true){
return new B();
}
return new A();
}
//子类A
static class A extends Test{
@Override
public void p() {
}
}
//子类B
static class B extends Test{
@Override
public void p() {
}
}
}
为了方便,我上面直接将A、B
写为静态内部类。可以看到newInstance
方法我们可以根据我们的需求去决定返回哪些子类,而高层客户端调用模块不会因为我们的改变而需要改代码
1.4、所返回对象可随传入的参数变化
这点和1.3类似,可以在静态工厂方法里面加入不同的参数去决定返回哪些对象;又例如在Integer
类中:
//静态工厂1
public static Integer valueOf(String s, int radix)
throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
可以看到Integer
多个静态工厂方法中,其中一个方法可以根据传入的radix(进制)
去决定返回哪一种进制的整型
1.5、方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不存在
说实话这一点我没有看懂,对这句话进行提炼:类可以动态生成。那不就是动态代理吗?
还是说这里是翻译有误了,应该是“方法返回的对象”而不是“方法返回的对象所属的类”?如果是前者的话,那主要是在反射
中体现,也有在部分Android源码
中,例如:
//ActivityTaskManager.class
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}
//SingleTon.class
public abstract class Singleton<T> {
@UnsupportedAppUsage
private T mInstance;
protected abstract T create();
@UnsupportedAppUsage
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
注:以上代码前提是“方法返回的对象”,原文我没有看懂,如果有了解的小伙伴可以分享一下在评论区吗,谢谢~
可以看到ActivityTaskManager
也是通过一个静态工厂方法去获取服务的。
2、缺点
2.1、类如果不含public
或protected
的构造函数,就无法被子类实例化
很浅显地描述,但是我们知道,除了用继承
,组合
是更被推荐使用的一种结构方式
2.2、很难被发现
的确不扫一遍源码的structure
,是很难发现有那么多api
的,所以《Effective Java》给我们推荐了很多静态工厂方法惯用的命名方式:
- 1、
from
- 类型转换方法
- 特点:单个参数,返回该类型相对应的一个实例
Date d = Date.from(instant);
- 2、
of
- 聚合方法
- 特点:带多个参数,返回该类型的一个实例,把它们合并起来
Set<Rank> faceCard = EnumSet.of(JACK, QUEEN, KING);
- 3、
valueOf
- 比
from
和of
更烦琐的一种替代方式
- 比
BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
- 4、
instance 或 getInstance
- 通常在
单例
或多例
情况下使用
- 通常在
StackWalker luke = StackWalker.getInstance(option);
- 5、
create 或 newInstance
- 类似
4
,但是这一类名称代表每次调用都返回一个新的实例
- 类似
Object newArray = Array.newInstance(classObject, arrayLen);
- 6、
getType
type
代表具体类型,作用类似于getInstance
FileStore fs = Files.getFileStore(path);
- 7、
newType
type
代表具体类型,作用类似于newInstance
BufferedReader br = Files.newBufferedReader(path);
- 8、
type
6、7
的简约版本
List<Compaint> litany = Collections.list(legactLitany);
五、涉及的其他知识点
1、Java 8 新特性:接口增强
JDK8
之后允许在接口里定义default
和static
的具体
方法,这一特性让接口和抽象类在使用上
的界限越来越模糊(但最好还是秉持着接口是约定和协议,抽象类是共性
的习惯去写),这一特性有如下特点:
- 1、
default
方法选择性重写,但如果A
、B
两个接口有一个完全同名的default
方法,那同时实现这两个接口的类C
必须重写这个方法 - 2、
static
方法同普通类的静态方法一样 - 3、变量不需要修饰
public static final
,但是实际上就是public static final
这一特点可以允许在不修改代码的情况下,对接口进行扩展。
接口和抽象类:
- 1、接口是继承体系下的扩展、约定和协议;抽象类是继承体系下的共性
- 2、都是抽象类型
- 3、都可以有具体实现方法,而且可以选择性继承
- 4、
java
不允许多重继承,可以通过内部类或接口来实现- 5、接口的变量只允许
public static final
六、总结
《Effective Java》这本书更多的是讲述一种编程思想,我也还在重新细读的过程中,也会继续将知识记录在博客, 就这样~共勉