高效Java(第三版) Effective Java
1. 考虑使用静态工厂方法替代构造方法 2. 当构造方法参数过多时使用builder模式 3. 使用私有构造方法或枚类实现Singleton属性 4. 使用私有构造方法执行非实例化 5. 使用依赖注入取代硬连接资源 6. 避免创建不必要的对象 7. 消除过期的对象引用 8. 避免使用Finalizer和Cleaner机制 9. 使用try-with-resources语句替代try-finally语句 10. 重写equals方法时遵守通用约定 11. 重写equals方法时同时也要重写hashcode方法 12. 始终重写 toString 方法 13. 谨慎地重写 clone 方法 14. 考虑实现Comparable接口 15. 使类和成员的可访问性最小化 16. 在公共类中使用访问方法而不是公共属性 17. 最小化可变性 18. 组合优于继承 19. 如果使用继承则设计,并文档说明,否则不该使用 20. 接口优于抽象类 21. 为后代设计接口 22. 接口仅用来定义类型 23. 优先使用类层次而不是标签类 24. 优先考虑静态成员类 25. 将源文件限制为单个顶级类 26. 不要使用原始类型 27. 消除非检查警告 28. 列表优于数组 29. 优先考虑泛型 30. 优先使用泛型方法 31. 使用限定通配符来增加API的灵活性 32. 合理地结合泛型和可变参数 33. 优先考虑类型安全的异构容器 34. 使用枚举类型替代整型常量 35. 使用实例属性替代序数 36. 使用EnumSet替代位属性 37. 使用EnumMap替代序数索引 38. 使用接口模拟可扩展的枚举 39. 注解优于命名模式 40. 始终使用Override注解 41. 使用标记接口定义类型 42. lambda表达式优于匿名类 43. 方法引用优于lambda表达式 44. 优先使用标准的函数式接口 45. 明智审慎地使用Stream 46. 优先考虑流中无副作用的函数 47. 优先使用Collection而不是Stream来作为方法的返回类型 48. 谨慎使用流并行 49. 检查参数有效性 50. 必要时进行防御性拷贝 51. 仔细设计方法签名 52. 明智而审慎地使用重载 53. 明智而审慎地使用可变参数 54. 返回空的数组或集合不要返回null 55. 明智而审慎地返回Optional 56. 为所有已公开的API元素编写文档注释 57. 最小化局部变量的作用域 58. for-each循环优于传统for循环 59. 熟悉并使用Java类库 60. 需要精确的结果时避免使用float和double类型 61. 基本类型优于装箱的基本类型 62. 当有其他更合适的类型时就不用字符串 63. 注意字符串连接的性能 64. 通过对象的接口引用对象 65. 接口优于反射 66. 明智谨慎地使用本地方法 67. 明智谨慎地进行优化 68. 遵守普遍接受的命名约定 69. 仅在发生异常的条件下使用异常 70. 对可恢复条件使用检查异常,对编程错误使用运行时异常 71. 避免不必要地使用检查异常 72. 赞成使用标准异常 73. 抛出合乎于抽象的异常 74. 文档化每个方法抛出的所有异常 75. 在详细信息中包含失败捕获信息 76. 争取保持失败原子性 77. 同步访问共享的可变数据 78. 避免过度同步 79. EXECUTORS, TASKS, STREAMS 优于线程 80. 优先使用并发实用程序替代wait和notify 81. 线程安全文档化 82. 明智谨慎地使用延迟初始化 83. 不要依赖线程调度器 84. 其他替代方式优于Java本身序列化 85. 非常谨慎地实现SERIALIZABLE接口 86. 考虑使用自定义序列化形式 87. 防御性地编写READOBJECT方法 88. 对于实例控制,枚举类型优于READRESOLVE 89. 考虑序列化代理替代序列化实例

列表优于数组

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

数组在两个重要方面与泛型不同。 首先,数组是协变的(covariant)。 这个吓人的单词意味着如果SubSuper的子类型,则数组类型Sub []是数组类型Super []的子类型。 相比之下,泛型是不变的(invariant):对于任何两种不同的类型Type1Type2List<Type1>既不是List <Type2>的子类型也不是父类型。[JLS,4.10; Naftalin07,2.5]。 你可能认为这意味着泛型是不足的,但可以说是数组缺陷。 这段代码是合法的:

// Fails at runtime!
Object[] objectArray = new Long[1];
objectArray[0] = "I don"t fit in"; // Throws ArrayStoreException

但这个不是:

