ITEM 1: 用静态工厂方法替代构造函数

ITEM 1: CONSIDER STATIC FACTORY METHODS INSTEAD OF CONSTRUCTORS

获取一个实例的传统方式是调用它的构造函数,然而还有另一种应当在每个程序员的工具箱里的方法 —— 类提供静态工厂方法。举一个简单的例子:

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE :  Boolean.FALSE;
}

注意,本节描述的静态工厂方法与工厂方法设计模式不同。

一个类提供静态工厂方法替代构造函数的优势在于:

(1)静态工厂方法有自己的函数名。
它能够很好的描述方法返回的实例的具体语义。例如:BigInteger(int, int, Random),这个构造函数返回一个可能是质数的BigInteger实例,但你不可能在构造函数的方法名中体现出这一点。 BigInteger.probablePrime(int, int, Random) 这个名字就好多了。
此外,一个类只能有一个签名相同的构造函数,如果两个构造函数有相同类型的入参,那程序员只能调整其中一个的入参顺序好让编译器通过,然而这样的构造函数对于用户来说就是一场遭难(没有文档的帮助你永远不会记得顺序)。而对于静态工厂方法来说,给它们一个合适的名字就解决了。

(2)静态工厂方法被调用时,不要求每次新建一个实例。
这就允许不变类使用之前构造的/缓存的实例返回,以避免重复构造不必要的实例。上文的Boolean.valueOf() 正是这样(这有点像享元模式),这样做能够提高性能,在需要频繁创建实例而它们的开销又较大时。
这种做法还能使类对他们的实例进行控制,这被称作 instance-controlled (实例控制?)。类能利用这个技术实现单例、不可实例化。同时,它还能使不可变类保证不会有两个相同的实例存在。这个技术也是享元模式的基础,Enum types (Item 34) provide this guarantee.(?)

(3)静态工厂方法可以返回一个子类实例。
这给予了程序员很大的自由,API可以返回实例而不需要暴露实际的类。这种技术适用于基于接口的框架,其中接口为静态工厂方法提供了自然的返回类型。在Java 8之前,接口不能有静态方法。按照惯例,一个名为Type的接口的静态工厂方法被放在一个名为Types的非实例化伙伴类中。
例如,Java Collections框架有45个接口的实用程序实现,提供不可修改的集合、同步的集合等等。几乎所有这些实现都是通过一个不可实例化类(java.util.Collections)中的静态工厂方法导出的。返回对象的类都是非公共的。
集合框架API比导出45个单独的公共类要小得多,每个公共类对应一个方便的实现。减少的不仅仅是API的大部分,还有概念的权重:程序员必须掌握的概念的数量和难度,才能使用API。程序员知道返回的对象精确地拥有由其接口指定的API,因此不需要为实现类读取额外的类文档。此外,使用这种静态工厂方法要求客户机通过接口而不是实现类引用返回的对象,这通常是一种很好的实践(第64项)。从Java 8开始,接口不能包含静态方法的限制被消除了,因此通常没有理由为接口提供非实例化的伴随类。许多公共静态成员应该放在接口本身中,而不是放在类中。但是,请注意,仍然有必要将这些静态方法后面的大部分实现代码放在单独的包私有类中。这是因为Java 8要求接口的所有静态成员都是公共的。Java 9允许私有静态方法,但是静态字段和静态成员类仍然需要是公共的。

(4)静态工厂方法返回的实例,可以随调用入参的不同而返回不同的子类实例。
例如:EnumSet 类在 OpenJDK 实现中,可能返回两种子类实例,如果有64或更少的元素,就会返回 RegularEnumSet 实例 由一个单独的 long 实现;而如果有65个以上元素,就会返回 JumboEnumSet,由一个 long 数组实现。客户端既不知道也不关心从工厂返回的对象的类,它们只关心它是某个 EnumSet 的子类。如果有一天人们研究出性能比 RegularEnumSet 更好的方法,那么可以在未来的版本直接替换它。

(5)在编写包含方法的类时,返回对象的类不需要存在
这种灵活的静态工厂方法构成了服务提供者框架的基础,比如Java数据库连接API (JDBC)。服务提供者框架是一个系统,在这个系统中,提供者实现了一个服务,并且系统使这些实现对客户端可用,从而将客户端与实现解耦。
服务提供者框架中有三个基本组件:表示实现的服务接口;提供程序注册API,提供程序使用该API注册实现;和服务访问API,客户端使用该API获取服务实例。服务访问API允许客户端指定选择实现的标准。如果没有这些标准,API将返回默认实现的实例,或者允许客户端遍历所有可用的实现。服务访问API是构成服务提供者框架基础的灵活静态工厂。
服务提供者框架的第四个可选组件是服务提供者接口,它描述了生成服务接口实例的工厂对象。在缺少服务提供者接口的情况下,必须反射地实例化实现(第65项)。在JDBC中,Connection扮演服务接口的角色。DriverManager.registerDriver是提供者注册API。DriverManager.getConnection是服务访问API, Driveris是服务提供者接口。
服务提供者框架模式有许多变体。例如,服务访问API可以向客户机返回比提供者提供的服务接口更丰富的服务接口。这是桥型[Gamma95]。依赖注入框架(第5项)可以被视为功能强大的服务提供者。自Java 6以来,该平台包括一个通用服务提供者框架Java.util.ServiceLoader,所以不需要,通常也不应该编写自己的(第59项)。JDBC不使用ServiceLoader,因为前者早于后者。

静态工厂方法也存在缺点:

(1)如果一个类没有public/protected 构造函数,那么就不能被继承
例如,你不能继承任何 Collections 中的类,不过这也许是因祸得福,Java鼓励程序员对集合使用组合而不是继承。并且这也符合一个不可变类的要求。

(2)静态工厂方法很难被程序员发现
它们在API文档中不像构造函数那样“亮眼”,因此很难找出如何实例化一个提供静态工厂方法而不是构造函数的类。程序员可以通过在类或接口文档中关注静态工厂,并遵循公共命名约定来减少这个问题。下面是静态工厂方法的一些常见名称:

  • from, 一种类型转换方法,它接受一个参数并返回该类型的对应实例
    Date d = Date.from(instant)

  • of, 一种聚合方法,它接受多个参数并返回包含这些参数的这种类型的实例
    Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING)

  • valueOf, 相比from/of,一个更冗长的替代方案
    BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);

  • instance / getInstance, 返回一个实例,该实例由它的参数(如果有的话)描述,但不能说具有相同的值
    StackWalker luke = StackWalker.getInstance(options);

  • create / newInstance 类似instance或getInstance,但方法保证每次调用返回一个新实例
    Object newArray = Array.newInstance(classObject, arrayLen);

  • getType, 与getInstance类似,但如果工厂方法位于不同的类中,则使用该方法。Type是工厂方法返回的对象类型
    FileStore fs = Files.getFileStore(path);

  • newType, 与newInstance类似,但如果工厂方法位于不同的类中,则使用该方法。Type是工厂方法返回的对象类型
    BufferedReader br = Files.newBufferedReader(path);

  • type, 一个简洁的getType和newType的替代方案
    List<Complaint> litany = Collections.list(legacyLitany);

    总之,静态工厂方法和公共构造函数都有各自的用途,了解它们的相对优点是有好处的。通常静态工厂是更好的选择,因此避免在没有首先考虑静态工厂的情况下提供公共构造函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354