55面试教程网 高效Java(第三版) Effective Java
高效Java(第三版) Effective Java
考虑使用静态工厂方法替代构造方法当构造方法参数过多时使用builder模式使用私有构造方法或枚类实现Singleton属性使用私有构造方法执行非实例化使用依赖注入取代硬连接资源避免创建不必要的对象消除过期的对象引用避免使用Finalizer和Cleaner机制使用try-with-resources语句替代try-finally语句重写equals方法时遵守通用约定重写equals方法时同时也要重写hashcode方法始终重写 toString 方法谨慎地重写 clone 方法考虑实现Comparable接口使类和成员的可访问性最小化在公共类中使用访问方法而不是公共属性最小化可变性组合优于继承如果使用继承则设计,并文档说明,否则不该使用接口优于抽象类为后代设计接口接口仅用来定义类型优先使用类层次而不是标签类优先考虑静态成员类将源文件限制为单个顶级类不要使用原始类型消除非检查警告列表优于数组优先考虑泛型优先使用泛型方法使用限定通配符来增加API的灵活性合理地结合泛型和可变参数优先考虑类型安全的异构容器使用枚举类型替代整型常量使用实例属性替代序数使用EnumSet替代位属性使用EnumMap替代序数索引使用接口模拟可扩展的枚举注解优于命名模式始终使用Override注解使用标记接口定义类型lambda表达式优于匿名类方法引用优于lambda表达式优先使用标准的函数式接口明智审慎地使用Stream优先考虑流中无副作用的函数优先使用Collection而不是Stream来作为方法的返回类型谨慎使用流并行检查参数有效性必要时进行防御性拷贝仔细设计方法签名明智而审慎地使用重载明智而审慎地使用可变参数返回空的数组或集合不要返回null明智而审慎地返回Optional为所有已公开的API元素编写文档注释最小化局部变量的作用域for-each循环优于传统for循环熟悉并使用Java类库需要精确的结果时避免使用float和double类型基本类型优于装箱的基本类型当有其他更合适的类型时就不用字符串注意字符串连接的性能通过对象的接口引用对象接口优于反射明智谨慎地使用本地方法明智谨慎地进行优化遵守普遍接受的命名约定仅在发生异常的条件下使用异常对可恢复条件使用检查异常,对编程错误使用运行时异常避免不必要地使用检查异常赞成使用标准异常抛出合乎于抽象的异常文档化每个方法抛出的所有异常在详细信息中包含失败捕获信息争取保持失败原子性同步访问共享的可变数据避免过度同步EXECUTORS, TASKS, STREAMS 优于线程优先使用并发实用程序替代wait和notify线程安全文档化明智谨慎地使用延迟初始化不要依赖线程调度器其他替代方式优于Java本身序列化非常谨慎地实现SERIALIZABLE接口考虑使用自定义序列化形式防御性地编写READOBJECT方法对于实例控制,枚举类型优于READRESOLVE考虑序列化代理替代序列化实例

考虑使用静态工厂方法替代构造方法

Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。

条目1. 考虑使用静态工厂方法替代构造方法

一个类允许客户端获取其实例的传统方式是提供一个公共构造方法。 其实还有另一种技术应该成为每个程序员工具箱的一部分。 一个类可以提供一个公共静态工厂方法,它只是一个返回类实例的静态方法。 下面是一个Boolean简单的例子(boolean基本类型的包装类)。 此方法将boolean基本类型转换为Boolean对象引用:

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

注意,静态工厂方法与设计模式中的工厂方法模式不同[Gamma95]。本条目中描述的静态工厂方法在设计模式中没有直接的等价。

类可以为其客户端提供静态工厂方法,而不是公共构造方法。提供静态工厂方法而不是公共构造方法有优点也有缺点。

静态工厂方法的一个优点是,不像构造方法,它们是有名字的。 如果构造方法的参数本身并不描述被返回的对象,则具有精心选择名称的静态工厂更易于使用,并且生成的客户端代码更易于阅读。 例如,返回一个可能为素数的BigInteger的构造方法BigInteger(int,int,Random)可以更好地表示为名为BigInteger.probablePrime的静态工厂方法。 (这个方法是在Java 1.4中添加的。)