// Won"t compile!
List<Object> ol = new ArrayList<Long>(); // Incompatible types
ol.add("I don"t fit in");

无论哪种方式,你不能把一个String类型放到一个Long类型容器中,但是用一个数组,你会发现在运行时产生了一个错误;对于列表,可以在编译时就能发现错误。 当然,你宁愿在编译时找出错误。

数组和泛型之间的第二个主要区别是数组被具体化了(reified)[JLS,4.7]。 这意味着数组在运行时知道并强制执行它们的元素类型。 如前所述,如果尝试将一个String放入Long数组中,得到一个ArrayStoreException异常。 相反,泛型通过擦除(erasure)来实现[JLS,4.6]。 这意味着它们只在编译时执行类型约束,并在运行时丢弃(或擦除)它们的元素类型信息。 擦除是允许泛型类型与不使用泛型的遗留代码自由互操作(条目 26),从而确保在Java 5中平滑过渡到泛型。

由于这些基本差异,数组和泛型不能很好地在一起混合使用。 例如,创建泛型类型的数组,参数化类型的数组,以及类型参数的数组都是非法的。 因此,这些数组创建表达式都不合法:new List <E> []new List <String> []new E []。 所有将在编译时导致泛型数组创建错误。

为什么创建一个泛型数组是非法的? 因为它不是类型安全的。 如果这是合法的,编译器生成的强制转换程序在运行时可能会因为ClassCastException异常而失败。 这将违反泛型类型系统提供的基本保证。

为了具体说明,请考虑下面的代码片段:

// Why generic array creation is illegal - won"t compile!
List<String>[] stringLists = new List<String>[1];  // (1)
List<Integer> intList = List.of(42);               // (2)
Object[] objects = stringLists;                    // (3)
objects[0] = intList;                              // (4)
String s = stringLists[0].get(0);                  // (5)

让我们假设第1行创建一个泛型数组是合法的。第2行创建并初始化包含单个元素的List<Integer>。第3行将List<String>数组存储到Object数组变量中,这是合法的,因为数组是协变的。第4行将List <Integer>存储在Object数组的唯一元素中,这是因为泛型是通过擦除来实现的:List<Integer>实例的运行时类型仅仅是List,而List<String> []实例是List [],所以这个赋值不会产生ArrayStoreException异常。现在我们遇到了麻烦。将一个List<Integer>实例存储到一个声明为仅保存List<String>实例的数组中。在第5行中,我们从这个数组的唯一列表中检索唯一的元素。编译器自动将检索到的元素转换为String,但它是一个Integer,所以我们在运行时得到一个ClassCastException异常。为了防止发生这种情况,第1行(创建一个泛型数组)必须产生一个编译时错误。

类型EList<E>List<String>等在技术上被称为不可具体化的类型(nonreifiable types)[JLS,4.7]。 直观地说,不可具体化的类型是其运行时表示包含的信息少于其编译时表示的类型。 由于擦除,可唯一确定的参数化类型是无限定通配符类型,如List <?>Map <?, ?>(条目 26)。 尽管很少有用,创建无限定通配符类型的数组是合法的。

禁止泛型数组的创建可能会很恼人的。 这意味着,例如,泛型集合通常不可能返回其元素类型的数组(但是参见条目 33中的部分解决方案)。 这也意味着,当使用可变参数方法(条目 53)和泛型时,会产生令人困惑的警告。 这是因为每次调用可变参数方法时,都会创建一个数组来保存可变参数。 如果此数组的元素类型不可确定,则会收到警告。 SafeVarargs注解可以用来解决这个问题(条目 32)。

当你在强制转换为数组类型时,得到泛型数组创建错误,或是未经检查的强制转换警告时,最佳解决方案通常是使用集合类型List <E>而不是数组类型E []。 这样可能会牺牲一些简洁性或性能,但作为交换,你会获得更好的类型安全性和互操作性。

例如,假设你想用带有集合的构造方法来编写一个Chooser类,并且有个方法返回随机选择的集合的一个元素。 根据传递给构造方法的集合,可以使用选择器作为游戏模具,魔术8球或数据源进行蒙特卡罗模拟。 这是一个没有泛型的简单实现:

// Chooser - a class badly in need of generics!
public class Chooser {
    private final Object[] choiceArray;


    public Chooser(Collection choices) {
        choiceArray = choices.toArray();
    }


    public Object choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceArray[rnd.nextInt(choiceArray.length)];
    }
}

要使用这个类,每次调用方法时,都必须将Object的choose方法的返回值转换为所需的类型,如果类型错误,则转换在运行时失败。 我们先根据条目 29的建议,试图修改Chooser类,使其成为泛型的。

// A first cut at making Chooser generic - won"t compile
public class Chooser<T> {
    private final T[] choiceArray;

    public Chooser(Collection<T> choices) {
        choiceArray = choices.toArray();
    }

    // choose method unchanged
}

如果你尝试编译这个类,会得到这个错误信息:

Chooser.java:9: error: incompatible types: Object[] cannot be
converted to T[]
        choiceArray = choices.toArray();
                                     ^
  where T is a type-variable:
    T extends Object declared in class Chooser

没什么大不了的,将Object数组转换为T数组:

choiceArray = (T[]) choices.toArray();

这没有了错误,而是得到一个警告:

Chooser.java:9: warning: [unchecked] unchecked cast
        choiceArray = (T[]) choices.toArray();
                                           ^
  required: T[], found: Object[]
  where T is a type-variable:
T extends Object declared in class Chooser

编译器告诉你在运行时不能保证强制转换的安全性,因为程序不会知道T代表什么类型——记住,元素类型信息在运行时会被泛型删除。 该程序可以正常工作吗? 是的,但编译器不能证明这一点。 你可以证明这一点,在注释中提出证据,并用注解来抑制警告,但最好是消除警告的原因(条目 27)。

要消除未经检查的强制转换警告,请使用列表而不是数组。 下面是另一个版本的Chooser类,编译时没有错误或警告:

// List-based Chooser - typesafe
public class Chooser<T> {
    private final List<T> choiceList;


    public Chooser(Collection<T> choices) {
        choiceList = new ArrayList<>(choices);
    }


    public T choose() {
        Random rnd = ThreadLocalRandom.current();
        return choiceList.get(rnd.nextInt(choiceList.size()));
    }
}

这个版本有些冗长,也许运行比较慢,但是值得一提的是,在运行时不会得到ClassCastException异常。

总之,数组和泛型具有非常不同的类型规则。 数组是协变和具体化的; 泛型是不变的,类型擦除的。 因此,数组提供运行时类型的安全性,但不提供编译时类型的安全性,反之亦然。 一般来说,数组和泛型不能很好地混合工作。 如果你发现把它们混合在一起,得到编译时错误或者警告,你的第一个冲动应该是用列表来替换数组。