前言
最近因为疫情和年底的起因,有大量的工夫来做一些本人的事,便在京东上买了一本 Effective Java 中文第三版,零散的读齐全本后,尽管网上评估褒贬不一 (大部分是因为翻译问题),但真心感觉这是一本十分经典的书籍,书中一共 90 个条目,每个条目探讨一条规定,这些规定反映了最优良的程序员在实践中罕用的一些无益的做法。
本着「你不输入,怎么提高 」的准则,后边会把一些章节用来练习,而后把本人学习的过程和想法记录下来。本文将重点介绍 用动态工厂办法代替结构器。
动态工厂办法概述
Java 中获取一个类的实例,最传统的办法是提供一个 (或几个) 私有的结构器,而后用 new 关键字去创立一个实例,然而在一些特定的用例中,咱们还能够抉择应用 动态工厂办法 去获取实例。在本书中,本文的作者 Joshua Bloch 给了一个清晰的倡议。
思考动态工厂办法而不是构造函数
动态工厂办法绝对于构造方法的优劣势
动态工厂办法和私有结构器都各有用途,须要了解他们各自的短处,通常状况下动态工厂更加适合,因而切忌第一反馈就是提供私有的结构器,而不优先思考动态工厂。所以本书中也总结了其中的一下优缺点。
劣势
可读性比拟高
在 Java 中有大量的应用 动态工厂办法,其中 String 就是一个典型代表。在创立 String 对象中,咱们都晓得不太可能通过结构器去创立新的 String 对象,但它的写法是能够的,尽管看起来怪怪的。
String value = new String("Henry");
然而 String 也能够应用 动态工厂办法 来创建对象。这里提供了几种 valueOf() 的重载,每个对象都返回一个新的 String 对象,取决于具体传的参数,该命名十分清晰的表白了这个办法的作用。
String value1 = String.valueOf(0);
String value3 = String.valueOf(true);
String value3 = String.valueOf("Henry");
再举例几个 Java 中的简洁并且名称十分清晰的示例
Optional<String> value1 = Optional.empty();
Optional<String> value2 = Optional.of("Henry");
Optional<String> value3 = Optional.ofNullable(null);
不用每次都创立一个新实例
在 Java 中,Boolean 类的 valueOf() 类就是提前事后构建好的实例,或将构建好的实例缓存起来,进行反复的利用,从而防止创立不必要的反复对象,缩小 JVM 的开销。
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);
}
在调用 valueOf() 时候返回的就是这两个实例的援用,能够防止每次都创建对象
public static Boolean valueOf(String s) {return toBoolean(s) ? TRUE : FALSE;
}
能够返回原返回类型的任何子类对象
咱们在抉择返回对象的类时就更有灵活性。这种灵活性的一种利用是 API 能够返回对象,同时又不会使对象的类变成私有的。这种形式暗藏实现类会使 API 变得十分简洁,能够依据须要不便地返回任何它的子类型的实例。并且还遵循了设计模式六大准则中的其中一条「里氏替换准则」
派生类(子类)对象能够在程式中代替其基类(超类)对象
public Class Animal {public static Animal getInstance(){
// 能够返回 Dog or Cat
return new Animal();}
}
Class Dog extends Animal{}
Class Cat extends Animal{}
所返回的对象的类能够随着每次调用而发生变化,这取决于动态工厂办法的参数值
这句比拟绕嘴,总结一句话就是,返回对象的类可能随着发行版本的不同而不同。书中对 EnumSet 进行了举例,EnumSet 没有私有的结构器,只有动态工厂办法,判断条件是,如果元素大于 64 个,就返回 RegularEnumSet,并用单个 long 进行反对;如果大于 64 个元素,就返回 JumboEmunSet 实例,用一个 long 数组进行反对。如果 RegularEnumSet 不能再给小的枚举类型提供性能劣势,就可能从将来的发行版本中将它删除,不会造成任何负面影响,并且客户端是不可见的。同样的也可进行扩大。
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);
}
毛病
类如果不蕴含私有的或者受爱护的结构器,就不能被继承
例如:要想将 Collections 中的任何工具实现子类化,这是不可能的。但这也正是激励程序员应用复合,而不是继承 (准则是:如果类不是专门设计来用于被继承的就尽量不要应用继承而应该应用组合)。
程序员很难发现它们
动态工厂办法不像构造方法一样在 API 中是标识进去的。所以查看 API 时不太容易发现。所以尽可能采纳规范的动态工厂办法命名形式去防止。例如:
-
from:类型转换办法,它承受单个参数并返回此类型的相应实例
- Date d = Date.from(instant);
-
of:聚合办法,承受多个参数并返回该类型的实例,并把他们合并在一起
- Set faceCards = EnumSet.of(JACK, QUEEN, KING);
-
valueOf:比 from 和 of 更繁琐的一种代替办法
- BigInteger prime = BigInteger.valueOf(Integer.MAX_VALUE);
创立动态工厂办法
自定义动态工厂办法
当然,咱们能够实现本人的动态工厂办法。 然而什么时候须要这样做,而不是通过惯例的构造函数创立类实例?
首先咱们创立一个惯例的 User 类:
public class User {
private final String name;
private final String email;
private final String country;
public User(String name, String email, String country) {
this.name = name;
this.email = email;
this.country = country;
}
}
这种状况下,应用构造函数创立和应用动态工厂办法创立没有什么区别。但当初变更一下条件,如果咱们心愿所有的 User 实例都取得 Counrty 字段的默认值须要怎么做?如果咱们应用默认值初始化字段,那么咱们也必须重构构造函数,从而让设计更加严格。此时能够采纳动态工厂办法:
public static User createWithDefaultCountry(String name, String email) {return new User(name, email, "China");
}
以下是咱们如何为 User 实例调配默认值的地区字段的办法:
User user = User.createWithDefaultCountry("Henry", "Henry@google.com");
总结
在书中的前序中有一段话十分喜爱,这里摘录进去:
尽管本书中的规定不会百分之百地实用于任何时刻和任何场合,然而,它们的确体现了绝大多数状况下的最佳编程实际。你不应该自觉的遵循这些规定,但偶然有了充沛的理由之后,能够去突破这些规定。同大多数学科一样,学习编程艺术首先要学会根本的规定,而后能力晓得什么时候去突破它。