转载链接:https://www.jianshu.com/p/ceb5ec8f1174
1.序:什么是静态工厂方法
在 Java 中,获得一个类实例最简单的方法就是使用 new 关键字,通过构造函数来实现对象的创建。
就像这样:

Fragment fragment = new MyFragment();
// or
Date date = new Date();

不过在实际的开发中,我们经常还会见到另外一种获取类实例的方法:

 Fragment fragment = MyFragment.newIntance();// or Calendar calendar = Calendar.getInstance();// or Integer number = Integer.valueOf("3");

↑ 像这样的:不通过 new,而是用一个静态方法来对外提供自身实例的方法,即为我们所说的静态工厂方法(Static factory method)。

知识点:new 究竟做了什么?
简单来说:当我们使用 new 来构造一个新的类实例时,其实是告诉了 JVM 我需要一个新的实例。JVM
就会自动在内存中开辟一片空间,然后调用构造函数来初始化成员变量,最终把引用返回给调用方。

2 .Effective Java 所说优点
2.1 静态工厂方法与构造器不同的第一优势在于,它们有名字
由于语言的特性,Java 的构造函数都是跟类名一样的。这导致的一个问题是构造函数的名称不够灵活,经常不能准确地描述返回值,在有多个重载的构造函数时尤甚,如果参数类型、数目又比较相似的话,那更是很容易出错。
比如,如下的一段代码 :

Date date0 = new Date();
Date date1 = new Date(0L);
Date date2 = new Date("0");
Date date3 = new Date(1,2,1);
Date date4 = new Date(1,2,1,1,1);
Date date5 = new Date(1,2,1,1,1,1);

—— Date 类有很多重载函数,对于开发者来说,假如不是特别熟悉的话,恐怕是需要犹豫一下,才能找到合适的构造函数的。而对于其他的代码阅读者来说,估计更是需要查看文档,才能明白每个参数的含义了。
(当然,Date 类在目前的 Java 版本中,只保留了一个无参和一个有参的构造函数,其他的都已经标记为 @Deprecated 了)
而如果使用静态工厂方法,就可以给方法起更多有意义的名字,比如前面的 valueOf、newInstance、getInstance 等,对于代码的编写和阅读都能够更清晰。
2.2 第二个优势,不用每次被调用时都创建新对象
这个很容易理解了,有时候外部调用者只需要拿到一个实例,而不关心是否是新的实例;又或者我们想对外提供一个单例时 —— 如果使用工厂方法,就可以很容易的在内部控制,防止创建不必要的对象,减少开销。
在实际的场景中,单例的写法也大都是用静态工厂方法来实现的。
2.3 第三个优势,可以返回原返回类型的子类
这条不用多说,设计模式中的基本的原则之一——『里氏替换』原则,就是说子类应该能替换父类。
显然,构造方法只能返回确切的自身类型,而静态工厂方法则能够更加灵活,可以根据需要方便地返回任何它的子类型的实例。

