高效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以上的版本。

62. 当有其他更合适的类型时就不用字符串

字符串被设计用来表示文本,它们在这方面做得很好。因为字符串是如此常见,并且受到开发语言的良好支持,所以很自然地倾向会将字符串用于其他目的,而不是它们设计的原本目的。这一条目讨论了一些不应该使用字符串做的事情。

字符串是其他值类型的不良替代品。当一段数据从文件、网络或键盘输入进入程序时,它通常是字符串形式的。有一种自然的倾向是这样的,但是这种倾向只有在数据本质上是文本的情况下才合理。如果是数值类型,则应将其转换为适当的数值类型,如int、float或BigInteger。如果是“是”或“否”问题的答案,则应将其转换为适当的枚举类型或boolean值。更通常地说,如果有合适的值类型,无论是基本值还是对象引用,都应该使用它;如果没有,你应该编写一个。虽然这条建议似乎很明显,但经常被违反。

字符串是枚举类型的不良替代品。 正如条目 34中所讨论的,枚举使得枚举类型常量比字符串好得多。

字符串是聚合类型的不良替代品。 如果实体具有多个组件,则将其表示为单个字符串通常是个坏主意。 例如,这里是来自真实系统的一行代码——标识符名称已被更改:

// Inappropriate use of string as aggregate type
String compoundKey = className + "#" + i.next();

这种方法有许多缺点。 如果用于分隔属性的字符出现在某个属性中,结果可能会产生混乱。 要访问单个属性,必须解析字符串,这很慢,很乏味且容易出错。 不能提供equals,toString或compareTo方法,但必须接受String类提供的行为。 更好的方法是编写一个类来表示聚合,通常是私有静态成员类(条目 24)。

字符串是功能的不良替代品。 有时,字符串用于授予对某些功能的访问权限。 例如,考虑ThreadLocal的设计。 这样的工具提供了每个线程都有自己值的变量。 从版本1.2开始,Javal类库就有了一个ThreadLocal工具,但在此之前,程序员必须自己动手来实现。 当多年前遇到设计这样一个工具的任务时,几个人独立地想出了相同的设计,其中客户提供的字符串键用于识别每个线程局部变量:

// Broken - inappropriate use of string as capability!
public class ThreadLocal {
    private ThreadLocal() { } // Noninstantiable

    // Sets the current thread"s value for the named variable.
    public static void set(String key, Object value);

    // Returns the current thread"s value for the named variable.
    public static Object get(String key);
}

这种方法的问题是,字符串键表示线程本地变量的共享全局命名空间。为了使这种方法有效,客户端提供的字符串键必须是惟一的;如果两个客户端各自决定为它们的线程本地变量使用相同的名称,它们无意中共享一个变量,这通常会导致两个客户端都失败。而且,安全性很差。恶意客户端可以故意使用与另一个客户端相同的字符串密钥来非法访问另一个客户机端数据。

可以通过用一个不可伪造的键(有时称为功能)替换字符串来修复这个API:

public class ThreadLocal {
    private ThreadLocal() { }    // Noninstantiable

    public static class Key {    // (Capability)
        Key() { }
    }

    // Generates a unique, unforgeable key
    public static Key getKey() {
        return new Key();
    }

    public static void set(Key key, Object value);

    public static Object get(Key key);
}

虽然这解决了基于字符串的API的这两个问题,但是可以做得更好。不再真正需要静态方法。它们可以变成键上的实例方法,此时不再是线程局部变量的键:而是线程局部变量。此时,顶层类不再做任何事情,可以删除它,并将嵌套类重命名为ThreadLocal:

public final class ThreadLocal {
    public ThreadLocal();
    public void set(Object value);
    public Object get();
}

此API不是类型安全的,因为当从线程局部变量中检索它时,必须将值从Object转换为其实际类型。原始的基于字符串的API类型安全是不可能实现的,基于键的API类型安全也是很难实现的,但通过使ThreadLocal成为参数化类(第29项)来使这种API类型安全是一件简单的事情:

public final class ThreadLocal<T> {
    public ThreadLocal();
    public void set(T value);
    public T get();
}

粗略地说,这是java.lang.ThreadLocal提供的API。 除了解决基于字符串的API的问题之外,它还比任何基于键的API更快,更优雅。

总而言之,当存在或可以编写更好的数据类型时,避免将对象表示为字符串的自然倾向。 使用不当,字符串比其他类型更麻烦,更灵活更差,速度更慢,更容易出错。 字符串通常被滥用的类型包括基本类型,枚举类型和聚合类型。