《Effective Java》

经典重读——亚马逊链接


笔记链接


导图:

Effective Java overview.png

笔记文本:

Effective Java
1 第2章 创建和销毁对象
1.1 1. 考虑用静态工厂方法代替构造器
1.1.1 优点
1.1.1.1 优势一:有名称
1.1.1.2 优势二:不必在每次调用它们的时候都创建一个新对象
1.1.1.3 优势三:可以返回原返回类型的任何子类型的对象
1.1.1.4 优势四:在创建参数化类型实例时,它们使代码变得更简洁
1.1.2 缺点
1.1.2.1 类如果不含有公有的或者受保护的构造器,就不能被子类化
1.1.2.2 它们与其他静态方法实际上没有任何区别
1.2 2. 遇到多个构造器参数时要考虑用构建器
1.2.1 Builder
1.2.2 与构造器相比的略微优势在于:builder可以有多个可变参数
1.2.3 虽然创建builder的开销在实践中可能不那么明显,但是在某些十分注重性能的情况下,可能就成问题
1.2.4 builder比重叠构造器更冗长,因此只在很多参数时才使用
1.3 3. 用私有构造器或者枚举类型强化Singleton属性
1.3.1 单元素的枚举类型已经成为实现Singleton的最佳方法
1.4 4. 通过私有构造器强化不可实例化的能力
1.4.1 弊端:使得其不能被子类化
1.5 5. 避免创建不必要的对象
1.5.1 对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象
1.5.2 除了重用不可变的对象之外,也可以重用那些已知不会被修改的可变对象
1.5.3 要优先使用基本类型而不是装箱基本类型,要当心无意识的自动装箱
1.5.4 自己维护对象池只在对象非常重量级时才显得必要,比如数据库连接池,线程池
1.6 6. 消除过期的对象引用
1.6.1 一旦对象引用已经过期,只需清空这些引用即可
1.6.2 缓存导致的内存泄露
1.6.2.1 一种是确认在缓存之外存在对某几个项的键的引用,该项就有意义 ——可以用WeakHashMap代表缓存
1.6.2.2 针对“缓存项生命周期是否有意义”难以确认的情况 ——缓存应该时不时地清除掉没用的项。这项清除工作可以由一个后台线程来完成,或者也可以在给缓存添加新条目时顺便清理(LinkedHashMap类利用它的removeEldestEntry方法可以很容易实现)
1.6.2.3 第三种引发来源是:监听器和其他回调。 确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用 weak reference
1.7 7. 避免使用finalizer方法
1.7.1 通常是不可预测的
1.7.1.1 一个对象从不可用开始,到它的终结方法被执行,这段时间是任意长的
1.7.2 使用它有一个非常严重的性能损失
1.7.3 显示的终止方法通常与try-finally结合使用,以确保及时终止
1.7.4 用途:充当安全网、本地对等体
1.7.5 结论:不应该依赖终结方法来更新重要的持久状态
1.7.6 结论:除非是作为安全网,或者是为了终止非关键的本地资源,否则不要使用
2 第3章 对所有对象都通用的方法
2.1 8. 覆盖equals时请遵守通用约定
2.1.1 建议:能不覆盖就不要覆盖
2.1.2 不用覆盖的情形
2.1.2.1 类的每个实例本质上都是唯一的
2.1.2.2 不关心类是否提供了“逻辑相等”
2.1.2.3 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的
2.1.2.4 类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用
2.1.3 覆盖的约定
2.1.3.1 自反性
2.1.3.2 对称性
2.1.3.3 传递性
2.1.3.4 一致性
2.1.3.5 非空性
2.1.3.5.1 对于任何非null的引用值,x.equals(null)必须返回false
2.1.4 诀窍
2.1.4.1 使用== 检查参数是否为这个对象的引用
2.1.4.2 使用instanceof检查 参数是否为正确的类型
2.1.4.3 把参数转换为正确的类型
2.1.4.4 对于该类中的每个关键域,检查参数中的域是否与该对象中对应的域相匹配
2.1.4.5 是否满足:对称、传递、一致
2.1.4.6 覆盖equals时总要覆盖hashCode
2.1.4.7 不要企图让equals方法过于智能
2.1.4.8 不要将equals声明中的Object对象替换为其他类型
2.2 9. 覆盖 equals时总要覆盖 hashCode
2.2.1 最佳实践:
2.2.1.1 @Override public int hashCode(){ int result = 17; result = 31 * result + areaCode; result = 31 * result + prefix; result = 31 * result + lineNumber; }
2.3 10. 总要覆盖 toString
2.3.1 toString方法应该返回对象中包含的所有值得关注的信息
2.4 11. 谨慎地覆盖clone
2.4.1 通则:永远不要让客户去做任何类库能够替客户完成的事情
2.4.2 最好提供某些其他途径来代替对象拷贝,或者干脆不提供这样的功能
2.4.3 另一个实现对象拷贝的好方法是提供一个拷贝构造器或拷贝工厂
2.4.4 由于它有这么多缺点,有些专家级的程序员干脆从来不去覆盖clone方法,也从来不去调用它,除非拷贝数组
2.5 12. 考虑实现comparable接口
2.5.1 compareTo不但允许进行简单的等同性比较,而且允许执行顺序比较
2.5.2 如果你在编写一个值类,它具有非常明显的内在排序关系,比如按字母顺序、按数值顺序或者按年代顺序,那你就应该坚决考虑实现这个接口
2.5.3 如果一个类有多个关键域,那么你必须从最关键的域开始,逐步进行到所有的重要域
3 第4章 类和接口
3.1 13. 使类和成员的可访问性最小化
3.1.1 信息隐藏 或 封装
3.1.2 尽可能地使每个类或者成员不被外界访问
3.1.3 有一条规则限制了降低方法的可访问性能力:如果方法覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别
3.1.4 注意:长度非零的数组总是可变的,所以,类具有公有的静态final数组域或者返回这种域的访问方法总是会出错
3.2 14. 在公有类中使用访问方法而非公有域
3.2.1 如果类可以在它所在的包的外部进行访问,就提供访问方法
3.2.2 如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质错误
3.2.3 Java类库的反面典型:Point和Dimension,暴露dimension类的内部数据造成了严重的性能问题,而且这个问题依然存在
3.3 15. 使可变性最小化
3.3.1 理由:比可变类更加易于设计、实现和使用。它们不容易出错,且更加安全
3.3.2 使类成为不可变需要遵循五条规则
3.3.2.1 1 不要提供任何会修改对象状态的方法
3.3.2.2 2 保证类不会被扩展
3.3.2.3 使所有的域都是final的
3.3.2.4 使所有的域都成为私有的
3.3.2.5 确保对于任何可变组件的互斥访问
3.3.3 不可变对象的优势
3.3.3.1 不可变对象本质上是线程安全的,它们不要求同步
3.3.3.2 不仅可以共享不可变对象,甚至可以共享它们的内部信息
3.3.3.3 不可变对象为其他对象提供了大量的构件
3.3.3.4 不可变对象真正唯一的缺点是,对于每个不同的值都需要一个单独的对象
3.3.4 使类成为不可变的方法 (绝不允许自身被子类化)
3.3.4.1 1 使类成为final
3.3.4.2 2 让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂来替代公有的构造器
3.3.4.3 静态工厂方法除了允许多个实现类的灵活性之外,这种方法还使得有可能通过改善静态工厂的对象缓存能力,在后续的发行版中改进该类的性能
3.3.4.4 还有许多其他优势,比如,提供一种其他功能的 构造器 ,只需添加第二个静态工厂,并且工厂的名字清楚地表明它的功能
3.3.5 如果你选择让自己的不可变类实现Serializable接口,并且包含一个或多个指向可变对象的域,就必须提供一个显式的readObject或者readResolve方法
3.3.6 除非有很好的理由要让类成为一个可变的类,否则就应该是不可变的
3.3.7 如果类不能被做成不可变的,仍然应该尽可能地限制它的可变性。降低对象可存在的状态数,可以更容易地分析该对象的行为,同时降低出错的可能性
3.4 16. 复合优先于继承
3.4.1 继承是实现代码重用的有力手段,但它并非永远是完成这项工作的最佳工具
3.4.2 与方法调用不同的是,继承打破了封装性
3.4.3 新类中的每个实例方法都可以调用被包含的现有类实例中对应的方法,并返回它的结果,这被称为“转发”,新类中的方法被称为转发方法
3.4.4 使用复合的类也被称为包装类,这也正是“装饰模式”
3.4.5 包装类不适合用在回调框架中。 编写转发方法倒是有点琐碎,但是只需要给每个接口编写一次构造器,转发类则可以通过包含接口的包替你提供
3.4.6 只有当子类真正是超类的子类型时,才适合用继承
3.4.7 如果在适合于使用复合的地方使用继承,则会不必要地暴露实现细节。这样得到的API会把你限制在原始的实现上,永远限定了类的性能
3.5 17. 要么为继承而设计,并提供文档说明,要么就禁止继承
3.5.1 说明可覆盖方法的自用性
3.5.2 好的文档应该描述一个给定的方法做了什么,而不是如何做到的
3.5.3 对于为了继承而设计的类,唯一的测试方法就是编写子类
3.5.4 构造器决不能调用可被覆盖的方法
3.5.5 为了继承而设计的类,对于这个类会有一些实质性的限制
3.5.6 对于那些并非为了安全地进行子类化而设计和编写文档的类,要禁止子类化
3.6 18. 接口优于抽象类
3.6.1 现有的类可以很容易被更新,以实现新的接口
3.6.2 接口是定义mixin(混合类型)的理想选择
3.6.3 接口允许我们构造非层次结构的类型框架
3.6.4 接口使得安全地增强类的功能成为可能
3.6.5 通过对你导出的每个重要接口都提供一个抽象的骨架实现类,把接口和抽象类的优点结合起来
3.6.6 抽象类与接口相比有一个明显优势:抽象类的演变比接口的演变要容易得多。如果在后续发行版中,你希望在抽象类中增加新的方法,始终可以增加具体方法,它包含合理的默认实现。然后,该抽象类的所有实现都将提供这个新方法。对于接口,这样做是行不通的
3.6.7 设计公有接口要非常谨慎。接口一旦被公开发行,并且已被广泛实现,再想改变这个接口几乎是不可能的
3.7 19. 接口只用于定义类型
3.7.1 有一种接口被称为 常量接口。这种接口不包含任何方法,它只包含静态final域,每个域都导出一个常量。使用这些常量的类实现这个接口,以避免用类名来修饰常量名。
3.7.2 应该将这种量导出
3.8 20. 类层次优于标签类
3.8.1 缺点
3.8.1.1 充斥着样板代码,包括枚举声明、标签域以及条件语句
3.8.1.2 标签类过于冗长、容易出错,并且效率低下
3.9 21. 用函数对象表示策略
3.9.1 比较器函数代表一种为元素排序的策略
3.9.2 我们在设计具体的策略类时,还需要定义一个策略接口
3.10 22. 优先考虑静态成员类
3.10.1 嵌套类是指被定义在另一个类的内部的类。嵌套类有四种
3.10.1.1 静态成员类
3.10.1.2 非静态成员类
3.10.1.3 匿名类
3.10.1.4 局部类
3.10.2 如果声明成员类不要求访问外围实例,就要始终把static修饰符放在它的声明中
4 第5章 泛型
4.1 23. 请不要在新代码中使用原生态类型
4.2 24. 消除非受检警告
4.2.1 要尽可能消除每一个非受检警告
4.2.2 如果无法消除警告,同时可以证明引起警告的代码是类型安全的,(只有在这种情况下才)可以用一个@SuppressWarning("uncheck")注解来禁止这条警告
4.3 25. 列表优先于数组
4.3.1 数组与泛型相比,有两个重要不同点

    4.3.1.1 1 数组是协变的
    4.3.1.2 2 数组是具体化的