一个类只能有一个给定签名的构造方法。 程序员知道通过提供两个构造方法来解决这个限制,这两个构造方法的参数列表只有它们的参数类型的顺序不同。 这是一个非常糟糕的主意。 这样的API用户将永远不会记得哪个构造方法是哪个,最终会错误地调用。 阅读使用这些构造方法的代码的人只有在参考类文档的情况下才知道代码的作用。

因为他们有名字,所以静态工厂方法不会受到上面讨论中的限制。在类中似乎需要具有相同签名的多个构造方法的情况下,用静态工厂方法替换构造方法,并仔细选择名称来突出它们的差异。

静态工厂方法的第二个优点是,与构造方法不同,它们不需要每次调用时都创建一个新对象。这允许不可变的类(条目17)使用预先构建的实例,或者在构造时缓存实例,并反复分配它们以避免创建不必要的重复对象。boolean.valueof(boolean)方法说明了这种方法:它从不创建对象。这种技术类似于享元模式(Flyweight)[Gamma95]。如果经常请求等价对象,那么它可以极大地提高性能,特别是如果在创建它们非常昂贵的情况下。

静态工厂方法从重复调用返回相同对象的能力允许类保持在任何时候存在的实例的严格控制。这样做的类被称为实例控制( instance-controlled)。编写实例控制类的原因有很多。实例控制允许一个类来保证它是一个单例(3)项或不可实例化的(条目4)。同时,它允许一个不可变的值类(条目17)保证不存在两个相同的实例:当且仅当a== ba.equals(b)。这是享元模式的基础[Gamma95]。Enum类型(条目34)提供了这个保证。

静态工厂方法的第三个优点是,与构造方法不同,它们可以返回其返回类型的任何子类型的对象。 这为你在选择返回对象的类时提供了很大的灵活性。

这种灵活性的一个应用是API可以返回对象而不需要公开它的类。 以这种方式隐藏实现类会使 API非常紧凑。 这种技术适用于基于接口的框架(条目20),其中接口为静态工厂方法提供自然返回类型。

在Java 8之前,接口不能有静态方法。根据约定,一个名为Type的接口的静态工厂方法被放入一个非实例化的伙伴类(companion class)(条目4)Types类中。例如,Java集合框架有45个接口的实用工具实现,提供不可修改的集合、同步集合等等。几乎所有这些实现都是通过静态工厂方法在一个非实例类(java .util. collections)中导出的。返回对象的类都是非公开的。

Collections框架API的规模要比它之前输出的45个单独的公共类要小得多,每个类有个便利类的实现。不仅是API的大部分减少了,还包括概念上的权重:程序员必须掌握的概念的数量和难度,才能使用API。程序员知道返回的对象恰好有其接口指定的API,因此不需要为实现类读阅读额外的类文档。此外,使用这种静态工厂方法需要客户端通过接口而不是实现类来引用返回的对象,这通常是良好的实践(条目64)。

从Java 8开始,接口不能包含静态方法的限制被取消了,所以通常没有理由为接口提供一个不可实例化的伴随类。 很多公开的静态成员应该放在这个接口本身。 但是,请注意,将这些静态方法的大部分实现代码放在单独的包私有类中仍然是必要的。 这是因为Java 8要求所有接口的静态成员都是公共的。 Java 9允许私有静态方法,但静态属性和静态成员类仍然需要公开。

静态工厂的第四个优点是返回对象的类可以根据输入参数的不同而不同。 声明的返回类型的任何子类都是允许的。 返回对象的类也可以随每次发布而不同。

EnumSet类(条目 36)没有公共构造方法,只有静态工厂。 在OpenJDK实现中,它们根据底层枚举类型的大小返回两个子类中的一个的实例:如果大多数枚举类型具有64个或更少的元素,静态工厂将返回一个RegularEnumSet实例, 返回一个long类型;如果枚举类型具有六十五个或更多元素,则工厂将返回一个JumboEnumSet实例,返回一个long类型的数组。