Class Person {public static Person getInstance(){return new Person();// 这里可以改为 return new Player() / Cooker()}
}
Class Player extends Person{}
Class Cooker extends Person{}

比如上面这段代码,Person 类的静态工厂方法可以返回 Person 的实例,也可以根据需要返回它的子类 Player 或者 Cooker。(当然,这只是为了演示,在实际的项目中,一个类是不应该依赖于它的子类的。但如果这里的 getInstance () 方法位于其他的类中,就更具有的实际操作意义了)
2.4 第四个优势,在创建带泛型的实例时,能使代码变得简洁
这条主要是针对带泛型类的繁琐声明而说的,需要重复书写两次泛型参数:

Map<String,Date> map = new HashMap<String,Date>();

不过自从 java7 开始,这种方式已经被优化过了 —— 对于一个已知类型的变量进行赋值时,由于泛型参数是可以被推导出,所以可以在创建实例时省略掉泛型参数。

Map<String,Date> map = new HashMap<>();

所以这个问题实际上已经不存在了。
3.除此之外
以上是《Effective Java》中总结的几条应该使用静态工厂方法代替构造器的原因,如果你看过之后仍然犹豫不决,那么我觉得可以再给你更多一些理由 —— 我个人在项目中是大量使用静态工厂方法的,从我的实际经验来世,除了上面总结的几条之外,静态工厂方法实际上还有更多的优势。
3.1 可以有多个参数相同但名称不同的工厂方法
构造函数虽然也可以有多个,但是由于函数名已经被固定,所以就要求参数必须有差异时(类型、数量或者顺序)才能够重载了。
举例来说:

class Child{int age = 10;int weight = 30;public Child(int age, int weight) {this.age = age;this.weight = weight;}public Child(int age) {this.age = age;}
}

Child 类有 age 和 weight 两个属性,如代码所示,它已经有了两个构造函数:Child(int age, int weight) 和 Child(int age),这时候如果我们想再添加一个指定 wegiht 但不关心 age 的构造函数,一般是这样:

public Child( int weight) {this.weight = weight;
}

↑ 但要把这个构造函数添加到 Child 类中,我们都知道是行不通的,因为 java 的函数签名是忽略参数名称的,所以 Child(int age) 跟 Child(int weight) 会冲突。
这时候,静态工厂方法就可以登场了。

class Child{int age = 10;int weight = 30;public static Child newChild(int age, int weight) {Child child = new Child();child.weight = weight;child.age = age;return child;}public static Child newChildWithWeight(int weight) {Child child = new Child();child.weight = weight;return child;}public static Child newChildWithAge(int age) {Child child = new Child();child.age = age;return child;}
}

其中的 newChildWithWeight 和 newChildWithAge,就是两个参数类型相同的的方法,但是作用不同,如此,就能够满足上面所说的类似Child(int age) 跟 Child(int weight)同时存在的需求。
(另外,这两个函数名字也是自描述的,相对于一成不变的构造函数更能表达自身的含义,这也是上面所说的第一条优势 —— 『它们有名字』)
3.2 可以减少对外暴露的属性
软件开发中有一条很重要的经验:对外暴露的属性越多,调用者就越容易出错。所以对于类的提供者,一般来说,应该努力减少对外暴露属性,从而降低调用者出错的机会。
考虑一下有如下一个 Player 类:

// Player : Version 1
class Player {public static final int TYPE_RUNNER = 1;public static final int TYPE_SWIMMER = 2;public static final int TYPE_RACER = 3;protected int type;public Player(int type) {this.type = type;}
}

Player 对外提供了一个构造方法,让使用者传入一个 type 来表示类型。那么这个类期望的调用方式就是这样的:

Player player1 = new Player(Player.TYPE_RUNNER);
Player player2 = new Player(Player.TYPE_SWEIMMER);

但是,我们知道,提供者是无法控制调用方的行为的,实际中调用方式可能是这样的:

Player player3 = new Player(0);
Player player4 = new Player(-1);
Player player5 = new Player(10086);

提供者期望的构造函数传入的值是事先定义好的几个常量之一,但如果不是,就很容易导致程序错误。
—— 要避免这种错误,使用枚举来代替常量值是常见的方法之一,当然如果不想用枚举的话,使用我们今天所说的主角静态工厂方法也是一个很好的办法。
插一句:
实际上,使用枚举也有一些缺点,比如增大了调用方的成本;如果枚举类成员增加,会导致一些需要完备覆盖所有枚举的调用场景出错等。
如果把以上需求用静态工厂方法来实现,代码大致是这样的:

// Player : Version 2
class Player {public static final int TYPE_RUNNER = 1;public static final int TYPE_SWIMMER = 2;public static final int TYPE_RACER = 3;int type;private Player(int type) {this.type = type;}public static Player newRunner() {return new Player(TYPE_RUNNER);}public static Player newSwimmer() {return new Player(TYPE_SWIMMER);}public static Player newRacer() {return new Player(TYPE_RACER);}
}

注意其中的构造方法被声明为了 private,这样可以防止它被外部调用,于是调用方在使用 Player 实例的时候,基本上就必须通过 newRunner、newSwimmer、newRacer 这几个静态工厂方法来创建,调用方无须知道也无须指定 type 值 —— 这样就能把 type 的赋值的范围控制住,防止前面所说的异常值的情况。
插一句:
严谨一些的话,通过反射仍能够绕过静态工厂方法直接调用构造函数,甚至直接修改一个已创建的 Player 实例的 type 值,但本文暂时不讨论这种非常规情况。
3.3 多了一层控制,方便统一修改
我们在开发中一定遇到过很多次这样的场景:在写一个界面时,服务端的数据还没准备好,这时候我们经常就需要自己在客户端编写一个测试的数据,来进行界面的测试,像这样:

// 创建一个测试数据
User tester = new User();
tester.setName("隔壁老张");
tester.setAge(16);
tester.setDescription("我住隔壁我姓张!");
// use tester
bindUI(tester);
……

要写一连串的测试代码,如果需要测试的界面有多个,那么这一连串的代码可能还会被复制多次到项目的多个位置。
这种写法的缺点呢,首先是代码臃肿、混乱;其次是万一上线的时候漏掉了某一处,忘记修改,那就可以说是灾难了……
但是如果你像我一样,习惯了用静态工厂方法代替构造器的话,则会很自然地这么写,先在 User 中定义一个 newTestInstance 方法:

static class User{String name ;int age ;String description;public static User newTestInstance() {User tester = new User();tester.setName("隔壁老张");tester.setAge(16);tester.setDescription("我住隔壁我姓张!");return tester;}
}

然后调用的地方就可以这样写了:

  // 创建一个测试数据User tester = User.newTestInstance();// use testerbindUI(tester);

是不是瞬间就觉得优雅了很多?!
而且不只是代码简洁优雅,由于所有测试实例的创建都是在这一个地方,所以在需要正式数据的时候,也只需把这个方法随意删除或者修改一下,所有调用者都会编译不通过,彻底杜绝了由于疏忽导致线上还有测试代码的情况。
4. 总结
总体来说,我觉得『考虑使用静态工厂方法代替构造器』这点,除了有名字、可以用子类等这些语法层面上的优势之外,更多的是在工程学上的意义,我觉得它实质上的最主要作用是:能够增大类的提供者对自己所提供的类的控制力。
作为一个开发者,当我们作为调用方,使用别人提供的类时,如果要使用 new 关键字来为其创建一个类实例,如果对类不是特别熟悉,那么一定是要特别慎重的 —— new 实在是太好用了,以致于它经常被滥用,随时随地的 new 是有很大风险的,除了可能导致性能、内存方面的问题外,也经常会使得代码结构变得混乱。
而当我们在作为类的提供方时,无法控制调用者的具体行为,但是我们可以尝试使用一些方法来增大自己对类的控制力,减少调用方犯错误的机会,这也是对代码更负责的具体体现。

5.缺点
1、类如果不含有公有的或者受保护的构造器,就不能被子类化,不能被继承。
2、静态工厂方法与其他的静态方法实际上没有任何区别。在API文档里面,它们没有像构造器那样明确标识出来,所以,对于提供了静态工厂方法而不是构造器的类来说,想要查明如何实例化一个类,非常困难。
因此我们约定了一些静态工厂方法的常用名称
1.valueOf —— 不太严格的讲,该方法返回的实例与它的参数具有相同的值。例如:

   String id = String.valueOf(1);

2.of —— valueOf的一种简洁的替代,在EnumSet中使用并流行起来。
聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:

EnumSet enumSet = EnumSet.of(AssignTypeEnum.GENERAL, AssignTypeEnum.DISPERSE,AssignTypeEnum.DISPERSE);

3.instance 或 getinstance —— 返回一个由其参数 (如果有的话) 描述的实例,但不能说它具有相同的值,例如:

Date now = Calendar.getInstance().getTime();

4.create 或 newInstance —— 与 instance 或 getInstance 类似,但该方法保证每次调用返回一个新的实例,例如:

Object newArray = Array.newInstance(classObject, arrayLen);

5.getType —— 与 getInstance 类似,但是在工厂方法处于不同的类中的时候使用。Type 是工厂方法返回的对象类型。
6.newType —— 与 newInstance 类似,但是在工厂方法处于不同的类中的时候使用。Type 是工厂方法返回的对象类型。

第一条:考虑用静态工厂方法代替构造器相关推荐

  1. java 静态工厂方法代替构造器的好处

    Java 的静态工厂方法 序:什么是静态工厂方法 Effective Java 2.1 静态工厂方法与构造器不同的第一优势在于,它们有名字 2.2 第二个优势,不用每次被调用时都创建新对象 2.3 第 ...

  2. Java九十条经验法则之第一条:用静态工厂方法代替构造器

    一.用静态工厂方法代替构造器的优势 1.静态构造方法与构造器不同的第一大优势在于,他们有名称,而构造器名字相同,容易混淆,例如: 使用静态构造方式的方式,我们就可以通过知道产生的是什么样类型的对象,而 ...

  3. 第1条:考虑采用静态工厂方法代替构造器

    第1条:考虑采用静态工厂方法代替构造器 第1条:考虑采用静态工厂方法代替构造器 对类而言,为了让客户端获取它自身的一个实例,最常用的方法就是提供一个公有的构造器.还有一种应该被程序员重视的方法:类提供 ...

  4. 《Effective Java》学习笔记 - (1) 使用静态工厂方法代替构造器

    文章目录 前言 使用静态工厂方法代替构造器 1. 优点 1.1 静态工厂方法有名称 1.2 不必每次调用的时候都创建一个对象 1.3 可以返回类型的任何子类型的对象 1.4 所返回的对象的类型可以随着 ...

  5. 用静态工厂方法代替构造器

    1.静态工厂方法与构造器不同的第一大优势在于,它们有名称.如果构造器的参数本身没有确切地描述正被返回的对象,那么具有适当名称的静态工厂会更容易使用,产生的客户端代码也更易于阅读.例如,构造器Bigin ...

  6. 静态工厂方法代替构造器实例_静态工厂方法与传统构造方法

    静态工厂方法代替构造器实例 之前,我已经讨论过一些关于Builder模式的信息 , Builder Pattern是一种有用的模式,用于实例化具有几个(可能是可选的)属性的类,这些属性可以使读取,编写 ...

  7. 01、静态工厂方法替代构造器

    考虑用静态工厂方法替代构造器 考虑使用静态工厂方法来替代构造器的原因: 静态工厂方法有名称:普通的构造器中,参数并不能很好地描述返回对象的特点,代码的阅读性不好. 考虑下面的程序: Random ra ...

  8. Effective Java 第一条:考虑用静态工厂方法代替构造器

    优势: 第一条:静态工厂方法有名称,可以从名字看出方法的作用,容易使用. 第二条:不必每次调用都创建一个新对象. 第三条:静态工厂方法可以返回原类型的任何子类型的对象. 第四条:在创建参数化类型的实例 ...

  9. 第1条:考虑用静态工厂方法代替构造器

    为了获得一个类的实例,有两种办法1.类提供一个公有的构造器 2.类提供一个公有的静态工厂方法. 静态工厂方法的优势: 1.有名称. 慎重地选择方法名称能突出多个构造器的区别,例如使用BigIntege ...

最新文章

  1. php解决 mysql_connect(): The mysql extension is deprecated and will be removed in the future: use mysq
  2. java 成绩 排名算法_Java计算成绩平均值和排名
  3. 10月第3周安全回顾 中小企业易受攻击 RealPlayer现漏洞
  4. 免费直播 | 特邀大厂产品战略咨询顾问,详解数据驱动产品商业化
  5. 使用Golang搭建gRPC服务提供给.NetCore客户端调用
  6. linux ps aux 结果解释
  7. 在中国使用pip更新或安装库的配置
  8. python字符串命名_python-字符串及其常用方法
  9. php 日期 间隔,PHP实现计算日期间隔天数的方法
  10. 2021年中国独角兽企业发展研究报告
  11. js 链接websocket马上断开_SpringBoot+WebSocket实现简单的数据推送
  12. 为什么不可以使用哈曼顿距离_声光色影俱佳,美也要与众不同,哈曼卡顿无线水晶4评测...
  13. QT6.1.2下载和安装教程
  14. 不同tric 改进的理由
  15. 图片哈希概述(image hash)
  16. 用 BOX-COX 变换进行数据正态性处理
  17. Vue3使用axios的配置教程
  18. 怎样使用GetAsyncKeyState()
  19. ubuntu系统中用c语言编写简单程序
  20. 计算机三维建模概述论文,基于视觉的三维场景建模研究与实现-计算机科学与技术专业毕业论文.docx...

热门文章

  1. 题目81:输入任一的自然数A, B, 求A , B的最小公倍数。
  2. Buffalo LS-QVL安装+配置信息
  3. Win10创建原始套接字失败原因及解决方法
  4. sql server 将日期时间转为中文大写日期
  5. 和平精英——产品分析
  6. 打印后台程序无法运行
  7. Beginners Guide To The MQTT Protocol
  8. [转]《梦幻西游》手游服务器如何实现200万玩家同时在线?(技术篇)
  9. 8款设计者必藏优秀免费英文字体
  10. erlang 中#(井号)号的作用