EffectiveJava_创建和销毁对象_静态工厂方法的使用

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

在基本类型的包装类中,如Boolean存在静态工厂方法:

public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE : Boolean.FALSE;    
}

考虑静态工厂方法的优势:

  • 静态工厂方法有自己的名称,而构造器则只能与类名相同。

对于有多个构造器的类,他们的不同只在于参数列表,对于这样的构造器用户经常不知道该调用哪个,从而会造成很多的错误。而静态工厂方法的好处是可以有自己的名称,用户从名称上就可以读懂这个构造器是构造什么样的实例。

  • 静态工厂方法可避免创建不必要的重复对象。

如上面提到的Boolean类,它从不创建对象,这种方法类似于享元模式。

public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);

    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

    /**
     * Returns a {@code Boolean} instance representing the specified
     * {@code boolean} value.  If the specified {@code boolean} value
     * is {@code true}, this method returns {@code Boolean.TRUE};
     * if it is {@code false}, this method returns {@code Boolean.FALSE}.
     * If a new {@code Boolean} instance is not required, this method
     * should generally be used in preference to the constructor
     * {@link #Boolean(boolean)}, as this method is likely to yield
     * significantly better space and time performance.
     *
     * @param  b a boolean value.
     * @return a {@code Boolean} instance representing {@code b}.
     * @since  1.4
     */
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }

    /**
     * Returns a {@code Boolean} with a value represented by the
     * specified string.  The {@code Boolean} returned represents a
     * true value if the string argument is not {@code null}
     * and is equal, ignoring case, to the string {@code "true"}.
     *
     * @param   s   a string.
     * @return  the {@code Boolean} value represented by the string.
     */
    public static Boolean valueOf(String s) {
        return parseBoolean(s) ? TRUE : FALSE;
    }

}
  • 静态工厂方法可以返回原返回类型的任何子类型的对象,而构造器只能构造本类型对象。

这是一种灵活性的应用,API可以返回对象,同时又不会使对象的类变成公有的。例如 java.util.Collections 提供了不同的静态工厂方法,返回对象的类都是非公有的,例如不可修改的集合、同步集合等等,这样的接口有32个。

对于这样做的好处,书中是这么写的:“现在的Collections Framework API比导出32个独立公有类的那种实现方式要小得多,每种便利实现都对应一个类。这不仅仅是指API数量上的减少,也是概念意义上的减少。”个人对这句话的理解是:每一个公有类对应着相应的文档供用户使用时阅读,但是对于不可变集合、同步集合这种只是在性质上发生改变而内部API等并无变化,操作上与普通集合一致的类并没有必要设计为独立的公有类。(用户只需了解它是不可变的,并不关心内部实现。)

public class Collections {
    // Suppresses default constructor, ensuring non-instantiability.
    //私有构造器,意味着该类不能实例化。
    private Collections() {
    }
    public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) {
        return new UnmodifiableCollection<>(c);
    }

    /**
     * @serial include
     */
    //其中的一种便利实现,不可变的集合
    //default的可见性,意味着包内可见。如果在包外尝试实例化,会编译报错。
    static class UnmodifiableCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 1820017752578914078L;

        final Collection<? extends E> c;

        UnmodifiableCollection(Collection<? extends E> c) {
            if (c==null)
                throw new NullPointerException();
            this.c = c;
        }

        public int size()                   {return c.size();}
        public boolean isEmpty()            {return c.isEmpty();}
        public boolean contains(Object o)   {return c.contains(o);}
        public Object[] toArray()           {return c.toArray();}
        public <T> T[] toArray(T[] a)       {return c.toArray(a);}
        public String toString()            {return c.toString();}

        public Iterator<E> iterator() {
            return new Iterator<E>() {
                private final Iterator<? extends E> i = c.iterator();

                public boolean hasNext() {return i.hasNext();}
                public E next()          {return i.next();}
                public void remove() {
                    throw new UnsupportedOperationException();
                }
                @Override
                public void forEachRemaining(Consumer<? super E> action) {
                    // Use backing collection version
                    i.forEachRemaining(action);
                }
            };
        }

        public boolean add(E e) {
            throw new UnsupportedOperationException();
        }
        public boolean remove(Object o) {
            throw new UnsupportedOperationException();
        }

      ...
}

