<创建和销毁对象>经验法则——考虑用静态工厂方法代替公有构造方法

一、引出静态工厂方法

  对于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个构造方法,只能令构造方法的参数个数不同或者个数相同但参数类型的顺序上保持不同,因此不能单从名字上就区分出每个重载方法的用途,用户调用时容易引起混淆。例如用静态工厂方法获取男女实例,显然能够更易于阅读:

 1 public class Gender {
 2     //私有化实例和构造方法
 3     private String description;
 4     private static final Gender female = new Gender("女");
 5     private static final Gender male = new Gender("男");
 6     private Gender(String description) {
 7         this.description = description;
 8     }
 9     //公有的静态工厂方法
10     public static Gender getFemaleInstance() {//获取女性实例
11         return female;
12     }
13     public static Gender getMaleInstance() {//获取男性实例
14         return male;
15     }
16     public String getDescription() {
17         return description;
18     }
19 }

  注意:用静态工厂方法代替构造方法后,构造方法就成了private的了,但如果你希望同时也提供公有的构造方法也是可以的。

  2、如以上提到的使用new构造方法方式每次创建实例时都要执行一次new语句,而静态工厂方法不必在每次调用时都创建一个新的对象,是否会创建一个新的对象完全取决于方法的实现。

1 public class StaticFactoryDemo {
2     private static final StaticFactoryDemo demo = new StaticFactoryDemo();//私有,静态,final,构造方法实例化对象
3     public static StaticFactoryDemo getInstance(){//公有的静态工厂方法,获取实例化对象
4            return demo;
5     }
6     public void printMessage(){//普通方法
7           System.out.println("Test Static Factory Class!");
8     }
9 }

  例如:在以上代码的全局唯一性对象中通过自定义的getInstance()静态方法提供对该对象的返回。如果需要在其他类中调用StaticFactoryDemo类中的printMessage方法,那么只需要使用如下语句即可,而不必使用new关键字:

StaticFactoryDemo.getInstance().printMessage();//调用printMessage方法

  利用这一特点,静态工厂方法可用来创建以下类的实例。

单例类:只有惟一的实例的类(如以上的StaticFactoryDemo类)。

枚举类:实例的数量有限的类(如:enum weekday{ sun,mou,tue,wed,thu,fri,sat };)。

具有实例缓存的类(该类通常满足:1、类的实例的数量有限2、程序执行过程中,频繁访问该类的一些特定实例):能把已经创建的实例暂且存放在缓存中的类。

具有实例缓存的不可变类:不可变类的实例一旦创建,其属性值就不会被改变。具有实例缓存的不可变类扩展代码:

 1 import java.lang.ref.SoftReference;
 2 import java.util.HashSet;
 3 import java.util.Iterator;
 4 import java.util.Set;
 5
 6 public class Name {
 7     private String firstname;
 8     private String lastname;
 9     private Name(String firstname, String lastname) {
10         this.firstname = firstname;
11         this.lastname = lastname;
12     }
13     // 实例缓存,存放Name对象的软引用
14     private static final Set<SoftReference<Name>> names = new HashSet<SoftReference<Name>>();
15     // valueOf静态工厂方法
16     public static Name valueOf(String firstname, String lastname) {
17         Iterator<SoftReference<Name>> it = names.iterator();
18         while (it.hasNext()) {
19             SoftReference<Name> ref = it.next(); // 获得软引用
20             Name name = ref.get(); // 获得软引用所引用的Name对象
21             if (name != null && name.firstname.equals(firstname) && name.lastname.equals(lastname))
22                 return name;
23         }
24         // 如果在缓存中不存在Name对象,就创建该对象,并同时把它的软引用加入到实例缓存
25         Name name = new Name(firstname, lastname);
26         names.add(new SoftReference<Name>(name));
27         return name;
28     }
29     // 主方法
30     public static void main(String[] args) {
31         Name n1 = Name.valueOf("大大", "张");
32         Name n2 = Name.valueOf("大大", "张");
33         Name n3 = Name.valueOf("小小", "李");
34         System.out.println(n1);
35         System.out.println(n2);
36         System.out.println(n3);
37         System.out.println(n1 == n2); // 打印true
38     }
39 }

     扩展<不可变类>:例如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方法,来确保类的不可变。例如:

 1 public final class FinalClass {//不可变类
 2     private final int[] iArray;//private成员变量
 3     public FinalClass(int[] aArray) {//整型数组不是原始变量,需要用深度clone方法,保证类的不可变
 4         this.iArray = aArray.clone();
 5     }
 6     public String toString() {
 7         StringBuffer sb = new StringBuffer("Numbers are: ");
 8         for (int i = 0; i < iArray.length; i++) {
 9             sb.append(iArray[i] + " ");
10         }
11         return sb.toString();
12     }
13 }

  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(核心基础);服务提供者接口(可选)。例如代码:

 1 /*服务提供者框架描述*/
 2 // 服务接口
 3 public interface Service {
 4     // ...
 5 }
 6
 7 // 服务提供者接口
 8 public interface Provider {
 9     Service newService();
10 }
11
12 public class Services {
13     private Services() {
14     }
15     private static final Map<String, Provider> providers = new ConcurrentHashMap<String, Provider>();//存储服务提供者信息
16     public static final String DEFAULT_PROVIDER_NAME = "<def>";
17     // 提供者注册API
18     public static void registerDefaultProvider(Provider p) {//默认注册服务提供者
19         registerProvider(DEFAULT_PROVIDER_NAME, p);
20     }
21     public static void registerProvider(String name, Provider p) {//注册服务提供者
22         providers.put(name, p);
23     }
24     // 服务访问API(使用静态工厂方法,返回值为接口类型)
25     public static Service newInstance() {//默认实例化服务
26         return newInstance(DEFAULT_PROVIDER_NAME);
27     }
28     public static Service newInstance(String name) {//实例化服务
29         Provider p = providers.get(name);
30         if (p == null)//若不存在注册的服务提供者,则无法提供服务
31             throw new IllegalArgumentException("No provider registered with name:" + name);
32         return p.newService();//若存在注册的服务提供者,则可newService()提供服务
33     }
34 }

  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...

