高效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
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。

65. 接口优于反射

核心反射工具java.lang.reflect提供对任意类的编程访问。 给定一个Class对象,可以获得Constructor,Method和Field实例,这些实例表示由Class实例表示的类的构造方法,方法和属性。 这些对象提供对类的成员名称,属性类型,方法签名等的编程访问。

此外,Constructor,Method和Field实例允许反射性地操作它们的底层对应物:可以通过在Constructor,Method和Field实例上调用方法来构造实例,调用方法和访问底层类的属性。 例如,Method.invoke方法允许在任何类的任何对象上调用任何方法(受通常的安全性约束)。 反射允许一个类使用另一个类,即使在编译前者时后者类并不存在。 然而,这种能力是有代价的:

  • 失去了编译时类型检查的所有好处,包括异常检查。如果一个程序试图通过反射调用一个不存在的或不可访问的方法,会在运行时失败,除非采取了特殊的预防措施。

  • 执行反射访问所需的代码笨拙而冗长。写起来很乏味,读起来很困难。

  • 性能受损。反射方法调用比普通方法调用慢得多。到底慢了多少还很难说,因为有很多因素在起作用。在我的机器上,调用一个没有输入参数和返回int类型的方法时,反射方法执行要比普通方法慢11倍。

有一些复杂的应用程序需要反射。示例包括代码分析工具和依赖注入框架。即使是这样的工具,随着它的缺点变得越来越明显,也在逐渐远离反射。如果你对应用程序是否需要反射有任何疑问,那么它可能是不需要的。

通过以非常有限的形式使用反射,可以获得反射的许多好处,同时花费很少。对于许多必须使用在编译时不可用的类的程序,在编译时存在一个适当的接口或父类来引用该类( 条目64)。如果是这种情况,可以使用反射创建实例,并通过它们的接口或父类正常地访问它们

例如,这是一个创建Set<String>实例的程序,其实例的类由第一个命令行参数指定。 程序将剩余的命令行参数插入到集合中并打印它。 无论第一个参数如何,程序都会打印剩余的参数,并删除重复项。 但是,打印这些参数的顺序取决于第一个参数中指定的类。 如果指定java.util.HashSet,则它们以明显随机的顺序打印; 如果指定java.util.TreeSet,则它们按字母顺序打印,因为TreeSet中的元素是按顺序排序的:

// Reflective instantiation with interface access
public static void main(String[] args) {
    // Translate the class name into a Class object
    Class<? extends Set<String>> cl = null;
    try {
        cl = (Class<? extends Set<String>>)  // Unchecked cast!
                Class.forName(args[0]);
    } catch (ClassNotFoundException e) {
        fatalError("Class not found.");
    }
    // Get the constructor
    Constructor<? extends Set<String>> cons = null;
    try {
        cons = cl.getDeclaredConstructor();
    } catch (NoSuchMethodException e) {
        fatalError("No parameterless constructor");
    }

    // Instantiate the set
    Set<String> s = null;
    try {
        s = cons.newInstance();
    } catch (IllegalAccessException e) {
        fatalError("Constructor not accessible");
    } catch (InstantiationException e) {
        fatalError("Class not instantiable.");
    } catch (InvocationTargetException e) {
        fatalError("Constructor threw " + e.getCause());
    } catch (ClassCastException e) {
        fatalError("Class doesn"t implement Set");
    }

    // Exercise the set
    s.addAll(Arrays.asList(args).subList(1, args.length));
    System.out.println(s);
}

private static void fatalError(String msg) {
    System.err.println(msg);
    System.exit(1);
}

虽然这只是一个演示程序,但它演示的技术非常强大的。演示程序可以很容易地变成泛型集合测试程序,通过积极地操纵一个或多个实例并检查它们是否遵守Set约定来验证指定的Set实现。 同样,它可以变成泛型集合性能分析工具。 事实上,这种技术足以实现一个成熟的服务提供者框架(service provider framework)(条目 1)。 通常,这种技术就是你在反射中所需要的。

这个例子说明了反射的两个缺点。 首先,该示例在运行时生成六个不同的异常,如果不使用反射实例化,则所有这些异常都是编译时错误。 (为了好玩,可以通过传入适当的命令行参数使程序生成六个异常中的每一个)。第二个缺点是需要25行繁琐的代码才能从其名称生成类的实例, 而构造函数方法使用一行代码即可。 可以通过捕获ReflectiveOperationException来减少程序的长度,该异常是Java 7中引入的各种反射异常的父类。这两个缺点仅限于实例化对象的程序部分。 实例化后,该集合与任何其他Set实例无法区分。 在真实的程序中,大量的代码因此不会受这种限定的反射使用的影响。

如果编译此程序,会获得未经检查的强制转换警告。 这个警告是合法的, 因此转换Class<? extends Set<String>> 也会成功,即使指定的集合不是Set接口的实现。在这种情况下,程序在实例化类时抛出ClassCastException异常。 要了解有关抑制警告的信息,请阅读条目 27。

反射的合法(如果很少)用途是管理类对运行时可能不存在的其他类、方法或属性的依赖关系。如果你正在编写一个必须针对其他包的多个版本运行的包,这将非常有用。该技术是根据支持包所需的最小环境(通常是最老的版本)编译包,并反射访问任何较新的类或方法。要使此工作正常进行,如果试图访问的新类或方法在运行时不存在,则必须采取适当的操作。适当的行动可能包括使用一些替代方法来完成相同的目标,或者使用简化的功能进行操作。

总之,反射是一种功能强大的工具,对于某些复杂的系统编程任务是必需的,但是它有很多缺点。如果编写的程序必须在编译时处理未知的类,则应该尽可能只使用反射实例化对象,并使用在编译时已知的接口或父类访问对象。