跳转至

Effective Java

Link : Effective Java 3rd

创建和销毁对象

  • 考虑用静态工厂方法替换构造器
  • 当遇到多个构造器参数时考虑用Builder替换
  • 用私有构造器或者枚举类型强化单例属性
  • 通过私有构造器强化不可实例化的能力
  • 依赖注入优先硬连接资源
  • 避免创建不必要的对象
  • 消除过期的对象引用
  • 避免使用终结方法和清理器
  • try-with-resources优先try-finally

所有对象通用的方法

  • 覆写equals时候遵守通用规定

  • 覆写equals时候总要覆写hashCode

  • 始终覆写toString

  • 小心覆写clone

  • 考虑实现Comparable接口

类和接口

  • 使类和成员可见性最小

  • 公有类中使用访问方法而非公有域

  • 使可变性最小

  • 组合优于继承

  • 要么为继承而设计并提供文档,要么禁止继承

  • 接口优于抽象类

  • 为后代(posterity)设计接口

  • 接口只用于定义类型

  • 类继承优于标签类

  • 优先考虑静态类而不是非静态

  • 将源文件限制为单个顶级类

泛型

  • 不要使用原生类型

  • 消除非受检警告

  • list优于数组

  • 优先考虑泛型

  • 优先考虑泛型方法

  • 使用有界通配符提升API的灵活性

  • 小心组合泛型和可变参数 @

  • 优先考虑类型安全的异构容器第

枚举和注解

  • 用enum代替int常量

  • 用实例域代替序数

  • 用EnumSet代替位域

  • 用EnumMap代替序数索引

  • 用接口模拟可扩展的枚举

  • 注解优于命名模式

  • 统一使用Override注解

  • 用标记接口定义类型

Lambda表达式和流 @

  • Lambda表达式优于匿名类
  • lambda的序列化
  • 方法引用优于Lambda表达式
  • 优先使用标准的函数式接口
  • 小心使用流
  • 流中优先使用无副作用的函数
  • Collection优先流作为返回类型
  • 当创建并行流的时候小心些

方法

  • 检查参数的有效性
  • 需要时进行保护性拷贝
  • 小心设计方法签名
  • 谨慎使用重载
  • 谨慎可变参数
  • 返回空集合或者数组,而不是null
  • 谨慎返回Optionals @
  • 为所有导出的API元素写文档注释

通用程序设计

  • 最小化局部变量作用域
  • for each优于传统for循环
  • 了解和使用类库
  • 如果需要精确答案,避免使用float和double
  • 基本类型优于装箱类型
  • 如果其他类型更合适,避免使用String
  • 小心String连接性能
  • 通过接口引用对象
  • 接口优于反射
  • 谨慎使用本地方法
  • 谨慎优化
  • 遵守普遍的命名规范

异常

  • 只针对异常情况才使用异常
  • 对可恢复的情况使用受检异常,对编程错误使用运行时异常
  • 避免不必要使用受检异常
  • 优先使用标准异常
  • 抛出与抽象对应的异常
  • 每个方法抛出异常要有文档
  • 在细节信息中包含捕获失败的信息
  • 努力使失败保持原子性
  • 不要忽略异常

并发

  • 同步访问共享可变数据

  • 避免过度同步

  • executors,task,stream优于线程 @
  • 并发工具优于wait和notify
  • 线程安全文档化
  • 慎用延迟初始化
  • 不要依赖线程调度器

序列化

  • 考虑其他可选择优于Java序列化 @

  • 考虑使用自定义序列化形式

  • 谨慎实现Serializable接口

  • 保护性编写readObject方法

  • 对于实例控制,枚举优于readResolve

  • 考虑序列化代理替换序列化实例

读书记录

  • 消除过期的引用对象**:防止内存泄露(当自行管理缓存时)

    • 实例:数组实现栈,pop的时候,删除的位置没有重新赋值为null;
  • 对于缓存(缓存项生命周期由键的外部引用决定时),可与使用WeakHashMap解决;

  • Equals方法覆盖:自反性、对称性、传递性、一致性,且需要重载hashCode方法;

无法在扩展可实例化的类的同时,既增加新的值组件,同时保留equals约定

  • 传递性(ColorPoint和Point)和里氏替换原则无法同时满足;
  • 可以通过组合而不是继承的方式进行扩展;
  • 可以在抽象的子类中添加新的值组件;

  • CompareTo

  • 使用Float.compareDouble.compare进行数值比较;
  • 建议实现x.compareTo(y) == x.equals(y)

  • 在公有类方法中使用公有方法而不是公有域

  • 类是外部可以访问的,必须使用公有方法,不能对外提供共有域;
  • 如果类是内部私有,或者私有的内部类,可以直接暴露其数据域(如果这些数据域确实描述该类提供的抽象)

  • 复合优先继承

  • 在包的内部使用继承是安全的,只有当子类真正是超类的子类型时;

  • 使用第三方包提供服务,使用组合;

  • 接口优于抽象类

