目录html
传统来说,为了使客户端可以获取它自身的一个实例,最传统的方法就是提供一个公有的构造器。像下面这样java
public class Apple { public Apple(){} public static void main(String[] args) { Apple apple = new Apple(); } }
还有另一种方式,为类提供静态工厂方法,它只是返回一个类的静态方法,下面是它的构造设计模式
public static Boolean valueOf(boolean b){ return b ? Boolean.TRUE : Boolean.FALSE; }
上面代码定义了一个valueof(boolean b)
的静态方法,此方法的返回值是一个对常量的的引用,为何说是常量?跟踪代码进去发现,TRUE是使用static final 修饰的。Boolean.TRUE
实际指向的就是一个Boolean
类的带有boolean类型构造函数。数组
public static final Boolean TRUE = new Boolean(true);
注意:此静态工厂方法与设计模式中的工厂方法模式不一样,本条目中所指的静态方法并不直接对应设计模式中的工厂方法。缓存
那么咱们为苹果增长一个属性appleSize,并分别提供静态的构造函数bigApple和smallApple,并提供一个方法来判断传进来的值,若是appleSize > 5的话就是大苹果,不然都是小苹果,改造后的代码以下安全
public class Apple { static int appleSize; public static final Apple bigApple = new Apple(5); public static final Apple smallApple = new Apple(2); public Apple(){} public Apple(int appleSize){ this.appleSize = appleSize; } } public class testApple { // 判断苹果的大小,大于5的都按5斤算,小于5的都按2斤算 static Apple judgeAppleSize(int size){ return size > 5 ? Apple.bigApple : Apple.smallApple; } public static void main(String[] args) { // Apple apple = new Apple(); judgeAppleSize(6); } }
那么,你可否根据上述两个代码思考一下静态工厂方法和公有构造器之间孰优孰劣呢?多线程
众所周知,构造器的声明必须与类名相同,构造方法顾名思义就是构造此类的方法,也就是经过构造方法可以得到这个类对象的引用,因此构造方法必须与类名相同。不知道你有没有碰见过相似的状况,看下面一个例子并发
BigInteger.javaapp
public BigInteger(int bitLength, int certainty, Random rnd) { ... prime = (bitLength < SMALL_PRIME_THRESHOLD ? smallPrime(bitLength, certainty, rnd) : largePrime(bitLength, certainty, rnd)); }
若是只是给BigInteger 传递了三个参数,可是你并不知道它的内部代码是怎样的,你可能还会查找到对应的源码来仔细研究,也就是说BigInteger 的名称和内部实现没有太大的关系。框架
若是用静态工厂方法呢?能够看下面一个例子
仍是BigInteger.java
public static BigInteger probablePrime(int bitLength, Random rnd) { if (bitLength < 2) throw new ArithmeticException("bitLength < 2"); return (bitLength < SMALL_PRIME_THRESHOLD ? smallPrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd) : largePrime(bitLength, DEFAULT_PRIME_CERTAINTY, rnd)); } private static BigInteger smallPrime(int bitLength, int certainty, Random rnd) {...} private static BigInteger largePrime(int bitLength, int certainty, Random rnd) {...}
一样是内部调用,静态工厂方法probablePrime
是你本身定义的名称,你是否从该名称看出来某些关于内部实现的东西呢?是否是就比调用其公有的构造函数要更加明确?
一个类只能有一个带有指定签名的构造器,若是提供两个构造器,他们只是在参数类型的顺序上有所不一样,你是否是也会有一头雾水不知道该调用哪一个构造器的感受?事实上这并非一个好的注意,面对这样的API,用户也记不住调用哪一个构造器,结果一般会调用错误的构造器。
因为静态方法有名称,因此在实现过程当中,因此它们不受上述限制,当一个类须要多个带有相同签名的构造器时,就用静态工厂方法替代构造器,并仔细的选取静态工厂的名称以便突出其主要功能。
咱们都知道,每一次调用一个构造函数都至关因而从新建立了一个该对象的实例,这使得不可变类可使用预先构建好的示例,或者将构建好的实例缓存起来,重复利用,从而避免建立没必要要的对象。Boolean.valueOf(boolean)方法说明了这一点,它历来不用建立对象,这种方法相似于享元模式,简单介绍一下:
https://www.runoob.com/design-pattern/flyweight-pattern.html
言归正传,静态工厂方法不会从新建立对象,静态工厂方法每次都返回相同的对象,这样有助于控制哪些类的实例应该存在。这种类称为实例受控的类,咱们以单例模式为例,来看一下实例受控的类的主要用法:
public class Singleton { // 懒汉式 private static Singleton INSTANCE; private Singleton(){} public static Singleton newInstance(){ if(INSTANCE == null){ INSTANCE = new Singleton(); } return INSTANCE; } }
这部分代码是一个典型的懒汉式实现,对外部只开放newInstance
方法,并把构造函数私有化,也就是说你不能经过构造函数new
出Singleton的实例,必须经过Singleton.newInstance()
来建立Singleton的实例,每次判断INSTANCE
是否为null,若是是null,则建立并返回 new Singleton()
的引用,不然,只是返回以前建立出来的Singleton 的引用。
这个Singleton类,就是实例受控的类,你不能无限制的建立Singletion的实例,由于Singleton是一种单例实现。固然,这种方式不是线程安全的,在多个线程并发访问时,你并不能保证单例的有效性,也就是说在多线程环境下你不能保证Singleton只有一个。那么如何保证呢?请往下读,下文会给你答案。
编写实例受控的类有几个缘由:
Singleton
Singleton是指仅仅被实例化一次的类。那么如何编写一个安全的Singleton呢?咱们来对上面的懒汉式进行部分改造
public class Singleton { // 饿汉式 private static final Singleton INSTANCE = new Singleton(); private Singleton(){} public static Singleton newInstance(){ return INSTANCE; } }
使用static final
强制了INSTANCE
的引用对象为不可更改的,也就是说,你不能再把INSTANCE对象的引用指向其余new Singleton()对象,这种方式就是在类装载的时候就完成实例化。避免了线程同步问题(其余单例的状况咱们在后面的章节中讨论)。
其实咱们上面的代码一直在确保此规定,那就是经过私有化构造函数,确保此类不能被实例化。你也能够经过使用下面这种方式来避免类的实例化
public class UtilityClass { private UtilityClass(){ throw new AssertionError(); } }
AssertionError()不是必须的,可是它能够避免不当心在类的内部调用构造器。
实例受控的类确保不会存在两个相等的实例,当且仅当 a==b时,a.equals(b)才为true,这是享元模式的基础(具体咱们在后面的章节中讨论)。
静态工厂方法与构造器不一样的第三大优点在于,它们能够返回原返回类型的任何子类型的对象。这样咱们就在选择返回对象的类时就有了更大的灵活性。Collections
和Arrays
工具类保证了这一点
Collections.java
public static <T> Collection<T> unmodifiableCollection(Collection<? extends T> c) { return new UnmodifiableCollection<>(c); } static class UnmodifiableCollection<E> implements Collection<E>, Serializable { ... UnmodifiableCollection(Collection<? extends E> c) { if (c==null) throw new NullPointerException(); this.c = c; } ... }
这是Collections.java 中的代码片断,静态方法unmodifiableCollection
返回一个新的UnmodifiableCollection,调用它的静态方法建立UnmodifiableCollection的对象,因为UnmodifiableCollection继承于Collection,也就是说静态方法unmodifiableCollection实际上是返回了一个子类的对象。
静态工厂的第四大优点在于,所返回的对象的类能够随着每次调用而发生变化,这取决于静态工厂方法的参数值。只要是已声明的返回类型的子类型,都是容许的。返回对象的类也可能随着发行版本的不一样而不一样。
EnumSet (详见第36条)没有公有的构造器,只有静态工厂方法。在OpenJdk实现中,它们返回两种子类之一的一个实例,具体则取决于底层枚举类型的大小:若是它的元素有6 4个或者更少,就像大多数枚举类型同样,静态工厂方法就会返回一个RegularEnumSet实例,用单个long进行支持;若是枚举类型有65个或者更多元素,工厂就返回JumboEnumSet实例,用一个long数组进行支持。
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); }
静态工厂的第五大优点在于,方法返回对象所属的类,在编写包含该静态工厂方法类时能够不存在。
这里直接从 这种静态工厂方法最典型的实现--服务提供者框架 来探讨。
服务提供者框架包含四大组件:(概念不太好理解,能够直接先看下面的例子讲解,而后回过头来再看概念)
服务接口:这是服务提供者要去实现的接口
服务提供者接口:生成服务接口实例的工厂对象(就是用来生成服务接口的)(可选)
提供者注册API:服务者 提供服务者自身的实现
服务访问API:根据客户端指定的某种条件去实现对应的服务提供者
//四大组成之一:服务接口 public interface LoginService {//这是一个登陆服务 public void login(); } //四大组成之二:服务提供者接口 public interface Provider {//登陆服务的提供者。通俗点说就是:经过这个newLoginService()能够得到一个服务。 public LoginService newLoginService(); } /** * 这是一个服务管理器,里面包含了四大组成中的三和四 * 解释:经过注册将 服务提供者 加入map,而后经过一个静态工厂方法 getService(String name) 返回不一样的服务。 */ public class ServiceManager { private static final Map<String, Provider> providers = new HashMap<String, Provider>();//map,保存了注册的服务 private ServiceManager() { } //四大组成之三:提供者注册API (其实很简单,就是注册一下服务提供者) public static void registerProvider(String name, Provider provider) { providers.put(name, provider); } //四大组成之四:服务访问API (客户端只须要传递一个name参数,系统会去匹配服务提供者,而后提供服务) (静态工厂方法) public static LoginService getService(String name) { Provider provider = providers.get(name); if (provider == null) { throw new IllegalArgumentException("No provider registered with name=" + name); } return provider.newLoginService(); } }
也能够参考这篇文章进一步理解:JAVA 服务提供者框架介绍
上面提到了一些静态工厂方法的优势,那么任何事情都有利弊,静态工厂方法主要缺点在于,类若是不含公有的或者受保护的构造器,就不能被子类化。例如,要想将Collections Framework中任何便利的实现类子类化,这是不可能的。
静态工厂方法最终也是调用该类的构造方法,若是没有该类的构造方法,静态工厂的方法也就没有意义,也就是说,静态工厂方法实际上是构造方法的一层封装和外观,其实最终仍是调用的类的构造方法。
在API文档中,它们没有像构造器那样在API文档中被标明,所以,对于提供了静态工厂方法而不是构造器的类来讲,要想查明如何实例化一个类是很是困难的。下面提供了一些静态工厂方法的惯用名称。这里只列出来了其中的一小部分
Date d = Date.form(instant);
Set<Rank> faceCards = EnumSet.of(JACK,QUEEN,KING);
BigInteger prime = BigInteger.valueof(Integer.MAX_VALUE);
StackWalker luke = StackWalker.getInstance(options);
Object newArray = Array.newInstance(classObject,arrayLen);
FileStore fs = Files.getFileStore(path);
BufferedReader br = Files.newBufferedReader(path);
List<Complaint> litany = Collections.list(legacyLitancy);
简而言之,静态工厂方法和公有构造器都各有用处,咱们须要理解它们各自的长处。静态工厂常常更加合适,所以切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。
公众号提供 优质Java资料 以及CSDN免费下载 权限,欢迎你关注我