4.4 26. 优先考虑泛型
4.4.1 编写自己的泛型会比较困难,但是值得花些时间去学习如何编写
4.5 27. 优先考虑泛型方法
4.5.1 泛型方法就像泛型一样,使用起来比要求客户端转换输入参数并返回值的方法来得更加安全,也更加容易。
4.5.2 就像泛型一样,你应该确保新方法可以不用转换就能使用,这通常意味着要将它们泛型化
4.6 28. 利用有限制通配符来提升API的灵活性
4.6.1 <? Extends E>
4.6.2
4.6.3 PECS 表示: producer-extends, consumer-super
4.6.4 所有的comparable和comparator都是消费者
4.7 29. 优先考虑类型安全的异构容器
5 第6章 枚举和注解
5.1 第30条 用enum代替int常量
5.1.1 因为没有可访问的构造器,枚举类型是真正的final
5.1.2 枚举是实例受控的
5.1.3 它们是单例的泛型化,本质上是单元素的枚举
5.1.4 你可以增加或重新排列枚举类型中的常量,而无需重新编译它的客户端代码,因为导出常量的域在枚举类型和它的客户端之间提供了一个隔离层:常量值并没有被编译到客户端代码中,而是在int枚举模式之中
5.1.5 最终,可以通过调用toString方法将枚举转化成可打印的字符串
5.1.6 除了完善了int枚举模式不足之外,枚举类型还允许添加任意的方法和域,并实现任意接口
5.1.7 将不同的行为与每个枚举常量关联起来:在枚举类型中声明一个抽象的方法
5.1.7.1 pubic enum Operation{ PLUS{double apply(double x, double y){return x + y;}}, MINUS{double apply(double x, double y){return x - y;}}, TIMES{double apply(double x, double y){return x * y;}}, DIVIDE{double apply(double x, double y){return x / y;}}; abstract double apply(double x, double y); }
5.1.7.2 如果给第二版添加新常量,你就不可能会忘记提供apply方法
5.1.8 特定于常量的方法实现可以与特定于常量的数据结合起来
5.1.8.1 pubic enum Operation{ PLUS("+") {double apply(double x, double y){return x + y;}}, MINUS("-") {double apply(double x, double y){return x - y;}}, TIMES("*") {double apply(double x, double y){return x * y;}}, DIVIDE("/") {double apply(double x, double y){return x / y;}}; private final String symbol; Operation(String symbol) {this.symbol = symbol;} @Override public String toString(){ return this.sybol;} abstract double apply(double x, double y); }
5.1.9 枚举类型有一个自动产生的valueOf(String )方法,它将常量的名字转变成常量本身
5.1.10 策略枚举
5.1.10.1 嵌套策略枚举之后,枚举中就不需要switch语句或者特定于常量的方法实现了。虽然这种模式没有switch语句那么简洁,但更加安全,也更加灵活
5.2 第31条 用实例域代替序数
5.2.1 永远不要根据枚举的序数导出与它关联的值,而是要将它保存在一个实例域中
5.2.2 ordinal()——大多数程序员都不需要这个方法,它是设计成用于像EnumSet和EnumMap这种基于枚举的通用数据结构的
5.3 第32条 用EnumSet代替位域
5.3.1 枚举类型要用于集合中,没有理由用位域来表示它们
5.4 第33条 用EnumMap代替序数索引
5.4.1 使用EnumMap,运行速度可以与使用序数的程序相媲美,没有不安全的转换,不必手工标注这些索引的输出;计算数组索引时也不可能出错
5.4.2 之所以能与通过序数索引的数组相媲美,是因为EnumMap在内部使用了这种数组
5.4.3 最好不要用序数来索引数组,而要使用EnumMap
5.5 第34条 用接口模拟可伸缩的枚举
5.5.1 虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,对它进行模拟
5.6 第35条 注解优先于命名模式
5.6.1 示例
5.6.1.1 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Test{ }
5.6.1.2 这种注解称为元注解
5.7 第36条 坚持使用Override注解
5.7.1 能够避免疏漏的 重载
5.8 第37条 用标记接口定义类型
5.8.1 定义:没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口
6 第7章 方法
6.1 第38条 检查参数的有效性
6.2 第39条 必要时进行保护性拷贝
6.2.1 假设类的客户端会尽其所能来破坏这个类的约束条件,因此你必须保护性地设计程序
6.2.2 为了保护类的内部信息免受直接访问修改的攻击,对于构造器的每个可变参数进行保护性拷贝是必要的
6.2.3 对与参数类型可以被不可信任方子类化的参数,请不要使用clone方法进行保护性拷贝
6.2.4 虽然替换构造器就可以成功地避免上述攻击,但是改变Period实例仍然是有可能的,因为它的访问方法提供了对其可变内部成员的访问能力 为了防御这种攻击,只需要修改访问方法,使它返回可变内部域的保护性拷贝即可
6.3 第40条 谨慎设计方法签名
6.3.1 谨慎选择方法的名称
6.3.2 不要过于追求提供便利的方法
6.3.2.1 每个方法都应该尽其所能。
6.3.2.2 方法太多会使类难以学习、使用、文档化、测试和维护
6.3.2.3 只有当一项操作被经常使用时,才考虑为它提供快捷方法,如果不能确定,还是不提供为好
6.3.3 避免过长的参数列表
6.3.3.1 相同类型的长参数列表格外有害
6.3.3.2 例如,List接口提供了subList方法,返回子列表的视图(view)
6.3.3.3 解决方法
6.3.3.3.1 1 分解为多个方法
6.3.3.3.2 2 创建辅助类
6.3.3.3.3 3 采用builder模式
6.3.3.4 对参数类型,要优先使用接口而不是类
6.3.3.5 对于boolean参数,要优先使用两个元素的枚举类型
6.4 第41条 慎用重载
6.4.1 对于重载方法的选择是静态的,而对于被覆盖的方法的选择则是动态的
6.4.2 安全而保守的策略是,永远不要导出两个具有相同参数数目的重载方法
6.5 第42条 慎用可变参数
6.5.1 EnumSet类对它的静态工厂使用这种方法,最大限度地减少创建枚举集合的成本。枚举集合为位域提供在性能方面有竞争力的替代方法
6.6 第43条 返回零长度的数组或者集合,而不是null
6.6.1 对于不返回任何元素的调用,每次都返回同一个零长度数组是有可能的,因为零长度数组是不可变的
6.7 第44条 为所有导出的API元素编写文档注释
6.7.1 为了正确地编写API文档,必须在每个被导出的 类、接口、构造器、方法和域声明之前增加一个文档注释
6.7.2 方法的文档注释应该简洁地描述出它和客户端之间的约定
6.7.2.1 precondition
6.7.2.2 postcondition
6.7.2.3 side effect
6.7.2.4 thread safety
6.7.3 可以使用HTML标签
6.7.4 使用{@code}标签
6.7.5 {@literal}
7 第8章 通用程序设计
7.1 第45条 将局部变量的作用域最小化
7.1.1 几乎每个局部变量的声明都应该包含一个初始化表达式
7.1.2 使用for循环,可以大大降低 “剪切-粘贴”错误
7.1.3 使用for循环与使用while相比还有另外一个优势:更简短,从而增强可读性
7.2 第46条 for-each循环优先于传统的for循环
7.2.1 for-each循环不仅让你遍历集合和数组,还让你遍历任何实现Iterable接口的对象
7.2.2 如果你在编写的类型表示的是一组元素,即使你选择不让它实现Collection,也要让它实现Iterable。这样可以允许用户利用for-each循环遍历你的类型
7.2.3 有三种常见情况无法使用for-each循环
7.2.3.1 过滤
7.2.3.2 转换
7.2.3.3 平行迭代
7.3 第47条 了解和使用类库
7.4 第48条 如果需要精确答案,请避免使用float和double
7.4.1 二进制浮点运算
7.4.2 float和double不适合货币计算
7.4.3 使用BigDecimal、int或者long进行
7.5 第49条 基本类型优先于装箱基本类型
7.5.1 装箱类型的合理用处
7.5.1.1 作为集合中的元素、键和值
7.5.1.2 在参数化类型中,必须使用装箱基本类型作为类型参数
7.5.1.3 在反射调用时,必须使用装箱基本类型
7.6 第50条 如果其他类型更合适,则尽量避免使用字符串
7.6.1 字符串不适合代替其他的值类型
7.6.2 字符串不适合代替枚举类型
7.6.3 字符串不适合代替聚集类型
7.6.4 字符串不适合代替能力表
7.7 第51条 当心字符串连接的性能
7.7.1 使用StringBuilder替代
7.8 第52条 通过接口引用对象
7.8.1 如果你养成了用接口作为类型的习惯,你的程序将更加灵活
7.8.2 如果没有合适的接口存在,完全可以用类而不是接口来引用对象
7.9 第53条 接口优先于反射机制
7.9.1 反射
7.9.1.1 丧失了编译时类型检查的好处
7.9.1.2 执行反射访问所需要的代码非常笨拙和冗长
7.9.1.3 性能损失
7.10 第54条 谨慎地使用本地方法
7.10.1 使用本地方法提升性能的做法不值得提倡
7.11 第55条 谨慎地进行优化
7.11.1 优化的弊大于利
7.11.2 要努力编写好的程序而不是快的程序
7.11.3 努力避免那些限制性能的设计决策
7.11.4 要考虑API设计决策的性能后果
7.11.5 为了获得好的性能而对API进行包装,这是一种非常不好的想法
7.12 第56条 遵守普遍接受的命名规则
8 第9章 异常
8.1 第57条 只针对异常的情况才使用异常
8.1.1 基于异常的模式比标准模式要慢得多
8.2 第58条 对可恢复的情况使用受检异常,对编程错误使用运行时异常
8.2.1 受检异常
8.2.2 用运行时异常来表明编程错误
8.3 第59条 避免不必要地使用受检异常
8.4 第60条 优先使用标准异常
8.5 第61条 抛出与抽象相对应的异常
8.5.1 更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常
8.5.2 异常链
8.6 第62条 每个方法抛出的异常都要有文档
8.6.1 单独声明受检异常,使用Javadoc的@throws标记
8.6.2 不要在非受检异常上使用@throws
8.7 第63条 在细节消息中包含能捕获失败的信息
8.7.1 为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值
8.8 第64条 努力使失败保持原子性
8.8.1 失败的方法调用应该使对象保持在被调用之前的状态
8.9 第65条 不要忽略异常
8.9.1 空的catch块达不到应有的目的
8.9.2 至少包含一条忽略的说明
9 第10章 并发
9.1 第66条 同步访问共享的可变数据
9.1.1 Java语言规范保证读或者写一个变量是原子性的
9.1.2 volatile修饰符不执行互斥访问,但它可以保证任一线程在读取该域时都将看到最近刚被写入的值
9.1.3 使用volatile时,务必要小心,++操作并不是原子性的
9.1.4 AtomicInt、AtomicLong
9.1.4.1 getAndIncrement
9.1.5 多线程共享可变数据时,每个读或者写数据的线程都必行执行同步
9.2 第67条 避免过度同步
9.3 第68条 executor和task优先于线程
9.3.1 Executor FrameWork中有一个可以替代Timer的东西,即ScheduleThreadPoolExecutor 虽然就Timer更加容易,但executor更灵活。Timer对于长期运行任务时会影响到定时准确性。 如果Timer唯一的线程抛出未被捕获异常,Timer就会停止执行,而executor支持多个线程,并且优雅地从抛出的未受检异常的任务中恢复
9.4 第69条 并发工具优先于wait和notify
9.4.1 既然正确地使用wait和notify比较困难,就应该用更高级的并发工具来代替
9.4.2 concurrent工具分三类:Executor Framework、并发集合 以及 同步器
9.4.2.1 并发集合为标准的集合接口(如List、Queue和Map)
9.4.2.1.1 ConcurrentHashMap出了提供卓越的并发性之外,速度也非常快
9.4.2.1.2 优先使用ConcurrentHashMap而不是Hashtable或者Collections.synchronizedMap
9.4.2.2 有些集合接口已经通过阻塞操作进行了扩展,他们会一直等待可以成功执行为止
9.4.2.2.1 如BlockingQueue
9.4.2.2.2 不出所料,大多数ExecutorService实现(包括ThreadPoolExecutor)都使用BlockingQueue
9.4.2.3 同步器 是一些使线程能够等待另一个线程的对象,允许它们协调动作
9.4.2.3.1 最常用的是CountDownLatch和Semaphore
9.4.2.3.2 对于间歇式定时,应该使用System.nanoTime,更加精准
9.4.2.3.3 传递给Timer方法的executor必须允许创建至少与指定并发级别一样多的线程,否则这个测试就永远不会结束,这就是线程饥饿死锁
9.4.3 使用wait和notify就像用“并发汇编语言”进行编程一样
9.4.4 维护时:一般情况下你应该优先使用notifyAll,而不是notify
9.5 第70条 线程安全性的文档化
9.6 第71条 慎用延迟初始化
9.6.1 除非绝对必要,否则就不要这么做
9.6.2 在大多数情况下,正常的初始化要优先于延迟初始化
9.6.3 如果处于性能考虑需要对实例域使用延迟初始化,就使用双重检查模式
9.7 第72条 不要依赖于线程调度器
9.7.1 任何依赖于线程调度器来达到正确性或者性能要求的程序,很有可能都是不可移植的
9.7.2 不要依赖Thread.yield或者线程优先级。这些设施仅仅对调度器作些按时
9.7.3 线程优先级可以用来提高一个已经能够正常工作的程序的服务质量,但永远不应该用来“修正”一个原本不能正常工作的程序
9.8 第73条 避免使用线程组
10 第11章 序列化
10.1 第74条 谨慎实现Serializable接口
10.1.1 代价
10.1.1.1 降低了改变这个类的实现的灵活性
10.1.1.2 增加了出现Bug和安全漏洞的可能性
10.1.1.3 相关的测试负担增加
10.2 第75条 考虑使用自定义的序列化形式
10.3 第76条 保护性地编写readObject方法
10.4 第77条 对于实例控制,枚举类型优先于readResolve
10.5 第78条 考虑用序列化代理代替序列化实例

你可能感兴趣的:(《Effective Java》)