这两个实现类的存在对于客户端是不可见的。 如果RegularEnumSet不再为小枚举类型提供性能优势,则可以在未来版本中将其淘汰,而不会产生任何不良影响。 同样,未来的版本可能会添加EnumSet的第三个或第四个实现,如果它证明有利于性能。 客户端既不知道也不关心他们从工厂返回的对象的类别; 他们只关心它是EnumSet的一些子类。

静态工厂的第5个优点是,在编写包含该方法的类时,返回的对象的类不需要存在。这种灵活的静态工厂方法构成了服务提供者框架的基础,比如Java数据库连接API(JDBC)。服务提供者框架是提供者实现服务的系统,并且系统使得实现对客户端可用,从而将客户端从实现中分离出来。

服务提供者框架中有三个基本组:服务接口,它表示实现;提供者注册API,提供者用来注册实现;以及服务访问API,客户端使用该API获取服务的实例。服务访问API允许客户端指定选择实现的标准。在缺少这样的标准的情况下,API返回一个默认实现的实例,或者允许客户端通过所有可用的实现进行遍历。服务访问API是灵活的静态工厂,它构成了服务提供者框架的基础。

服务提供者框架的一个可选的第四个组件是一个服务提供者接口,它描述了一个生成服务接口实例的工厂对象。在没有服务提供者接口的情况下,必须对实现进行反射实例化(条目65)。在JDBC的情况下,Connection扮演服务接口的一部分,DriverManager.registerDriver提供程序注册API、DriverManager.getConnection是服务访问API,Driver是服务提供者接口。

服务提供者框架模式有许多变种。 例如,服务访问API可以向客户端返回比提供者提供的更丰富的服务接口。 这是桥接模式[Gamma95]。 依赖注入框架(条目5)可以被看作是强大的服务提供者。 从Java 6开始,平台包含一个通用的服务提供者框架java.util.ServiceLoader,所以你不需要,一般也不应该自己编写(条目59)。 JDBC不使用ServiceLoader,因为前者早于后者。

只提供静态工厂方法的主要限制是,没有公共或受保护构造方法的类不能被子类化。例如,在Collections框架中不可能将任何方便实现类子类化。可以说,这可能是因祸得福,因为它鼓励程序员使用组合而不是继承(条目18),并且是不可变类型(条目17)。

静态工厂方法的第二个缺点是,程序员很难找到它们。它们不像构造方法那样在API文档中突出,因此很难找出如何实例化一个提供静态工厂方法而不是构造方法的类。Javadoc工具可能有一天会引起对静态工厂方法的注意。与此同时,可以通过将注意力吸引到类或接口文档中的静态工厂以及遵守通用的命名约定来减少这个问题。下面是一些静态工厂方法的常用名称。以下清单并非完整:

  • from——A类型转换方法,它接受单个参数并返回此类型的相应实例,例如:Date d = Date.from(instant);
  • of——一个聚合方法,接受多个参数并返回该类型的实例,并把他们合并在一起,例如:Set<Rank> faceCards = EnumSet.of(JACK, QUEEN, KING);
  • valueOf——from和to更为详细的替代方式,例如:BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
  • instance或getInstance——返回一个由其参数(如果有的话)描述的实例,但不能说它具有和参数相同的值,例如:StackWalker luke = StackWalker.getInstance(options);
  • create 或 newInstance——与instance 或 getInstance类似,除了该方法保证每个调用返回一个新的实例,例如:Object newArray = Array.newInstance(classObject, arrayLen);
  • getType——与getInstance类似,但是如果在工厂方法中不同的类中使用。Type是工厂方法返回的对象类型,例如:FileStore fs = Files.getFileStore(path);
  • newType——与newInstance类似,但是如果在工厂方法中不同的类中使用。Type是工厂方法返回的对象类型,例如:BufferedReader br = Files.newBufferedReader(path);
  • type—— getType 和 newType简洁的替代方式,例如:List<Complaint> litany = Collections.list(legacyLitany);

总之,静态工厂方法和公共构造方法都有它们的用途,并且了解它们的相对优点是值得的。通常,静态工厂更可取,因此避免在没有考虑静态工厂的情况下提供公共构造方法。