时间: 2024-12-06 08:39:22

<创建和销毁对象>经验法则——考虑用静态工厂方法代替公有构造方法的相关文章

经验法则:考虑用静态工厂方法代替公有构造方法

经验法则:考虑用静态工厂方法代替公有构造方法 一.引出静态工厂方法 对于java类而言,为了让使用者获取它自身的一个实例化对象,会有以下方法: 1.该类提供一个公有的构造方法.在这种情况下,程序可以通过多个“new 构造方法”语句来创建类的任意多个实例.但是每执行一条new语句,都会导致java虚拟机的堆区中产生一个新的对象. 2.该类提供一个公有的静态工厂方法(它只是一个简单的静态方法,返回的是类的一个实例,要区别于设计模式中的工厂方法模式).对于某些java平台库类或自己的工具类.参数化类,

【代码优化】考虑使用静态工厂方法取代构造器

静态工厂方法与设计模式中的工厂方法模式不同,和设计模式中的工厂方法模式不直接相应. 使用静态工厂方法比构造器的优势: 第一.静态工厂方法是有名称的,而构造器是通过參数推断的. 每一个静态工厂方法都有自己的名字,能够依据名称就能够推断它要做什么事情,而构造器是做不到的. 如:构造器BigInteger(int,int),返回BigInteger能够是素数,偶数等.而用名称BigInteger.probalePrime的静态工厂方法 就一目了然了,代码easy阅读. 第二.不必再每次调用时候都创建一

Effective Java 读书笔记(一):使用静态工厂方法代替构造器

这是Effective Java第2章提出的第一条建议: 考虑用静态工厂方法代替构造器 此处的静态工厂方法并不是设计模式,主要指static修饰的静态方法,关于static的说明可以参考之前的博文<java中final与static的使用场景总结>. 什么是静态工厂方法? 可以参考书中的例子(摘自JDK1.7 java.lang.Boolean) public final class Boolean implements java.io.Serializable, Comparable<

【代码优化】考虑使用静态工厂方法代替构造器

静态工厂方法与设计模式中的工厂方法模式不同,和设计模式中的工厂方法模式不直接对应. 使用静态工厂方法比构造器的优势: 第一.静态工厂方法是有名称的,而构造器是通过参数判断的. 每个静态工厂方法都有自己的名字,可以根据名称就可以判断它要做什么事情,而构造器是做不到的. 如:构造器BigInteger(int,int),返回BigInteger可以是素数,偶数等,而用名称BigInteger.probalePrime的静态工厂方法 就一目了然了,代码容易阅读. 第二.不必再每次调用时候都创建一个新的

Think in Java 静态工厂方法学习总结

静态工厂方法是一个返回类实例的静态方法.静态工厂方法的用于替代构造方法. 1.静态工厂方法的优点 如果一个构造方法的参数没有具体描述要返回的对象,那么使用一定意义名字的静态工厂方法使得该类更利于使用. 如果一个类有多个特征相同的构造方法,那么应该考虑用有特殊意义名字的静态工厂方法来代替构造方法. 静态工厂方法在调用的时候不要求一定要有类对象.对于一个非可变类,可以使用一个预先构造好的实例,或者实例已经缓存起来,避免创建重复对象. 静态工厂方法可以为重复的调用返回同一个对象.第一他可以使一个类确保

一、考虑使用静态工厂方法替代构造函数

1.何为静态工厂方法 静态工厂方法就是一个返回类实例的静态方法.比如Boolean的valueof方法: 1 public static final Boolean TRUE = new Boolean(true); 2 public static final Boolean FALSE = new Boolean(false); 3 4 public static Boolean valueOf(boolean b) { 5 return (b ? TRUE : FALSE); 6 } 2.为

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

为了获得一个类的实例,有两种办法1.类提供一个公有的构造器 2.类提供一个公有的静态工厂方法. 静态工厂方法的优势: 1.有名称. 慎重地选择方法名称能突出多个构造器的区别,例如使用BigInteger(int, int, Random)构造器,返回的BigInteger可能为素数,如果用 BigInteger.probalePrime(int, Random)静态工厂方法,显得更为清楚. 2.不必在每次调用的时候都创建一个新的对象. Boolean类的代码中有public static fin

Effective JAVA NO1考虑用静态工厂方法代替构造器

NO1.考虑用静态工厂方法代替构造器 静态工厂方法与构造器不同的第一大优势在于它们有名称: 静态工厂方法与构造器不同的第二大优势在于,不必在每次调用它们的时候都创建一个新对象: 第三大优势,它们可以返回原返回类型的任何子类型的对象: 第四大优势,在创建参数化类型实例的时候,它们使代码变得更加简洁: 缺点: 1.类如果含公有的或者受保护的构造器,就不能被子类化. 2.它们与其他的静态方法实际不上没有任何区别.

[Effective Java]考虑用静态工厂方法代替构造器

本文主要介绍如何使用静态工厂方法已经在那种场合来使用这种方式代替构造方法. 众所周知,对于类而言,我们为了获得一个类的实例对象,通常情况下会提供一个公有的(public) 的构造器.当然除了这种方法以外,我们还可以通过给类提供一个public的静态工厂方法(static factory method)的方式来完成,让它返回一个类的实例. 先看一个简单的Boolean的示例,这个示例将boolean基本类型值转换成一个Boolean对象的引用. public static Boolean valu