public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
静态工厂方法的优点:
Boolean.valueOf(bool)
EnumSet.of(...)
方法,根据元素长度,返回 RegularEnumSet
或者 JumboEnumSet
静态工厂方法的缺点:
当构造时的参数不超过 3 个时,通常使用 Telescoping constructor pattern
或者 JavaBean Pattern
。
// Telescoping constructor pattern
public HashMap();
public HashMap(int initialCapacity)
public HashMap(int initialCapacity, float loadFactor)
Telescoping constructor pattern
的缺点是,当参数多于 3 个时,难以书写和阅读。
// JavaBean Pattern
public class User {
private String name;
private String password;
public void setName(String name);
public void setPassword(String password);
}
JavaBean Pattern
的缺点是无法实现类的 immutable,因为每个使用对象的客户都可以用 set 方法修改对象的属性。
当构造器或者静态工厂方法含有超过(或者将来可能超过) 3 个的参数时,推荐使用 buider。
// 支持类继承拓展的 builder pattern
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>> {
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping) {
topping.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
// Subclasses must override this method to return "this"
protected abstract T self();
}
Pizza(Builder<?> builder) {
toppings = builder.toppings.clone();
}
}
// public final 字段的单例
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public void leaveTheBuilding() {...}
}
public final 字段实现单例的优点是:
// 静态工厂的单例
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() {...}
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {...}
}
静态工厂实现单例的优点:
Elvis::instance
是一个 Supplier
// 枚举实现的单例 -- 推荐方式
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {...}
}
枚举类型实现单例可以保证绝对的安全:
抽象类不能保证不可实例化性(noninstantiability),其子类可以实例化,而且它会误导使用者认为此类被设计用来继承。
可行的方案是私有化构造器:
// Noninstantiable utility class
public class UtilityClass {
// Suppress default constructor for noninstantiability
private UtilityClass() {
throw new AssertionErrot(); // 防止反射实例化
}
...
}
硬编码的依赖实例化是意译,原文是 hardwiring resources。
// 静态工具模式(不建议,不灵活且不可测试)
public class SpellChecker {
private static final Lexicon dictionary = ...;
private SpellChecker() {} // Noninstantiable
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
// 单例模式(不建议,不灵活且不可测试)
public class SpellChecker {
private final Lexicon dictionary = ...;
private SpellChecker() {}
public static INSTANCE = new SpellChecker(...);
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
在实现一个类时,如果它依赖了其他资源且该资源的类型会影响此类,那么不要使用单例模式或者静态工具模式,也不要让它硬编码创建依赖实例。推荐的做法是,将依赖对象或依赖对象的工厂作为参数,传递给构造器、静态工厂方法或 builder,这就是依赖注入,它大大增强了类的灵活性、复用性和可测试性。
// 依赖注入,具备了灵活性和可测试性
public class SpellChecker {
private final Lexicon dictionary;
private SpellChecker(Lexicon dictionary) {
this.dictionary = dictionary;
}
public static boolean isValid(String word) {...}
public static List<String> suggestions(String typo) {...}
}
对于大项目,通常包含成千上万的依赖,使用依赖注入会使得项目复杂化,但是可以通过使用优秀的依赖注入框架来解决这个问题,比如 Dagger、Guice 或 Sping。
创建了不必要的对象的例子:
String s = new String("bikini");
中创建了两次 String 对象。String.matches(...)
方法中,新建了 Pattern 对象来匹配,如果多次调用此方法,会多次创建 Pattern 对象。Long sum = 0L; sum += 1;
原始类型的自动装箱也会创建不必要的对象,因此尽量使用原始类型替代其包装类。但是,有些时候应该创建重复对象(Item 50)。因为没有做好必要的防御性拷贝可能带来可怕的 bugs 和安全漏洞,而创建了不必要的对象仅仅会影响代码风格和性能。
// ArrayList
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
内存泄漏的来源:
filnalizer 的行为不可预测,危险,通常都不需要。cleaner 不如 finalizer 那么危险,但是仍然不可预测,缓慢,通常也不需要。最好的建议就是不要使用它们。
try-with-resources 语句块简洁又周到,是 Java 7 以来最优的关闭资源的方式,能使用它的场合就使用它,否则再考虑 try-finally。
举个例子:
BufferedReader br = new BufferdReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (c != null) {
c.close();
}
}
如果物理设备出现故障无法访问,readLine() 会抛出异常,在 finally 代码块中 close() 也会抛出异常, 第二个异常会泯灭第一个异常,使得用户看不到自己真正关注的异常。
try (BufferedReader br = new BufferdReader(new FileReader(path))) {
return br.readLine();
}
使用 try-with-resources 代码块后,两个异常都会保留,且 close() (隐式自动关闭)的异常隐含在 readLine() 的异常中,因此保留了使用者真正关心的异常,同时被隐含的异常并没有被丢弃,可以在 stack trace print 中看到,还可以用 getSuppressed()
方法获取到 Throwable 对象。