<创建和销毁对象>经验法则——考虑用静态工厂方法代替公有构造方法
一、引出静态工厂方法
对于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 ...
随机推荐
- OpenGL学习之路(二)
1 引子 在上一篇读书笔记中,我们对书本中给出的例子进行详细的分析.首先是搭出一个框架:然后填充初始化函数,在初始化函数中向OpenGL提供顶点信息(缓冲区对象)和顶点属性信息(顶点数组对象),并启用 ...
- 使用Spring 3的@value简化配置文件的读取
Spring 3支持@value注解的方式获取properties文件中的配置值,大简化了读取配置文件的代码. 1.在applicationContext.xml文件中配置properties文件 & ...
- 18个jQuery Mobile开发贴士和教程
jQuery Mobile 是 jQuery 在手机上和平板设备上的版本.jQuery Mobile 不仅会给主流移动平台带来jQuery核心库,而且会发布一个完整统一的jQuery移动UI框架.支持 ...
- Bootstrap学习笔记上(带源码)
阅读目录 排版 表单 网格系统 菜单.按钮 做好笔记方便日后查阅o(╯□╰)o bootstrap简介: ☑ 简单灵活可用于架构流行的用户界面和交互接口的html.css.javascript工具集 ...
- MongoDB之一介绍(MongoDB与MySQL的区别、BSON与JSON的区别)
MySQL与MongoDB的操作对比,以及区别 MySQL与MongoDB都是开源的常用数据库,但是MySQL是传统的关系型数据库,MongoDB则是非关系型数据库,也叫文档型数据库,是一种NoSQL ...
- kali linux 一些工具及命令集1(搜集DNS信息)
DNS信息收集 1.dnsdict6 用于查看ipv6的dns信息,国内很少ipv6,基本无用 2.dnsmap 收集dns信息,同类别还有dnsenum,dnswalk 使用dnsmap需先找到 ...
- Python 用 os.walk 遍历目录
今天第一次进行 文件遍历,自己递归写的时候还调试了好久,(主要因为分隔符号的问题),后来发现了os.walk方法,就忍不住和大家分享下. 先看下代码: import os for i in os.wa ...
- PASCAL相关图书推荐
PASCAL程序设计(第2版) 作 者 郑启华 著 出 版 社 清华大学出版社 出版时间 2013-01-01 版 次 2 页 数 286 印刷时间 2013-01-01 ...
- enter mysql
1, mysql -u database username -p 2, database password 3, use (database name) -> change database 4 ...
- BITED-Windows8应用开发学习札记之三:如何在Win8应用中实现数据绑定
在微软官方提供的资源中,我们可以看到SampleDataSource.cs已经拥有了定义好了相应的数据结构以及实现类: 建立本地数据 由于我们已经有数据以及相应的数据类,我们需要做的仅仅是将数据放进数 ...