泛型

  • ? 表示任意类型
  • Set\是个参数化类型,表示可以包含任何对象类型的一个集合;
  • Set<?>是个通配符类型,表示只能包含某种未知对象类型的一个集合;
  • 消除受检异常
  • 最小化@SuppressWarnings("unchecked")的范围
  • 列表优先数组
  • new List\[]是编译错误;
  • 数组是具体化、协变的,声明父类数组编译时无法发现问题;
  • 泛型是不可变的,通过擦除实现,擦除使泛型可以与没有使用泛型的代码随意进行互用
  • PECS: producer-extends, consumer-super
  • 参数化类型表示一个T生产者,就使用<? extends T>;如果表示一个T消费者,就使用<? super T>;
  • staic \ E reduce(List<? extends E> list, function\ f, E initVal)

  • 不要用通配符 ? 作为返回类型

  • 枚举和注解

    • Enum 实现常量数的策略模式
    • 在枚举类型中声明一个抽象的apply方法,并在特定于常量的类主体中覆盖;
    • 等价于: 接口(抽象类) + 实现类 + 单例

    • 使用EnumSet替代位域

    • 使用EnumMap替代序数索引

    • 坚持使用@Override注解

    • 用标记接口定义类型

    • 标记接口是没有包含方法声明的接口,如Serializable接口

    方法

    • 必要时进行保护性拷贝
    • 假设类的客户端会尽其所能破坏类的约束条件;
    • 保护性拷贝是在检查参数有效性之前,且针对的是拷贝之后的对象,避免多线程修改问题;
    • 对于参数类型可以被不信任方子类化的参数,不要使用clone方法进行保护性拷贝;
    • 类信任调用方或者类和客户端在同一个包,可以不进行保护性拷贝,但需要在类文档中说明清楚,调用者不能修改受影响的参数或返回值;

    • 谨慎设计方法签名

    • 对于boolean参数,优先使用两个元素的枚举类型,易懂且可扩展性好;

    • 慎用重载

    • 重载(overloading)是在编译时确定调用方法,根据声明的类型选择;
    • 覆盖(overriding)是在运行时确定调用方法,根据实际类型选择;
    • 返回零长度的数组或集合,而不是null
    • 零长度的数组可以共享;

    • 为所有导出的API元素编写文档注释

    • 类、接口、构造器、方法、域;
    • 方法:概要描述、详细描述、参数、返回、异常
    • 类和方法的线程安全性;
    • javadoc产生的HTML文件检查

    通用程序设计

    • foreach优于for循环

    • 基本类型优于装箱类型

    • 精确答案时,用BigDecimal、int、long替代float和double

    • 不用使用字符串连接操作符来合并多个字符串

    • 谨慎进行优化
    • 设计API、线路层协议和永久数据格式,需要考虑性能因素
    • 专业的工具进行profile,找到关键点再进行优化

    异常

    • 不要用异常实现正常的控制流

    • 对可恢复的情况使用受检异常,对编程错误使用运行时异常

    • 抛出有抽象相对应的异常

    • 高层实现捕获低层异常,同时抛出可按照高层抽象解释的异常;

    • 异常链,底层的异常(cause)被传到高层的异常;

    • 在细节消息中包含能捕获失败的信息

    • 失败保持原子性

    • 失败的方法调用应该使对象保持在被调用之前的状态 ;

    • 调整计算顺序使可能会失败的部分在对象状态被修改前发生;恢复代码;临时拷贝

    并发

    Java语言规范保证读或写一个变量是原子的(atomic),除非变量类型是long或double。

    - volatile保证线程可见性

    • 用synchronized修饰只包含变量的读/写的方法,这里同步为了通信效果,而不是为了互斥;
    • 避免过度同步
    • 避免死锁和数据破坏,不要从同步区域内调用外来方法;
    • executor和task优先于线程
    • 并发工具优先于wait, notify
    • Lock, CountDownLatch, Semaphore, CyclicBarrier, Condition
    • 并发集合:ConcurrentHashMap, CopyOnWriteList,CopyOnWriteSet, AtomicXXX, Collections.synchronizedXXX, LinkedBlockingQueue, ConcurrentLinkedQueue

    • 对于间歇式的定时,优先使用nanoTime

    • nanoTime不受系统的实时时钟的调整所影响

    • 线程安全性的文档化说明

    • Immutable, ThreadSafe, NotThreadSafe。

    • 不要让应用程序的正确性依赖线程调度器

    • 不要依赖Thread.yield或者线程优先级

    • 用线程池代替线程组(ThreadGroup)

    序列化

    • 显示指定序列化版本UID
    • 内部类不可实现Serializable,静态成员类可以
    • 内部类的默认序列化形式是定义不清楚的
    • @serial 标签
    • Javadoc会将文档信息放在序列化形式的特殊文档中
    • 线程安全
    • 如果在读取整个对象状态的任何其他方法上强制任何同步,则也必须在对象序列化上强制这种同步

    • 保护性地编写readObject方法

    • 防止通过直接构造byte[],通过默认序列化方式生成违反约束条件的实例;

    • readObject中添加保护性拷贝,防止通过“恶意编制的对象引用”引用并修改对象中的变量;

    • 单例的序列化:枚举优先于readResolve

    • 如果使用readResolve进行实例控制,带有对象引用类型的所有实例域则都必须被声明为transient