1. 【创建与销毁对象】考虑用静态工厂方法代替构造器

来源:互联网 发布:深证指数数据 编辑:程序博客网 时间:2024/06/05 09:38

本文内容为《Effective Java》的读书笔记。


对于某一个类,要获取其对象,通常是使用共有的构造方法new一个。其实还有另一种方法也是经常用到的,那就是静态工厂方法

(注:此处的静态工厂方法不同于工厂模式)

举个例子:

    public static Integer valueOf(int i) {        if (i >= IntegerCache.low && i <= IntegerCache.high)            return IntegerCache.cache[i + (-IntegerCache.low)];        return new Integer(i);    }

这是java.lang.Integer的一个方法,通过给定一个int型的值得到相应的Integer对象。


相对于构造函数来说,静态工厂方法有什么好处呢?

1. 有明确的方法名。

直接上例子,例如构造器BigInteger(int, int, Random)返回的BigInteger可能为素数,如果用BigInteger.probablePrime的静态工厂方法表示则更清楚。

有的构造器有多个参数,如果单纯通过参数类型和顺序创建对象,经常记不住改用哪个构造器,这时候静态工厂方法便可发挥作用了。

2. 当不必每次都创建新的对象的时候。

您肯定首先就能想到单例模式,没错,通过静态工厂方法每次都获取特定的对象实例。

还有别的情况,比如刚才的valueOf方法,仔细看代码发现实际上返回的对象是做了缓存的,也就是从IntegerCache.low到IntegerCache.high之间的数字是缓存在IntegerCache中的,而IntegerCache中维护了一个静态的Integer数组cache[],比如当调用方法valueOf(123)的时候,会查看cache[]中是否有值为123的Integer对象,如果没有,那么创建一个Integer(123)返回,并保存到cache[]中;如果有,那么直接取出该对象返回。因此,两次valueOf(123)返回的对象是同一个,这样能有效减少对象个数。默认状态下,Integer是对-128到127的数字进行缓存的。

聪明的你可能接着就想到了Boolean.valueOf(boolean),没错,返回的对象始终只有那两个。

3. 可以返回原返回类型的子类型的对象。

这种灵活性的应用是,API可以返回对象,同时又不会使对象的类变成共有的。以这种方式隐藏实现类会是API变得非常简洁。

似懂非懂?那就直接看看java.util.Collections的源码吧,这个类是不能被实例化的,其内定义了数十个不同类型的集合(不可修改集合、空集合、同步集合等),同时有响应的静态工厂方法来获取这些集合。随便点开一个方法:

    public static <T> Set<T> unmodifiableSet(Set<? extends T> s) {        return new UnmodifiableSet<>(s);    }
返回的对象所属的类UnmodifiableSet就是在Collections类中定义的,但方法返回值是Set类型,也就是UnmodifiableSet的接口类型。可见,这种方式适用于基于接口的框架。

通过这种方式,这数十个不同类型的集合无需再定义独立的共有类了,这不仅仅是API数量的减少,也是概念意义上的减少。这时候客户端被要求通过接口来引用创建的对象,而不是具体的实现类,这是一种良好的习惯。

再举一个例子:

    public static <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType) {        Enum<?>[] universe = getUniverse(elementType);        if (universe == null)            throw new ClassCastException(elementType + " not an enum");        if (universe.length <= 64)            return new RegularEnumSet<>(elementType, universe);        else            return new JumboEnumSet<>(elementType, universe);    }
这是EnumSet类中的一个静态工厂方法,用于根据元素类型产生一个空EnumSet,并且根据不同的元素个数,创建不同的具体实体类对象(当元素个数少于64个的时候返回RegularEnumSet对象,否则返回JumboEnumSet),而使用者完全不用关心对象是什么具体类型的,只要能满足EnumSet的功能就OK。

更有些时候,在编写静态工厂方法所在的类的时候,具体的实现类还不存在呢。比如JDBC的API,不同的driver对应不同的数据库操作方式,但是我们不用关心,只知道增删改查即可,具体MySQL还是Oracle还是又新出的数据库产品它的增删改查操作的实现细节,那是数据库产品厂商提供的。这就是服务提供者框架的理念。

4. 在创建参数化实例的时候,它们使代码变得更加简洁。
即使是Java初学者,应该也写过类似下边的代码:

    Map<String, List<String>> map = new HashMap<String, List<String>>();
这还算好的,如果类型更加复杂,那么这句话会更长,怪不得总是被吐槽Java语言冗长。

假设HashMap提供了这样的方法:

    public static <K, V> HashMap<K, V> newInstance() {        return new HashMap<K, V>();    }
那么就可以用如下代码来创建对象了:
    Map<String, List<String>> map = HashMap.newInstance();

不过现在的Java版本已经具有类型推断的能力了,后边的<>中可以空着。


当然,静态工厂方法也存在一些不足:

1. 有些含有静态工厂方法的类,通常不再提供共有的或受保护的构造方法,因此也就不能被子类化了。

比如刚才提到的Collections中的实体类,他们就不能被子类化了。不过这样也是因祸得福,鼓励开发人员更多使用组合而不是继承。

2. 它们与其他的静态方法实际上并没有任何区别。

比如Java doc中会把构造方法单独拿出来,你一下就知道是可以创建对象的,但是静态工厂方法就不太容易找喽。


好吧,感觉这两点不足相对于它的优势来说,实在不足挂齿,在我们的开发过程中,如果能更加自如的应用静态工厂方法,会让代码的结构更佳、逼格更高哟。



0 0
原创粉丝点击