<创建和销毁对象>经验法则——考虑用静态工厂方法代替公有构造方法
一、引出静态工厂方法
对于java类而言,为了让使用者获取它自身的一个实例化对象,会有以下方法:
1、该类提供一个公有的构造方法。在这种情况下,程序可以通过多个“new 构造方法”语句来创建类的任意多个实例。但是每执行一条new语句,都会导致java虚拟机的堆区中产生一个新的对象。
2、该类提供一个公有的静态工厂方法(它只是一个简单的静态方法,返回的是类的一个实例,要区别于设计模式中的工厂方法模式)。对于某些java平台库类或自己的工具类、参数化类,需要进一步封装创建自身实例的细节,并且控制自身实例的数目,那么就可以采用方法2提供静态工厂方法来实现。例如:Class实例是Java虚拟机在加载一个类时自动创建的,程序无法用new语句创建java.lang.Class类的实例,因为Class类没有提供public类型的构造方法。为了使程序能获得代表某个类的Class实例,在Class类中提供了静态工厂方法forName(String name),如下:
- Class clazz=Class.forName("FirstDemo"); //返回代表FirstDemo类的实例
静态工厂方法和公有构造方法都有各自的长处,但通常静态工厂方法更为合适,所以在使用时,可以优先考虑静态工厂方法。
注意:并不是所有情况下,静态工厂方法都优于公有构造方法!要充分理解静态工厂方法的适用情况,能够把握该方式收放自如才可考虑使用该方式,否则最好还是使用java规范的公有构造方法,避免程序混乱。
下面来详细对比一下两者区别。
二、对比静态工厂方法和构造方法
在某些场合,提供静态工厂方法而不用公有的构造方法,具有以下的优势:
1、每个静态工厂方法可以有自己任意定义的名字,因此在方法名中就能体现出与实例有关的信息,可以提高程序代码的可读性。而一个类中构造方法的名字必须与类名相同,即使需要提供2个构造方法,只能令构造方法的参数个数不同或者个数相同但参数类型的顺序上保持不同,因此不能单从名字上就区分出每个重载方法的用途,用户调用时容易引起混淆。例如用静态工厂方法获取男女实例,显然能够更易于阅读:
- public class Gender {
- //私有化实例和构造方法
- private String description;
- private static final Gender female = new Gender("女");
- private static final Gender male = new Gender("男");
- private Gender(String description) {
- this.description = description;
- }
- //公有的静态工厂方法
- public static Gender getFemaleInstance() {//获取女性实例
- return female;
- }
- public static Gender getMaleInstance() {//获取男性实例
- return male;
- }
- public String getDescription() {
- return description;
- }
- }
注意:用静态工厂方法代替构造方法后,构造方法就成了private的了,但如果你希望同时也提供公有的构造方法也是可以的。
2、如以上提到的使用new构造方法方式每次创建实例时都要执行一次new语句,而静态工厂方法不必在每次调用时都创建一个新的对象,是否会创建一个新的对象完全取决于方法的实现。
- public class StaticFactoryDemo {
- private static final StaticFactoryDemo demo = new StaticFactoryDemo();//私有,静态,final,构造方法实例化对象
- public static StaticFactoryDemo getInstance(){//公有的静态工厂方法,获取实例化对象
- return demo;
- }
- public void printMessage(){//普通方法
- System.out.println("Test Static Factory Class!");
- }
- }
例如:在以上代码的全局唯一性对象中通过自定义的getInstance()静态方法提供对该对象的返回。如果需要在其他类中调用StaticFactoryDemo类中的printMessage方法,那么只需要使用如下语句即可,而不必使用new关键字:
- StaticFactoryDemo.getInstance().printMessage();//调用printMessage方法
利用这一特点,静态工厂方法可用来创建以下类的实例。
单例类:只有惟一的实例的类(如以上的StaticFactoryDemo类)。
枚举类:实例的数量有限的类(如:enum weekday{ sun,mou,tue,wed,thu,fri,sat };)。
具有实例缓存的类(该类通常满足:1、类的实例的数量有限2、程序执行过程中,频繁访问该类的一些特定实例):能把已经创建的实例暂且存放在缓存中的类。
具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。具有实例缓存的不可变类扩展代码:
- import java.lang.ref.SoftReference;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Set;
- public class Name {
- private String firstname;
- private String lastname;
- private Name(String firstname, String lastname) {
- this.firstname = firstname;
- this.lastname = lastname;
- }
- // 实例缓存,存放Name对象的软引用
- private static final Set<SoftReference<Name>> names = new HashSet<SoftReference<Name>>();
- // valueOf静态工厂方法
- public static Name valueOf(String firstname, String lastname) {
- Iterator<SoftReference<Name>> it = names.iterator();
- while (it.hasNext()) {
- SoftReference<Name> ref = it.next(); // 获得软引用
- Name name = ref.get(); // 获得软引用所引用的Name对象
- if (name != null && name.firstname.equals(firstname) && name.lastname.equals(lastname))
- return name;
- }
- // 如果在缓存中不存在Name对象,就创建该对象,并同时把它的软引用加入到实例缓存
- Name name = new Name(firstname, lastname);
- names.add(new SoftReference<Name>(name));
- return name;
- }
- // 主方法
- public static void main(String[] args) {
- Name n1 = Name.valueOf("大大", "张");
- Name n2 = Name.valueOf("大大", "张");
- Name n3 = Name.valueOf("小小", "李");
- System.out.println(n1);
- System.out.println(n2);
- System.out.println(n3);
- System.out.println(n1 == n2); // 打印true
- }
- }
扩展<不可变类>:例如JDK基本类库中所有基本类型的包装类,如Integer和Long类,都是不可变类,java.lang.String也是不可变类。
创建一个不可变类:
1. 所有成员都是private
2. 不提供对成员的改变方法,例如:setXX
3. 确保所有的方法不会被重载。手段有两种:使用final Class(强不可变类),或者将所有类方法加上final(弱不可变类)。
4. 如果某一个类成员不是原始变量(boolean,byte, char, double ,float, integer, long, short)或者不可变类,必须通过在成员初始化或者get方法时通过深度clone方法,来确保类的不可变。例如:
- public final class FinalClass {//不可变类
- private final int[] iArray;//private成员变量
- public FinalClass(int[] aArray) {//整型数组不是原始变量,需要用深度clone方法,保证类的不可变
- this.iArray = aArray.clone();
- }
- public String toString() {
- StringBuffer sb = new StringBuffer("Numbers are: ");
- for (int i = 0; i < iArray.length; i++) {
- sb.append(iArray[i] + " ");
- }
- return sb.toString();
- }
- }
3、new构造方法只能创建当前类的实例,而静态工厂方法可以返回当前类的任何子类的实例化对象。
3.1 使用静态工厂方法时,要求调用者通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象。例如Vector和ArrayList都是List接口的实现:
- List<String> sStrings1 = new Vector<String>();
- List<String> sStrings2 = new ArrayList<String>();
3.2 静态工厂方法返回的对象所属的类,可以是非公有的;也可以随着每次调用而发生改变;在编写包含该静态工厂方法的类时可以不必存在。
3.3 这一特性可以在创建松耦合的系统接口时发挥作用。
扩展:静态工厂方法构成了“服务提供者框架”的基础。服务提供者框架指:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把它们从多个实现中解耦出来。包括4个组件:服务接口;提供者注册API;服务访问API(核心基础);服务提供者接口(可选)。例如代码:
- /*服务提供者框架描述*/
- // 服务接口
- public interface Service {
- // ...
- }
- // 服务提供者接口
- public interface Provider {
- Service newService();
- }
- public class Services {
- private Services() {
- }
- private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();//存储服务提供者信息
- public static final String DEFAULT_PROVIDER_NAME = "<def>";
- // 提供者注册API
- public static void registerDefaultProvider(Provider p) {//默认注册服务提供者
- registerProvider(DEFAULT_PROVIDER_NAME, p);
- }
- public static void registerProvider(String name, Provider p) {//注册服务提供者
- providers.put(name, p);
- }
- // 服务访问API(使用静态工厂方法,返回值为接口类型)
- public static Service newInstance() {//默认实例化服务
- return newInstance(DEFAULT_PROVIDER_NAME);
- }
- public static Service newInstance(String name) {//实例化服务
- Provider p = providers.get(name);
- if (p == null)//若不存在注册的服务提供者,则无法提供服务
- throw new IllegalArgumentException("No provider registered with name:" + name);
- return p.newService();//若存在注册的服务提供者,则可newService()提供服务
- }
- }
4、静态工厂方法,在创建参数化类型实例时,可简洁化代码。
假设如果调用参数化的构造方法时,通常需要两次注明参数的类型,如:
- Map<String,List<String>> m = new HashMap<String,List<String>>();
假设HashMap提供了以下的静态工厂方法:
- public static <K,V> HashMap<K,V> newInstance(){
- return new HashMap<K,V>();
- }
此时,可以使用以上的静态工厂方法实例化对象:
- Map<String,List<String>> m = HashMap.newInstance();
但是使用静态工厂方法也存在缺点:
1、类如果不含有public的或者protected的构造方法,则不能被子类化(被继承)。而且对于静态工厂方法返回的非公有类,也不能被继承。但是程序员通常被鼓励使用复合,而不是使用继承。
2、它与其他静态方法没有任何区别。如果系统中存在很多静态工厂方法,则不容易知道如何去实例化一个类。因此要求程序员在使用静态工厂方法时需要进行详细的注释说明,并遵守标准的命名习惯。其中惯用的名称如下:
valueOf(通常为类型转换方法);getInstance(获取实例);newInstance(创建实例);getType;newType...
<创建和销毁对象>经验法则——考虑用静态工厂方法代替公有构造方法的更多相关文章
- 【Effective Java】第二章-创建和销毁对象——1.考虑用静态工厂方法代替构造器
静态工厂方法的优点: 可以赋予一个具有明确含义的名称 可以复用唯一实例,不必每次新建 可以返回原实例类型的子类对象 可以在返回泛型实例时更加简洁 缺点: 类如果不含有共有的或者受保护的构造器,就不能被 ...
- 《Effect Java》学习笔记1———创建和销毁对象
第二章 创建和销毁对象 1.考虑用静态工厂方法代替构造器 四大优势: i. 有名称 ii. 不必在每次调用它们的时候都创建一个新的对象: iii. 可以返回原返回类型的任何子类型的对象: JDBC ...
- [Effective Java 读书笔记] 第二章 创建和销毁对象 第一条
第二章 创建和销毁对象 第一条 使用静态工厂方法替代构造器,原因: 静态工厂方法可以有不同的名字,也就是说,构造器只能通过参数的不同来区分不同的目的,静态工厂在名字上就能表达不同的目的 静态工厂方法 ...
- Chapter 01:创建和销毁对象
<一>考虑用静态工厂方法代替构造器 下面是Boolean类的一个简单示例: public final class Boolean implements java.io.Serializab ...
- Effective Java 读书笔记(一):使用静态工厂方法代替构造器
这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文&l ...
- Effective Java 读书笔记之一 创建和销毁对象
一.考虑用静态工厂方法代替构造器 这里的静态工厂方法是指类中使用public static 修饰的方法,和设计模式的工厂方法模式没有任何关系.相对于使用共有的构造器来创建对象,静态工厂方法有几大优势: ...
- [原创]java WEB学习笔记102:Spring学习---Spring Bean配置:bean配置方式(工厂方法(静态工厂方法 & 实例工厂方法)、FactoryBean) 全类名
本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java —— 用静态工厂方法代替构造器
本文参考 本篇文章参考自<Effective Java>第三版第一条"Consider static factory methods instead of constructor ...
随机推荐
- linux log4j 使用
1.首先到Apache官网下载log4j.jar文件http://logging.apache.org/log4j/1.2/download.html 引用到eclipse项目里面 2.在src目录下 ...
- C++容器和算法
转自:http://www.cnblogs.com/haiyupeter/archive/2012/07/29/2613145.html 容器:某一类型数据的集合. C++标准顺序容器包括:vecto ...
- MIME邮件格式
转自:http://kptu.iteye.com/blog/890180 排版做了调整. Q.什么是MIME?什么是MIME邮件? A. MIME, 全称为"Multipurpose Int ...
- Loadrunner 性能指标定位系统瓶颈
判断CPU瓶颈 1, %processor time 平均值大于95 2, processor queue length大于2 (大于处理器个数+1).可以确定CPU瓶颈 3, CPU空闲时间为零(z ...
- SoapUI Property
1. Test Suite(Case) Property 选择Test Suite(Case),switch to Custom properties 在request中的引用方式: ${[scope ...
- Windows下配置使用WinPcap
0.前提 windows: win7 x64 WinPcap版本:4.1.3 WinPcap开发包:4.1.2 目标:在VS2010中配置使用winpcap 获取目标计算机中安装的网卡列表 1.下 ...
- linux下安装filezilla客户端遇到的问题
访问filezilla ./filezilla 出现error while loading shared libraries : libpng12.so.o 缺少libpng12.so.o这个文件 解 ...
- Python的pep8(代码规范)
Python的pep8-代码规范 1. 代码布局设计 1.1 缩进 A. 使用四个空格来进行缩进 B. 换行的时候可以使用反斜杠,最好的方法是使用园括号,在使用反斜杠的时候,在反斜 ...
- ubuntu 下安装sh 文件
1. cd 到 指定文件夹 如: cd /home/ddy/下载 2. sudo chmod +x *.sh 3. sudo ./*.sh ok 了 (1)数据预处理 可以用下载好的数据集,也可 ...
- signal()函数
转自:http://blog.csdn.net/sddzycnqjn/article/details/7285760 1. 信号概念 信号是进程在运行过程中,由自身产生或由进程外部发过来的消息(事件) ...