说到这里好像并没有对“返回子类型的对象”有什么优势做出解释,更像是再说可以“减少公有类”的好处。个人理解:将静态工厂方法的返回类型设计成父类,只要返回对象的类型是子类,就是允许的。这符合里氏代换原则,我们在选择返回对象类型的时候就有更大的灵活性。另一方面,对于用户来说,也不关心返回的是什么类型的,完全可以又静态工厂方法的参数去决定使用那种更具有优势或者实际需要的子类。

这里我们将提到服务提供者架构(Service Provider Framework),以数据库连接操作作为例子可能更好理解。(以下是部分关键类类图)

JDBC部分类图

我们都知道JDBC统一、规范了Java对不同数据的操作。也就是服务定义接口(java.sql.Connection)、服务提供者接口(java.sql.Driver)和提供服务者注册类(java.sql.DriverManager)是由java统一制定的,而不同提供商只需要遵循这样的同一的规定去实现服务具体实现类(com.mysql.cj.jdbc.ConnectionImp)和提供服务者(com.mysql.cj.jdbc.NonRegisteringDriver)实现类。这样做的好处是:我们使用不同的数据库进行链接是只需要注册不同的驱动器,也就是Driver,就可以拿到正确的是数据库连接。可以看到在NonRegisteringDriver中connect方法返回的是超类Connection,无论是mysql或是oracle都是适用的。

我们获得Connection的操作通常是这样的:

Class.forName("com.mysql.jdbc.Driver");
Connection connection= DriverManager.getConnection("jdbc:mysql:///mydatabase", "root", "root");

我们可以看一下com.mysql.jdbc.Driver中的代码:

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }

    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can't register driver!");
        }
    }
}

很明显当我们调用Class.forName(...)这个方法到时候,就已经触发Driver中的静态代码块,把Driver加载进入DriverManager中了。在这里实际上在DriverManager的内部存在的是这样一个属性:

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

我们会发现这里的注册驱动器数组并不是Driver类型的,而是DriverInfo类型。查看以下源码(在DriverManager的同一文件下):

/*
 * Wrapper class for registered Drivers in order to not expose Driver.equals()
 * to avoid the capture of the Driver it being compared to as it might not
 * normally have access.
 */
//注册驱动程序的包装类,以便不公开Driver.Equals(),以避免捕获与之比较的驱动程序,因为它通常不具有访问权限。
class DriverInfo {

    final Driver driver;
    DriverAction da;
    DriverInfo(Driver driver, DriverAction action) {
        this.driver = driver;
        da = action;
    }

    @Override
    public boolean equals(Object other) {
        return (other instanceof DriverInfo)
                && this.driver == ((DriverInfo) other).driver;
    }

    @Override
    public int hashCode() {
        return driver.hashCode();
    }

    @Override
    public String toString() {
        return ("driver[className="  + driver + "]");
    }

    DriverAction action() {
        return da;
    }
}

这个类采用了复合的方式来扩展了Driver类,增加了DriverAction类,目前个人的理解是是用于注销驱动器的时候使用的。

而我们可以关注的一点是,注释中提到的equals方法。这在《Effective Java》第8条规则中将会被提到。

public static synchronized void deregisterDriver(Driver driver)
        throws SQLException {

    ...

                 // If a DriverAction was specified, Call it to notify the
                 // driver that it has been deregistered
                //如果指定了DriverAction,则调用它以通知驱动程序它已被注销。 
                 if(di.action() != null) {
                     di.action().deregister();
                 }
                 registeredDrivers.remove(aDriver);
            } else {
                // If the caller does not have permission to load the driver then
                // throw a SecurityException.
                //如果调用方没有加载驱动程序的权限,则抛出SecurityException。 
                throw new SecurityException();
            }
        } else {
            println("    couldn't find driver to unload");
        }
    }
  • 在创建参数化类型的时候,静态工厂方法可以使代码更加简洁。
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();

这个例子在jdk1.6以前是成立的。目前个人使用的jdk1.7已经可以写成:

Map<String ,List<String>> m = new HashMap<>();

关于静态工厂方法的缺点:

  • 类如果不包含公有的或者受保护的构造器,就不能被子类化。

原因就是子类化的时候都会先调用父类的构造器。

  • 静态工厂方法与其他静态方法没有区别。

在API文档中,它们没有像其他构造器那样在API文档中明确标识出来,因此对于提供静态工厂方法而不是构造器的类,要查明如何实例化一个类,这是非常困难的。解决这个问题的一个方法就是遵守标准的命名习惯,如:

valueOf、getInstance、newInstance、getType、newTpye等。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章