传统的方式获取一个类的实例,是经过提供一个 public
构造器。这里有技巧,每个程序员应该记住。一个类能够对外提供一个 public
的 静态工厂方法 ,该方法只是一个朴素的静态方法,不须要有太多复杂的逻辑,只须要返回该类的实例。java
这里经过 Boolean
(是原始类型 boolean
的包装类)举一个简单的例子:程序员
public static Boolean valueOf(boolean b) { return b ? Boolean.TRUE : Boolean.FALSE; }
这个方法,将一个 boolean
原始类型的值转换为 Boolean
对象引用。编程
值得注意的是,本条目中说的一个 静态工厂方法 不一样于 设计模式 的工厂模式1,一样的,本条目中描述的静态工厂方法在设计模式找不到对应的模式。设计模式
一个类能够对外提供静态工厂方法,来取代 public
的构造器,或者与 public
构造器并存,对外提供两种方式获取实例。用静态工厂方法取代 public
构造器,既有优点也有缺点。api
优点体如今下面几点:缓存
静态工厂方法与构造器比起来,它能够随意命名,而非固定的与类的名字保持一致。框架
若是一个构造器的参数自己,不能对将要返回的对象具备准确的描述。此时使用一个具备准确描述名字的静态工厂方法是一个不错的选择。它能够经过名字对将要返回的对象,进行准确的描述。使得使用的人能够见名知意。dom
举个例子,BigInteger(int, int, Random)
构造器,返回的值多是素数,所以,这里其实能够有更好的表达,经过使用一个静态工厂方法 BigInteger.probablePrime(int, int, Random)
该方法于 1.4
被加入。工具
一个类只能有一个指定方法签名2的构造器。一般咱们都知道如何绕过这限制,经过交换参数列表的顺序,获得不一样的方法签名。可是这是一个很糟糕的主意。这给使用 api
的开发人员形成负担,他们将很难记住哪个方法签名对应哪个对象的返回,最后每每都是错误的调用。阅读代码的人一样也蒙圈,若是没有相应的文档告诉他们,不一样的方法签名对应的构造器返回的对象是什么。性能
静态工厂方法不受上述限制,不须要去经过交换参数顺序来彼此区分,由于它们能够拥有本身的名字。所以,当一个类的多个构造器,方法签名差很少,仅仅参数顺序不同的时候,考虑使用静态工厂方法,仔细的为静态工厂方法取名字,以区分它们之间的不一样。
静态工厂方法与构造器比起来,没必要每次调用都建立新的对象
这容许不可变的类使用预建立的实例3,或者在建立实例的时候,将实例缓存起来4,重复的使用该实例,避免建立不重要的重复对象。Boolean.valueOf(boolean)
方法使用该技巧,它永远都不会建立对象,返回的都是预建立好的对象。这个技巧有点相似于设计模式中的享元模式5 。它能大幅度的提升性能,特别是在特定场景下:一些对象建立的时候,须要花费很大性能,而且这些对象常常被使用。
该特性容许类在任什么时候候,对其产生多少实例具备精确的控制。用这种技巧的类,被称为实例受控的类。这里有几个使用实例受控类的理由。实例受控容许一个类保证它是一个单例或者不可实例化的类。一样的,实例受控,也能够保证不可变类不会存在两个相等的实例。
静态工厂方法与构造器比起来,能够返回该类的任意子类型的对象
具备足够的灵活性,在获取对象的时候。能够返回协变类型,在方法中使用该类的子类构造器建立对象,而后返回,同时对外不须要暴露这些子类对象,适合于面向接口编程。在 1.8
以前,接口中不能有静态方法,针对状况的惯例作法是,针对名为 Type
类型的接口,它的静态方法被放在一个不可实例化的类 Types
中6,典型的例子是 java.util.Collections
类,它经过静态工厂方法,能够构建返回各式各样的集合:同步集合、不可修改的集合等等,可是返回的时候都是返回接口类型,具体实现类型不对外公开。
// Collections 中非公开类,同步map private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable { private static final long serialVersionUID = 1978198479659022715L; private final Map<K,V> m; // Backing Map final Object mutex; // Object on which to synchronize SynchronizedMap(Map<K,V> m) { this.m = Objects.requireNonNull(m); mutex = this; } SynchronizedMap(Map<K,V> m, Object mutex) { this.m = m; this.mutex = mutex; } // Collections 的静态工厂方法,返回接口接口map,可是内部是返回同步Map类型。 public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); }
起到简化 api
的做用,这种简化不只仅是 api
体积上的减小,减小对外暴露的类的数量,也给程序员直观上的简洁,他们只须要记住接口类型便可,无需记住具体的实现类型。这样也督促程序员面向接口编程,这是一种好的习惯。
1.8
之后,接口能够写静态方法。所以,再也不须要按照之前的习惯,为接口,写一个对应的类,直接在接口中写静态工厂方法。可是关于返回的实现类型,依然应该继续隐藏,使用非公开类实现。
静态工厂方法与构造器比起来,能够随着传入参数的不一样,返回不一样的对象。
能够返回任何子类型,和第三条同样,可是能够继续添加控制,根据传入参数的不一样,返回不一样的对象。
一个例子,EnumSet
,是一个不可实例的类,只有一个静态工厂方法,没有构造器。可是在 openJDK
的实现中,具体的返回类型,是根据实际枚举的个数决定的,若是小于等于 64
,则返回 RegularEnumSet
类型,不然返回 JumboEnumSet
类型。这两个类型对于使用者来讲,都是不可见的。若是 RegularEnumSet
类型,在未来再也不为小的枚举类型提供优点,即使在将来的发行版删除 RegularEnumSet
也不会有什么影响。一样的,将来也能够继续添加第三个、第四个版本,对使用者也是无感的。使用者不须要关心具体返回的是什么对象,他们只知道,返回的对象都是 EnumSet
类型。
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"); // 若是小于等于 64 ,则返回 RegularEnumSet if (universe.length <= 64) return new RegularEnumSet<>(elementType, universe); else return new JumboEnumSet<>(elementType, universe); } // RegularEnumSet 类是 EnumSet的子类 class RegularEnumSet<E extends Enum<E>> extends EnumSet<E> { ... }
静态工厂方法与构造器比起来,在编写静态工厂方法的时候,具体的类型能够不存在。
这句话仍是面向接口的优点,意思就是,咱们在编写方法的时候,能够没有任何实现类,在使用的使用,先注册实现类,而后再返回实现类,这使得扩展变得很容易。咱们只是在维护一个框架,一个接口,具体的实现,咱们不给出,谁均可以实现,而后注册使用。这也是 服务者框架 的含义。JDBC
就是这么一个思想的服务者框架。关于服务者框架看这里
缺点:
静态工厂方法与构造器比起来,没有 public
或者 protected
修饰的构造器,没法实现继承
例如,上面提到的 Collections
,就没法被继承,咱们就不能继承其中的任何一个便利的实现。这或许,也是一种对使用组合而非继承的鼓励。
静态工厂方法与构造器比起来,它们不容易被程序员所知晓
在文档中,它们不像构造器那么显眼,在上面单独的列出来,基于这个缘由,要想知道如何实例化一个类,使用静态工厂方法比使用构造器相比,前者是比较困难的,由于文档中,静态工厂方法和其余静态方法没啥区别,没有作特殊处理。java
文档工具或许在将来会注意到这个问题,对静态工厂方法多给予一些关注。
同时,咱们能够在文档中对静态工厂方法的名字作一些特殊处理,遵照常见的命名规范,来减小这个问题,好比像下面提到的几个规范:
from
,类型转换方法,根据一个单一传入的参数,返回一个对应的类型,好比:Date d = Date.from(instant);
。of
, 聚合方法,接受多个参数,返回一个合并它们的实例。好比:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
valueOf
,比 from 和 of 更加详细 。好比:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
instance or getInstance
,获取实例方法,根据传入的参数,返回实例,可是不保证返回的实例彻底同样,根据传入的参数不一样而不一样。好比:StackWalker luke = StackWalker.getInstance(options);
。create or newInstance
,获取新的实例,每次都返回新建立的实例。好比:Object newArray = Array.newInstance(classObject, arrayLen);
。getType
,和 getInstance
相似,用于返回的类型,不是静态工厂方法所在的类,而是其余类型。好比:FileStore fs = Files.getFileStore(path);
。newType
和 newInstance
类型,一样用于返回的类型,不是静态工厂方法所在的类,而是其余类型。好比:BufferedReader br = Files.newBufferedReader(path);
。type
,getType and newType
的简化版。好比:List<Complaint> litany = Collections.list(legacyLitany);
。总结下,静态工厂方法和 public
构造器都有本身的优势,了解它们各自的优势是有帮助的。大部分状况下,静态工厂方法更占优点,因此,咱们应该避免第一反应就使用构造器,而是先考虑下静态工厂方法。
这里的静态工厂方法,和设计模式中的静态工厂模式,很类似,可是设计模式中的静态工厂模式,它是对外隐藏对象的实现细节,经过一个工厂,根据不一样的输入,产生不一样的输出。本条目中的工厂,只会产生特定的输出,即本身的实例。两者仍是不一样的。↩
方法签名,指的是方法名字,以及方法参数列表,包括方法参数的顺序。↩
相似于单例模式的饿汉式↩
相似于单例模式的懒汉式↩
享元模式,23种设计模式中的一种, 它针对每一种内部状态仅提供一个对象,设置不一样的外部状态,产生多种不一样的形态,可是其内部状态对象只有一个,达到对象复用的目的。↩
这里的 Tyle
类型,不是泛型的 Type
,只是一种代指,跟 xxx
一个意思,表示 1.7
之前,对于面向接口编程的时候,想要返回协变类型,常规的作法,是写一个不可实现类 ,类的名字,就是接口的名字,多加一个 s
。↩