《Effective Java》学习笔记

  1. 语法+词汇+用法
  2. 面向算法,面向函数,面向对象

 

 

 

  • 引言
  1. 78个条目
  2. Java支持四种类型:接口(interface)、类(class)、数组(array)和基本类型(primitive)。
  3. 方法的签名(signature)由名称、参数组成,不包括返回类型。
  4. API元素(API element):类、接口、构造器、成员以及序列化形式

 

 

  • 创建和销毁对象
  1. 考虑用静态工厂方法代替构造器
    1. 优势:
      • 有名称
      • 不必再每次调用的时候都创建一个新对象。
      • 可以返回原返回类型的任何子类型的对象
      • 在创建参数化类型实例的时候,使代码变得更加简洁。
    2. 缺点
      • 类如果不含公有的或受保护的构造器,就不能被子类化。
      • 与其他的静态方法实际上没有任何区别。
    3. 服务提供者框架(Service Provider Framework):多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现中解耦出来。
      • 三个重要组件:
        1. 服务接口(Service Interface):提供者实现
        2. 提供者注册API(Provider Registration API):系统用来注册实现,让客户端访问
        3. 服务访问API(Service Access API):客户端用来获取服务的实例
      • 惯用名称:
        1. valueOf:类型转换
        2. of:EnmSet()
        3. getInstance
        4. newInstance:能够确保返回的每个实例都与所有其他实例不同
        5. getType
        6. newType
  2. 遇到多个构造器参数时要考虑用构造器
    1. 重叠构造器(telescoping constructor)
      • 重叠构造器可行,但是当有很多参数的时候,客户端代码会很难编写,并且较难阅读。
    2. JavaBeans模式
      • 调用一个无参构造器来创建对象,然后调用setter方法来设置每个参数
      • 构造过程被分到几个调用中,在构造过程中JavaBean可能处于不一致的状态。
      • JavaBeans模式组织了把类做成不可变的可能。
      • 编译期无法确保程序员会在使用之前先在对象上调用freeze方法。
    3. Builder模式
      • 如果类的构造器或者静态工厂中具有多个参数,使用Builder模式。
      • 模拟了具名的可选参数
      • 在对象域而不是builder域对参数进行检验
      • Class.newInstance破坏了编译时的异常检查。
      • 不足:为了创建对象,必须先创建它的构造器。
  3. 用私有构造器或枚举类型强化Singleton属性
    1. 使类成为Singleton会使它的客户端测试变得十分困难。
    2. 实现Singleton的三种方法:

享有特权的客户端可以借助AccessibleObject.setAccessible,通过反射机制调用私有构造器。

单元素的枚举类型是实现Singleton的最佳方法。

  1. 通过私有构造器强化不可实例化的能力
    1. 企图通过将类做成抽象类来强制该类不可被实例化是行不通的。

使得一个类不能被子类化。所有的构造器必须显式或隐式调用超类的构造器。

  1. 避免创建不必要的对象
    1. 如果对象是immutable,就始终可以被重用。
    2. 对于同时提供了静态工厂方法和构造器的不可变类,通常使用静态工厂方法,避免创建不必要的对象。Boolean.valueOf(String)
    3. 延迟初始化(lazily initializing)
    4. 适配器(adapter):把功能委托给一个后备对象(backing object),从而为后备对象提供一个可以替代的接口。
    5. autoboxing:允许程序员将primitive和boxed primitive type混用,按需自动装箱和拆箱。
    6. 要优先使用primitive类型,当心无意识的自动装箱。
    7. 小对象的创建和回收动作是非常廉价的。
    8. 通过维护自己的对象池(object pool)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。
    9. 数据库连接池。
    10. defensive copying
  2. 消除过期的对象引用
    1. 内存泄漏的第一个常见来源是过期引用。
      • 如果一个栈先增长,再收缩,从栈中弹出的对象不会被当做垃圾回收,即使不再引用。
      • 栈内部维护着对这些对象的过期引用(‘obsolete reference)。指永远不会再被解除的引用。
      • unintentional object retention:无意识的对象保持
      • 修复方法:一旦对象引用过期,清空引用
      • 尽快检测出程序中的错误
      • 清空对象引用应该是一种例外,而不是一种规范行为。最好的方法是让包含该引用的变量结束其生命周期。
      • 一旦数组元素变成了非活动部分的一部分,程序员就手工清空。
      • 只要类是自己管理内存,程序员就该警惕内存泄漏问题。
    2. 内存泄漏的第二个常见来源是缓存
      • 只有当所要的缓存项的生命周期由该键的外部引用而不是由值决定时,WeakHashMap才有用处。
    3. 内存泄漏的第三个常见来源是监听器和其他回调
      • 确保回调立即被当做垃圾回收的最佳方法是只保存他们的weak reference
  3. 避免使用终结方法
    1. finalizer通常是不可预测的,也是很危险的。一般情况下是不必要的。
    2. 不要把finalizer当做C++中destructors的对应物。
    3. finailizer不能保证及时执行。注重time-critical的任务不应该由finalizer完成。
    4. finalizer线程的优先级比较低。Java语言规范并不保证哪个线程会执行finalizer。
    5. 不应该依赖finalizer更新重要的持久状态。
    6. 如果未被捕获的异常在终结过程中被抛出,可以忽略,并且该对象的终结过程也会终止。如果异常发生在终止方法中,不会打印出Stack Trace,连警告也不会打印出来。
    7. 使用终结方法有一个Severe的性能损失。
    8. 提供一个显式的终止方法。并要求该类的客户端在每个实例不再有用的时候调用这个方法。在一个私有域中记录是否已经被终止。
    9. 显式的finalizer通常与try-finally结合使用,以确保及时终止。
    10. 终结方法的合法用途
      • 当对象的所有者忘记调用显式终止方法时,finalizer可以充当safety net。
      • native peer。本地对等体是一个native object,普通对象通过native method委托给一个本地对象。
    11. finalizer chaining并不会自动执行。
    12. 为每个将被终结的对象创建一个附加的对象。把终结方法放在一个匿名类中,终结它的enclosing instance。该匿名类的单个实例被称为finalizer guardian。

 

    1.  
  • 对于所有对象都通用的方法

第八条:覆盖equals时请遵守通用规定

  1. 如果不覆盖equals方法,类的每个实例都只与它自身相等。
    1. 类的每个实例本质上都是唯一的。
    2. 不关心类是否提供了“逻辑相等(logically equality)”的测试功能。
    3. 超类已经覆盖了equals,从超类继承过来的行为对于子类也是合适的。
    4. 类是私有的或是package,可以确定它的equals方法永远不会被调用。

  1. value class。逻辑相等。
  2. equals的通用规定
    1. equals方法实现了equivalence relation:
      • reflexivity:对于任何非null的引用值x,x.equals(x)必须返回true。对象必须等于其自身。
      • symmetric:对于任何非null的引用值x和y,当且仅当x.equals(y)返回true时,y.equals(x)返回true。任何两个对象对于“它们是否相等”的问题必须保持一致。
      • transitivity传递性:对于任何非null的引用值x/y/z,如果x.equals(y)返回true,y.equals(z)返回true,那么x.equals(z)返回true
      • consistent一致性:对于任何非null的引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,多次调用x.equals(y)会一致返回相同的结果。如果两个对象相等,它们就必须始终保持相等。
      • Non-nullity。对于任何非null的引用值x,x.equals(null)返回false
  3. 一旦违反了equals规定,当其他对象面对你的对象时,这些对象的行为将无法预测。
  4. Liskov substitution principle:一个类型的任何重要属性也将适用于它的子类型。因此为该类编写的任何方法,在其子类型上应同样运行的很好。
  5. getClass()             instanceof
  6. 复合优先于继承。
  7. 不要使equals方法依赖于不可靠的资源。
  8. 除了极少数例外,equals方法应该对内存中的对象执行确定性计算。
  9. 为了测试参数的等同性,equals方法必须先把参数转换成适当的类型,以便调用accessor,或者访问它的域。在进行转换之前,equals方法必须使用instanceof,检查其参数是否为正确的类型。
  10. 实现高质量equals方法的诀窍:
    1. 使用==检查“参数是否为这个对象的引用”。
    2. 使用instanceof 检查“参数是否为正确的类型”
    3. 把参数转换成正确的类型。
    4. 对于该类中的每个significant域,检查参数中的域是否与该对象中对应的域相匹配。
      • 既不是float也不是double的基本类型域,使用==;对象引用域,递归调用equals方法;对于float域,使用Float.compare;对于double,使用Double.compare。对于数组域,每个元素。Arrays.equals()。
      • canonical form
      • 域的比较顺序可能影响equals方法的性能。
    5. 编写完equals方法后,检测:是否对称、是否传递、是否一致。
  11.  
  12. 可以在一个abstract类的子类中增加新的值组件。
  13. 告诫
    1. 覆盖equals时总要覆盖hashCode()
    2. 不要企图让equals方法过于智能
    3. 不要将equals声明中的Object对象替换为其他的类型。overload。

 

第九条:覆盖equals时总要覆盖hashCode

  1. 约定内容
    1. Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCodemethod must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
    2. If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
    3. It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
  2. 简单的解决方法
    1. 把某个非零的常数值,保存在一个名为result的int类型变量中。
    2. 对于对象中每个关键域f(指equals方法中涉及的每个域):
      • 为该域计算int类型的散列码c
        1. 如果该域是boolean类型,则计算(f?1:0)
        2. byte、char、short或int类型,计算(int)f
        3. long类型,计算(int)(f^(f>>>32))
        4. float类型,计算Float.floatToIntBits(f)
        5. double类型,计算Double.doubleToLongBits(f),为得到的long类型值计算散列值
        6. 如果该域是一个对象引用,并且该类的equals方法通过递归的调用equals的方式比较这个域,则同样为这个域递归的调用hashCode。
        7. 如果该域是一个数组,则把每一个元素当做单独的域来处理。
      • 把散列码c合并到result中:
        1. result = 31 * result + c
    3. 返回result
    4. 单元测试验证
  3. 在散列码的计算过程中,可以把redundant field排除在外。
  4. 不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。

 

 

第十条:始终要覆盖toString

  1. 提供好的toString实现可以使类用起来更加舒适。
  2. 在实际应用中,toString应该返回对象中包含的所有值得关注的信息。
  3. 理想情况下,字符串应该是self-explanatory
  4. 无论是否指定格式,都应该在文档中明确表明意图。
  5. 无论是否指定格式,都为toString返回值中包含的所有信息,提供一种编程式的访问途径。

 

 

第十一条:谨慎的覆盖clone

  1. Cloneable接口的目的是作为对象的一个mixin接口,表明这样的对象允许clone。
  2. Cloneable接口缺少一个clone方法,Object的clone方法是受保护的。
  3. 对于Cloneable接口,它改变了超类中受保护的方法的行为。
  4. extralinguistic机制:无需调用构造器就可以创建对象
  5. java.lang.Object的约定内容:
    1. x.clone() != x            true
    2. x.clone().getClass() == x.getClass()         true
    3. x.clone().equals(x)           true。不是绝对的要求
  6. 拷贝对象往往会导致创建类的一个新实例,同时要求拷贝内部的数据结构。这个过程中没有调用构造器。
  7. 如果覆盖了非final类中的clone方法,则应该返回一个通过调用super.clone()得到的对象。
  8. 对于实现了Cloneable的类,总是期望提供一个功能适当的公有的clone方法。
  9. covariant return type。覆盖方法的返回类型可以使被覆盖方法返回类型的子类。
  10. 永远不要让客户去做任何类库能够替客户完成的事情
  11. 实际上,clone方法就是另一个构造器;必须确保不会伤害到原始的对象,并确保正确的创建被克隆对象中的invariant。
  12. clone架构与引用可变对象的final域的正常用法是不相兼容的。
  13. 克隆复杂对象的方法:
    1. iteration
    2. recursion
    3. 先调用super.clone(),然后把结果对象中的所有域都设置成virgin state,然后调用higher-level的方法来重新产生对象的状态。
  14. 如同构造器一样,clone方法不应该在构造过程中,调用新对象中任何非final的方法。
  15. 所有实现了Cloneable接口的类都应该用一个公有的方法覆盖clone。
  16. 另一个实现对象拷贝的好方法是提供一个copy constructor或copy factory。
  17. conversion constructor

 

 

第十二条:考虑实现Comparable接口

  1. 类实现了Comparable接口,就表明它的实例具有内在的natural ordering
  2. Java平台类库中的所有value classes都实现了Comparable接口。
  3. compareTo方法的通用规定:
    1. 该对象小于指定对象, 返回负整数
    2. 等于, 0
    3. 大于, 返回正整数
    4. 无法比较, 抛出ClassCastException
  4. 说明
    1. 必须确保所有的x和y都满足sgn(x.compareTo(y)) == -sgn(y.compareTo(x))
    2. 确保比较关系可传递:(x.compareTo(y) > 0 && y.compareTo(z) > 0)暗示着x.compareTo(z) > 0。
    3. 强烈建议(x.compareTo(y) == 0) == x.equals(y),但并非绝对必要。
  5. 与equals不同的是,compartTo可以对不同类不作比较,可以抛出ClassCastException。
  6. 在通常情况下,应该返回与equals方法同样的结果。
  7. 编写compareTo方法与编写equals方法的差别
    1. Comparable接口是参数化的,comparable方法是静态的类型,因此不必进行类型检查,也不必对其参数进行类型转换。
    2. compareTo方法中的域比较是顺序的比较,而不是同等性的比较。
    3. 如果一个类有多个关键域,从最关键的域开始,逐步进行到所有的重要域。
    4. compareTo方法没有指定返回值的大小,只是指定了返回值的符号。

 

  1.  
  • 类和接口

第十三条:使类和成员的可访问性最小化

  1. 设计良好的模块会隐藏所有的实现细节,把它的API和它的实现清晰的隔离开来。
    1. 可以有效接触各模块之间的耦合关系。
    2. 提高了软件的可重用性
    3. 信息隐藏降低了构建大型系统的风险。
  2. information hiding。encapsulation
  3. access control
  4. 尽可能的使每个类或成员不被外界访问。尽可能最小的访问级别。
  5. 顶层的雷和接口,只有两种可能的访问级别:package、public
  6. accessibility
    1. private
    2. package:default
    3. protected
    4. public
  7. 私有成员和包级私有成员都是一个类的实现中的一部分,一般不会影响它的导出的API
  8. Serializable
  9. protected成员应该尽量少用。
  10. 如果覆盖了超类中的一个方法,子类中的访问级别就不允许低于超类中的访问级别。
  11. 接口中的所有方法都隐含着公有访问级别
  12. 实例域决不能是公有的。
  13. 包含公有可变域的类并不是线程安全的。
  14. 如果final域包含可变对象的引用,它便具有非final域的所有缺点。
  15. 长度非零的数组总是可变的。所以,类具有公有的静态final数组域,或者返回这种域的访问方法,几乎总是错误的。
  16. 修正方法:

 

 

第十四条:在公有类中使用访问方法而非公有域

  1. 如果类可以在包外部进行访问,就提供访问方法。
  2. 如果类是private的,或是私有的嵌套类,直接暴露它的数据域并没有本质的错误。
  3. 公有类不应该直接暴露数据域
  4. 公有类永远不应该暴露可变的域。

 

 

第十五条:使可变性最小化

  1. 不可变类只是其实例不能被修改的类。
  2. Java平台类库中包含许多不可变的类:String、boxed primitive type、BigInteger、BigDecimal
  3. 为了使类成为不可变,要遵循以下五条规则:
    1. 不要提供任何会修改对象状态的方法(mutator)
    2. 保证类不会被扩展。一般做法是使该类称为final的。
    3. 使所有的域都是final的。
    4. 使所有的域都成为private的。
    5. 确保对于任何可变组件的互斥访问。defensive copy。
  4. functional做法。对操作数进行运算但并不修改它。procedural、imperative。
  5. 不可变对象比较简单。
  6. 不可变对象本质上是线程安全的,不要求同步。
  7. 不可变对象可以被自由的共享。永远也不需要进行defensive copy。不应该为不可变的类提供clone方法或copy constructor。
  8. public static final
  9. 不仅可以共享不可变对象,甚至可以共享它们的内部信息。
  10. 不可变对象为其他对象提供了大量的building blocks。
  11. 不可变类唯一的缺点:对于每个不同的值都需要一个单独的对象。
  12. 公有的可变配套类。String类,其可变配套类是StringBuilder。
  13. 为了确保不可变性,类绝对不允许自身被子类化。
    1. 使类成为final的
    2. 让类的所有构造器都变成private或package,并添加公有的static factory来代替公有的构造器。
  14. 保护性copy
  15. 没有一个方法能够对对象的状态产生externally visible的改变。
  16. lazily initialization
  17. 坚决不要为每个get方法编写一个相应的set方法。除非有很好的理由要让类成为可变的类,否则就应该是不可变的。
  18. 不可变类的唯一缺点是在特定的情况下存在潜在的性能问题。
  19. 如果类不能被做成是不可变的,应该尽可能的限制它的可变性。
  20. 除非有令人信服的理由要使域编程非final的,否则要使每个域都是final的。
  21. 构造器应该创建完全初始化的对象,并建立起所有的约束关系。

 

 

第十六条:复合优先于继承

  1. implementation inheritance
  2. 与方法调用不同的是,继承打破了封装性。
  3. 子类必须要跟着超类的更新而演变。
  4. self-use
  5. composition
  6. forwarding
  7. wrapper class
  8. 包装类不适合用在callback framework中。
  9. SELF问题
  10. 只有子类是超类的subtype时,才适合继承。
  11. 继承机制会把超类API中的所有缺陷传播到子类中,而复合则允许设计新的API来隐藏这些缺陷。
  12. 可以用复合和转发机制来代替继承。

 

 

 

 

 

第十七条:要么为继承而设计,并提供文档说明,要么禁止继承

  1.  该类必须有文档可以说明它overridable方法的self-use。
  2.  好的API文档应该描述一个给定的方法做了什么工作,而不是描述它如何做到的。
  3. 类必须通过某种形式提供适当的hook,以便能够进入它的内部工作流程中。
  4. 对于为了继承而设计的类,唯一的测试方法是编写子类。
  5. 必须在发布类之前先编写子类对类进行测试。
  6. 构造器决不能调用可被覆盖的方法。
  7. Cloneable、Serializable
  8. 为了继承而设计类,对这个类会有一些实质性的限制。
  9. skeletal implementation
  10. 对于那些并非为了安全进行子类化而设计和编写文档的类,要禁止子类化。
  11. 禁止子类化的方法
    1. final
    2. 把所有的constructor都变成私有的,或者包级私有的。并增加一些公有的静态工厂来代替构造器。
  12. wrapper class
  13. 确保这个类永远不会调用它的任何可覆盖的方法,并在文档中说明这一点。

 

 

第十八条:接口优于抽象类

  1. Java只允许单继承
  2. 抽象类作为类型定义
    1. 现有的类可以很容易被更新,以实现新的接口。
    2. 接口是定义mixin(混合类型)的理想选择。
    3. 接口允许我们构造非层次结构的类型框架。
  3. combinatorial explosion
  4. wrapper class。接口使得安全的增强类的功能成为可能。
  5. 通过对导出的每个重要接口都提供一个抽象的skeletal implementation,把接口和抽象类的优点结合起来。
  6. AbstractInterface。
  7. AbstractCollection、AbstractSet、AbstractList、AbstractMap。SkeletalCollection。
  8. simulated multiple inheritance
  9. skeletal implementation是为了继承而设计的,应遵从17条中所有关于设计和文档的指导原则。
  10. simple implementation。
  11. 抽象类的演变比接口的演变要容易的多。
  12. 接口一旦被公开发行,并且已被广泛实现,改变这个接口几乎是不可能的。

 

 

第十九条:接口只用于定义类型

  1. constant interface。不包含任何方法,只包含静态的final域。
  2. 常量接口是对接口的不良使用。
  3. 导出常量的合理选择方案:
    1. 如果常量与某个现有的类或接口紧密相关,就应该把这些常量添加到这个类或接口中
    2. 如果这些常量最好被看做枚举类型的成员,用enum type来导出这些常量。
    3. 使用不可实例化的utility class
  4. static import。1.5。
  5. 接口应该只被用来定义类型,不应该被用来导出常量。

 

 

第二十条:类层次优于标签类

  1. 标签类(tagged class)过于冗长、容易出错,并且效率低下。
  2. subtyping:定义能表示多种风格对象的单个数据类型。

 

 

第二十一条:用函数对象表示策略

  1. Strategy模式
  2. function object。其方法执行其他对象上的操作。
  3. strategy interface
  4. 函数指针的主要用途就是实现strategy模式。
  5. 当一个具体策略只被使用一次时,通过匿名类声明和实例化这个具体策略类。当一个具体策略被设计用来重复使用的时候,实现为私有的静态成员类,并通过公有的静态final域被导出。

 

 

第二十二条:优先考虑静态成员类

  1. nested class:为enclosing class提供服务。
  2. 嵌套类有四种:static member class、nonstatic member class、anonymous class、local class
  3. 非静态成员类的每个实例都隐含着与enclosing class的一个enclosing instance相关联。
  4. 如果声明成员类不要求访问enclosing instance,就要始终把static放在其声明中。
  5. 如果省略了static,每个实例都将包含一个指向enclosing object的引用。
  6. 私有静态成员类的一个常见用法是用来代表外围类所代表的对象的组件。
  7. 匿名类:在使用的同时被声明和实例化。
  8. 匿名类的常见用法:
    1. 动态的创建函数对象
    2. 创建process object
    3. 在静态工厂方法内部
  9. 局部类。

 

  1.  
  • 泛型
  1. Java 5中增加了generic。

 

第二十三条:不要在新代码中使用原生态类型

  1. generic类或接口:声明中具有一个或多个type parameter的类或接口。
  2. 泛型类和接口统称为泛型。
  3. 每个泛型都定义一个raw type,即不带任何实际类型参数的泛型名称。
  4. 出错之后应该尽快发现,最好是编译时发现。
  5. 如果使用raw type,就失掉了泛型在安全性和表述性方面的所有优势。
  6. migration compati’bility
  7. 泛型有子类型化(subtyping)的规则。List是raw type List的一个子类型,而不是List的子类型。
  8. 如果使用List这样的raw type,就会失掉类型安全性,如果使用List这样的参数化类型,则不会。
  9. unbounded wildcard type。Set
  10. wildcard类型是安全的,原生态类型是不安全的。
  11. generic method或者bounded wildcard type。
  12. 泛型信息可以在运行时被擦除。
  13. 在class literal中必须使用原生态类型。
  14. 因此在参数化类型而非unbounded wildcard type        上使用instanceof是非法的。
  15. 利用泛型来使用instanceof的首选方法:
  16. Set表示可以包含任何对象类型;Set表示只能包含某种位置对象类型的一个集合;Set则是个raw type,不安全。

     

     

    第二十四条:消除非受检警告

    1. 要尽可能消除每一个非受检警告。
    2. 如果无法消除警告,同时可以证明引起警告的代码时类型安全的(只有在这种情况下,才可以用@SuppressWarnings(“unchecked”))来禁止警告。
    3. 应该在尽可能小的范围中使用SuppressWarnings注解。永远不要在整个类上使用SuppressWarnings。
    4. 每当使用SuppressWarnings(“unchecked”)注解时,都要添加一条注释,说明为什么这么做是安全的。


     

    第二十五条:列表优先于数组

    1. 数组与泛型的不同:
      1. 数组是covariant。如果Sub为Super的子类型,那么Sub[]是Super[]的子类型。

      1. 数组是reified。因此数组在运行时才知道并检查他们的元素类型约束。泛型是通过erasure实现的。
    1. 泛型、数组不能很好的混合使用。
    2. 泛型数组不是类型安全的。
    3. non-reifiable类型是指运行时包含的信息比编译时包含的信息更少的类型。
    4. 唯一可reifiable 参数化类型是unbounded wildcard type。 。创建unbounded wildcard type数组是合法的。
    5. 不要从同步区域调用alien方法
    6. 元素类型信息会在运行时被erase
    7. 不可具体化的类型的数组转换只能在特殊情况下使用。
    8. 数组是covariant且reified;泛型是invariant且可以被erase。

     

     

    第二十六条:优先考虑泛型

    1. 将类泛型化:
      1. 给它的声明添加一个或多个类型参数
      2. 用相应的类型参数替换
    2. 不能创建non-reifiable类型的数组。解决方法:
      1. 直接绕过创建泛型数组的禁令:创建一个Object数组。
      2. 将E[]改为Object[]。
      3. 禁止数组类型的未受检转换比禁止scalar type更加危险。建议采用第二种方案。
    3. 编译器不可能证明你的程序是类型安全的。
    4. 只在包含未受检转换的任务上禁止警告。
    5. 不能创建基本类型的Stack:Stack、Stack
    6.  

    1. 只要时间允许,就把现有的类型都泛型化。

     

     

    第二十七条:优先考虑泛型方法

    1. 静态工具方法尤其适合于泛型化。
    2. 泛型方法的显著特性:无需明确指定类型参数的值。编译器通过检查方法参数的类型来计算类型参数的值。type inference。
    3. generic static factory method。
    4. generic singleton factory。
    5. 递归类型限制:通过某个包含该类型参数本身的表达式来限制类型参数是允许的。Comparable

     

     

    第二十八条:利用有限制通配符来提升API的灵活性

    1. 参数化类型是不可变的
    2. Bounded wildcard type
    3. 每个类型都是自身的子类型
    4. 为了获得最大限度的灵活性,要在标识生产者或消费者的输入参数上使用通配符类型。
    5. PECS:producer-extends consumer-super
    6. 不要用通配符类型作为返回类型
    7. 如果类的用户必须考虑通配符类型,类的API或许会出错
    8. 显式的类型参数
    9. 使用时始终应该是Comparable优先于Comparable
    10. 使用时始终应该是Comparator优先于Comparator
    11. 类型参数和通配符之间具有双重性
    12. 如果类型参数只在方法声明中出现一次,就可以用通配符取代

     

     

    第二十九条:优先考虑类型安全的异构容器

    1. 将key进行参数化而不是将container参数化
    2. 有限制的类型令牌(bounded type token)
    3. 注解API广泛利用了有限制的类型令牌
    4. asSubclass

     

     

     

     

    第六章 枚举和注解

    第三十条:用enum代替int常量

    1. Int枚举是编译时常量
    2. 通过公有的静态final域为每个枚举常量导出实例的类。
    3. 枚举类型是实例可控的。
    4. 枚举类型为类型安全的枚举模式提供了语言方面的支持。
    5. 枚举提供了编译时的类型安全。
    6. 特定于常量的方法实现(constant-specific method implementation)
    7. 枚举类型中的抽象方法必须被其所有常量中的举提方法覆盖
    8. 枚举构造器不可以访问枚举的静态域,除了编译时常量域
    9. 策略枚举(strategy enum):多个枚举常量同时共享相同的行为。
    10. 枚举中的switch语句适合于给外部的枚举类型增加特定于常量的行为。

     

     

    第三十一条:用实例域代替序数

    1. 永远不要根据枚举的序数导出与他关联的值,而要将其保存在一个实力域中。
    2. 最好完全避免使用ordinal方法

     

     

    第三十二条:用EnumSet代替位域

    1. Bit field
    2. 正是因为枚举类型要用在Set中,所以没有理由用位域来表示它。

     

     

     

    第三十三条:用EnumMap代替序数索引

    1. int不能提供枚举的类型安全
    2. java.util.EnumMap:专门用于枚举键
    3. EnumMap在内部使用了序数索引的数组
    4. 最好不要使用序数来索引数组,而要使用EnumMap
    5. 多维。EnumMap<.., EnumMap<…>>
    6. 一般情况下不适用Enum.ordinal

     

     

    第三十四条:用接口模拟可伸缩的枚举

    1. operation code
    2. 枚举类型不是可扩展的
    3. 用接口模拟可伸缩枚举的不足:无法将实现从一个枚举类型继承到另一个枚举类型
    4. 虽然无法编写可扩展的枚举类型,却可以通过编写接口以及实现该接口的基础枚举类型,进行模拟。

     

     

    第三十五条:注解优先于命名模式

    1. 命名模式(naming pattern)的缺点:
      1. 文字拼写错误会导致失败,且没有任何提示
      2. 无法确保他们只用于相应的程序元素上
      3. 没有提供将参数值与程序元素关联起来的好方法
    2. Meta-annotation
    3. Marker annotation
    4. 注解永远不会改变被注解代码的语义
    5. 所有的程序员都应该使用Java平台所提供的预定义的注解类型。

     

     

    第三十六条:坚持使用Override注解

    1. 在想要覆盖超类声明的每个方法声明中使用Override注解
    2. Code inspection
    3. 在具体的类中,不必标注确信覆盖了抽象方法声明的方法

     

     

    第三十七条:用标记接口定义类型

    1. marker interface:没有包含方法声明的接口,而只是指明一个类实现了具有某种属性的接口。
    2.  标记接口有两点胜过标记注解:
      1. 标记接口定义的类型是由被标记类的实例实现的
      2. 可以被更精确的进行锁定
    3. Set接口(restricted marker interface)
    4. 标记注解胜过标记接口的优点:
      1. 最大优点:可以通过默认的方式添加一个或多个注解类型元素,给已被使用的注解类型添加更多的信息。
      2. 更大的注解机制的一部分

     

     

    第七章 方法

    第三十八条:检查参数有效性

    1. 对于公有方法,要用Javadoc的@throws标签在文档中说明违法参数值限制时会抛出的异常。
    2. 非公有的方法通常使用assertion来检查他们的参数
    3. 失败原子性(failure atomicity)
    4. 异常转译(exception translation):将计算过程中抛出的异常转换为正确的异常

     

     

    第三十九条:必要时进行保护性拷贝

    1. 对于构造器的每个可变参数进行保护性拷贝(defensive copy)是必要的。
    2. 保护性拷贝是在检查参数的有效性之前进行的,并且有效性检查是针对拷贝之后的对象,而不是针对原始的对象
    3. Time-Of-Check/Time-Of-Use。TOCTOU攻击:从检查参数开始,直到拷贝参数之间的时间段。
    4. 对于参数类型可以被不可信任方子类化的参数,不要使用clone方法进行保护性拷贝。
    5. 返回可变内部域的保护性拷贝:
    6. 访问方法与构造器不同,在进行保护性拷贝的时候允许使用clone方法
    7. 一般情况下,最好使用构造器或静态工厂
    8. java.util.Date是非final的,不能保证clone方法一定返回类为java.util.Date的对象。
    9. 长度非零的数组总是可变的。在把内部数组返回给客户端之前,应该总要进行保护性拷贝。或返回immutable view。
    10. 尽量使用不可变的对象作为对象内部的组件
    11. 使用Date.getTime()返回的long基本类型作为内部的时间表示法
    12. 如果类具有从客户端得到或返回到客户端的可变组件,类就必须保护性的拷贝这些组件。

     

     

    第四十条:谨慎设计方法签名

    1. 谨慎的选择方法的名称
    2. 不要过于追求提供便利的方法
    3. 避免过长的参数列表。目标是四个,或更少。
    4. 相同类型的长参数序列格外有害。
    5. 缩短过长参数列表的方法:
      1. 把方法分解成多个方法
      2. 创建辅助类,保存参数的分组。一般为静态成员类。
      3. 从对象构建到方法调用都采用Builder模式
    6. power-to-weight比
    7. 对于参数类型,要优先使用接口而不是类
    8. 对于boolean参数,要优先使用两个元素的枚举类型。

     

     

    第四十一条:慎用重载

    1. 重载(overloading)调用是在编译时做出决定的。选择的依据是对象的运行时类型。
    2. 对于overloaded method的选择是静态的,而对于overloadden 方法的选择是动态的
    3. 当一个子类包含的方法声明与祖先类中的方法声明具有相同的签名时,方法就被覆盖了。
    4. 选择overridden方法的正确版本是在运行时进行的。
    5. 当调用被覆盖的方法时,对象的编译时类型不会影响哪个方法将被执行。most specific版本总被执行。
    6. 覆盖机制是规范,而重载机制是例外
    7. 避免胡乱使用重载机制
    8. 安全而保守的策略是:永远不要导出两个具有相同参数数目的重载方法
    9. 命名模式
    10. 构造器不可能被覆盖
    11. List接口有两个重载的remove方法:remove(E)和remove(int)
    12. Java语言中添加了泛型和自动装箱后,破坏了List接口
    13. String类导出两个重载的静态工厂方法:valueOf(cahr[])/valueOf(Object)

     

     

     

    第四十二条:慎用可变参数

    1. varargs。variable arity method。接受0个或多个指定类型的参数。数组。
    2. printf和反射机制都可从可变参数中极大的受益
    3. Arrays.toString(),专门为了将任何类型的数组转换成字符串而设计的
    4. 不必改造具有final数组参数的每个方法;只当确实在数量不定的值上执行调用时才使用可变参数
    5. 在重视性能的情况下,使用可变参数机制要特别小心
    6.  
    7. EnumSet

     

     

     

     

    第四十三条:返回零长度的数组或集合,而不是null

    1. 需要专门的方法来处理null返回值
    2. 零长度数组不可变
    3. Collections.emptySet/emptyList/emptyMap
    4. 返回类型为数组或集合的方法没理由返回null,而是返回一个零长度的数组或集合。
    5. 在C语言中,数组长度是与实际的数组分开返回的。

     

     

     

    第四十四条:为所有导出的API元素编写文档注释

    1. Javadoc。利用特殊格式的文档注释(doc comment),根据源码自动生成API文档
    2. @literal
    3. @code
    4. 为了正确的编写API文档,必须在每个被导出的类/接口/构造器/方法和域声明之前增加一个文档注释
    5. 方法的文档注释应该简洁的描述出它和客户端之间的约定。说明方法做了什么,列举出该方法的前置条件(precondition)/后置条件(postcondition)
      1. precondition:@throws,针对未受检的异常所隐含描述的。每个未受检的异常都对应一个precondition violation
      2. postcondition
      3. side effect
      4. thread safety
      5. 让每个参数都有一个@param标签. 紧跟名词短语。描述表示的值。
      6. @return(除非返回类型为void)。紧跟名词短语。描述表示的值。
      7. @throws:受检的/未受检的异常。if。描述异常将在什么样的条件下会被抛出。
    6. Javadoc工具会把文档注释翻译成HTML,文档注释中包含的HTML元素会出现在结果HTML文档中。
    7. ({@code}):以代码字体(code font)呈现;允许使用”<”
    8. {@code 代码}
    9. 文档注释在源码和产生的文档都应该易于阅读。如果无法实现,产生的文档可读性要优于源码的可读性。
    10. 每个文档注释的第一句话为summary description。
    11. 同一个类或接口中的两个成员或构造器,不应该具有同样的概要描述。重载!
    12. 对于方法和构造器,概要描述应该是个完整的动词短语(包含任何对象)
    13. 对于类/接口和域,概要描述应该是一个名词短语,描述了该类或接口的实例,或域本身代表的事物。
    14. 当为泛型或方法编写文档时,说明所有的类型参数
    15. 为枚举类型编写文档时,说明常量以及类型,还有任何公有的方法。
    16. 为注释类型编写文档时,要确保在文档中说明所有成员,以及类型本身。
    17. 包级私有的文档注释应放在package-info.java中
    18. 在文档中对类的线程安全级别进行说明
    19. 在文档中说明类的序列化形式。
    20. Javadoc具有继承方法注释的能力。
    21. 接口的文档注释优先于超类的文档注释。
    22. {@inheritDoc}
    23. 通过一个HTML有效性检查器(HTML validity checker)来运行由Javadoc产生的HTML文件
    24. W3C-validator
    25. 在文档注释内部出现任何HTML标签都是允许的,但必须经过转义。

     

     

    第八章 通用程序设计

    第四十五条:将局部变量的作用域最小化

    1. C语言要求局部变量必须在代码块开头处进行声明
    2. Java允许在任何可以出现语句的地方生命变量
    3. 要使局部变量的作用域最小化,在第一次使用的地方声明。
    4. 每个局部变量的声明都应该包含一个初始化表达式
    5. loop variable
    6. 如果在循环终止之后不再需要循环变量的内容,for循环优先于while循环。
    7. 使方法小而集中

     

     

     

    第四十六条:for-each循环优先于for循环

    1. 迭代器和索引变量会造成一些混乱
    2. 对数组索引的边界值只计算一次。
    3. 嵌套式迭代
    4. for-each循环没有性能损失
    5. 有三种情况无法使用for-each循环:
      1. 过滤:使用显式的迭代器
      2. 转换:使用列表迭代器或数组索引
      3. 平行迭代:显式的控制迭代器或索引变量
    6.  

     

     

     

     

    第四十七条:了解和使用类库

    1. 缺点:
      1. 产生的随机数序列将会重复
      2. 有些数会比其他的数出现的更频繁
      3. 极少数情况下,返回一个落在指定范围之外的数。
    2. 使用类库的好处:
      1. 通过使用标准类库,可以充分利用这些编写标准类库的专家的只是,以及其他人的使用经验
      2. 不必浪费时间在与工作不太相关的问题上
      3. 性能往往会随着时间推移而提高
      4. 可以使自己的代码融入主流
    3. 与新特性保持同步
    4. 应该熟悉java.lang/java.util/java.io
    5. Collections Framework
    6. java.util.concurrent。并发实用工具。
    7. 不要重新发明轮子。

     

     

     

     

     

    第四十八条:如果需要精确的答案,避免使用float和double

    1. float和double执行二进制浮点运算(binary floating-point arithmetic)
    2. 为了在广泛的数值范围上提供较为精确的快速近似计算。但没有提供完全精确的结果。
    3. float和double尤其不适合用于货币计算。
    4. 使用BigDecimal/int/long进行货币计算
    5. 使用BigDecimal的缺点:
      1. 不方便
      2. 很慢
    6. 如果数值范围没有超过9位十进制数字,int;不超过18位数字,使用long;超过18位数字,必须使用BigDecimal。

     

     

     

     

    第四十九条:基本类型优先于装箱类型

    1. Java类型系统
      1. 基本类型(primitive type)
      2. 引用类型(reference type)
    2. 每个基本类型对应一个引用类型,称作boxed primitive
    3. int/double/Boolean对应Int/Double/Boolean
    4. 基本类型和装箱基本类型的区别
      1. 基本类型只有值,而装箱基本类型具有与其值不同的同一性。两个装箱基本类型可以具有相同的值和不同的同一性。
      2. 基本类型只有功能完备的值,而装箱基本类型还有个非功能值:null
      3. 基本类型通常比装箱基本类型更节省时间和空间
    5. 对包装类型运用==几乎总时错误的。
    6. 当在一项操作中混合使用基本类型和装箱基本类型时,装箱基本类型就会自动拆箱。
    7. 装箱基本类型合理的用处:
      1. 作为集合中的元素/键和值。
      2. 在参数化类型中,必须使用装箱基本类型作为类型参数。
      3. 在进行反射的方法调用中,必须使用装箱基本类型。
    8. 自动装箱减少了使用装箱基本类型的繁琐性,但没有减少其风险。

     

     

     

    第五十条:如果其他类型更合适,则尽量避免使用字符串

    1. 不应该使用字符串的情形:
      1. 字符串不适合代替其他的值类型
      2. 字符串不适合代替枚举类型
      3. 字符串不适合代替聚集类型
        1. 私有的静态成员类
      4. 字符串不适合代替能力表(capabilities)
        1. 线程局部变量(thread-local variable)
        2. 用一个不可伪造的键(unforgeable key)
        3. 类型安全
    2. 经常被错误的用字符串代替的类型包括基本类型/枚举类型和聚集类型

     

     

     

    第五十一条:当心字符串连接的性能

    1. 为连接n个字符串而重复使用字符串连接操作符,需要n的平方级时间
    2. 字符串不可变
    3. 为了获取可以接受的性能,使用StringBuilder替代String。
    4. 或者使用字符数组,或者每次只处理一个字符串,而不是组合起来。

     

     

     

     

    第五十二条:通过接口引用对象

    1. 优先使用接口而不是类来引用对象
    2. 如果有合适的接口类型存在,那么对于参数/返回值/变量和域,就都应该使用接口进行声明
    3. 如果依赖于实现的任何特殊属性,就要在声明变量的地方给这些需求建立相应的文档说明。
    4. ThreadLocal
    5. 如果没有合适的接口存在,完全可以用类而不是接口来引用对象。
    6. value class
    7. Random
    8. 如果对象属于基于类的框架(class-based framework),使用相关的base class,而不是实现类。
    9. java.util.TimerTask

     

     

     

     

    第五十三条:接口优先于反射机制

    1. core reflection facility。java.lang.reflect。提供了通过程序来访问关于已装载的类的信息的能力。通过程序访问类的成员名称/域类型/方法签名等信息。
    2. Method.invoke可以调用任何类的任何对象的任何方法(遵从常规的安全限制)
    3. 反射机制允许一个类使用另一个类,即使当前被编译时后者还不存在。代价:
      1. 丧失了编译时类型检查的好处
      2. 执行反射访问所需的代码非常笨拙和冗长
      3. 性能损失
    4. 核心反射机制最初是为了基于组件的应用创建工具而设计的。
    5. 反射功能只是在design time被用到。通常,普通应用程序在运行时不应该以反射方式访问对象。
    6. RPC(远程过程调用)
    7. 如果以非常有限的方式使用反射机制,虽然也要付出少许代价,但是可以获得许多好处。
    8. 以反射方式创建实例,然后通过他们的接口或超类,以正常的方式访问这些实例。
    9. 如果适当的构造器不带参数,可以使用Class.newInstance方法
    10. TreeSet会按照字母顺序排序
    11. System.exit()。终止整个VM,对于命令行有效性的非法终止很合适
    12. 类对于在运行时可能不存在的其他类/方法或域的依赖性,用反射法进行管理,合理但很少使用。
    13. 如果编写的程序必须要与编译时未知的类一起工作,就应仅使用反射机制来实例化对象,访问对象时则使用编译时已知的某个接口或超类。

     

     

     

    第五十四条:谨慎的使用本地方法

    1. JNI(Java Native Interface)允许Java应用程序调用native method(本地程序设计语言,C/C++)
    2. 本地方法的用途:
      1. 提供访问特定于平台机制的能力;
      2. 提供访问遗留代码的能力(legacy data)
      3. 可通过本地语言,编写应用程序中注重性能的部分
    3. 使用本地方法来提高性能的做法不值得支持
    4. 使用本地方法的缺点:
      1. 不安全
      2. 不可移植
      3. 更难调试
      4. 需要胶合代码的本地方法难以阅读
    5.  

     

     

    第五十五条:谨慎的进行优化

    1. 要努力编写好的程序而不是快的程序
    2. 必须在设计过程中考虑到性能问题
    3. 努力避免那些限制性能的设计决策
    4. 要考虑API设计决策的性能后果
    5. 为获得好的性能对API进行包装
    6. 不要优化。在每次试图优化之前和之后,要对性能进行测量。
    7. 程序把80%的时间花在20%的代码上
    8. 性能剖析工具
    9. Java程序设计语言没有很强的performance model。
    10. 程序员编写的代码与CPU执行的代码之间存在semantic gap。
    11. 不要费力去写快速的程序,应该努力编写好的程序。

     

     

     

    第五十六条:遵守普遍接受的命名惯例

    1. naming convention
    2. 包的名称应该是层次状的,用句号分割每个部分。
    3. 标准类库和一些可选的包,以java和javax开头。
    4. 包名称的其余部分应该包括一个或多个描述该包的组成部分。
    5. Java语言没有提供对包层次的支持
    6. 类和接口的名称,包括枚举和注解类型的名称,应该包括一个或多个单词,每个单词的首字母大写。
    7. 方法或域的名称的第一个字母应该小写。
    8. 常量域应该包含一个或多个大写的单词,中间用下划线隔开。常量域是唯一推荐使用下滑线的情形。
    9. 局部变量名称允许缩写。
    10. 类型参数名称通常由单个字母组成,T表示任意的类型,E表示集合的元素类型,K和V表示映射的键和值类型,X表示异常。

     

     

    第九章 异常

    第五十七条:只针对异常的情况才使用异常

    1. 三个错误:
      1. 异常机制设计的初衷是用于不正常的情形,很少会有JVM试图对其进行优化
      2. 把代码放在try-catch块中反而组织了JVM实现本来可能要执行的某些特定优化
      3. 对数组进行遍历的标准模式不会导致冗余检查
    2. 在现代JVM实现上,基于异常的模式比标准模式要慢得多
    3. 异常应该只用于异常的情况,不应用于正常的控制流
    4. 设计良好的API不应该强迫客户端为了正常的控制流而使用异常。
    5. state-dependent
    6. 状态测试方法和可识别的返回值。状态测试提供了更好的可读性。

     

     

     

     

     

     

    第五十八条:对可恢复的情况使用受检异常,对编程错误使用运行时异常

    1. Java程序设计语言提供了三种可抛出结构(throwable):
      1. 受检的异常(checked exception)
      2. 运行时异常(run-time exception)
      3. 错误(error)
    2. 如果期望调用者能够适当的恢复,应该使用受检的异常
    3. 有两种未受检的可抛出结构:运行时异常和错误。不需要也不应该被捕获。
    4. 用运行时异常来表明编程错误
    5. precondition violation
    6. 实现的所有未受检的抛出结构都应该是RuntimeException的子类(直接的或间接的)
    7. 对于可恢复的情况,使用受检的异常;对于程序错误,使用运行时异常
    8. 解析异常的字符串表示法的代码可能是不可移植的,也是非常脆弱的

     

     

     

     

    第五十九条:避免不必要的使用受检的异常

    1. 如果方法抛出一个或多个受检的异常,调用该方法的代码就必须在一个或多个catch块中处理这些异常,或声明它抛出这些异常,并让它们传播出去。
    2. 把受检的异常变成未受检的异常的方法:把抛出异常的方法分成两个方法,一个返回boolean,表明是否应该抛出异常。

     

     

     

     

    第六十条:优先使用标准的异常

    1. 重用现有异常的好处:
      1. 使API更易于学习和使用
      2. 可读性更好
      3. 异常类越少,意味着内存footprint越小,装载这些类的时间开销越小。
    2. IllegalArgumentException:调用者传递的参数值不合适
    3. IllegalStateException:因为接收对象的状态而使调用非法
    4. ConcurrentModificationException:如果一个对象被设计位专用于单线程或与外部同步机制配合使用,一旦发现它正在或已经被并发的修改,就抛出此异常
    5. UnsuportedOperationException:如果对象不支持所请求的操作
    6. NullPointerException
    7. IndexOutOfBoundsException

     

     

     

     

     

    第六十一条:抛出与抽象相对应的异常

    1. 异常转译(exception translation):更高层的实现应该捕获低层的异常,同时抛出可以按照高层抽象进行解释的异常。
    2. 异常链(exception chaining):低层的异常(原因)被传到高层的异常,高层的异常提供访问方法来获得低层的异常
    3. 高层异常的构造器将原因传到支持链(chaining-aware)的超级构造器,最终被传给Throwable的其中一个运行异常链的构造器。
    4. 尽管异常转译与不加选择的从低层传递异常的做法相比有所改进,但不能被滥用
    5. 在给低层传递参数之前,检查更高层方法的参数的有效性,从而避免低层方法抛出异常

     

     

     

     

     

     

    第六十二条:每个方法抛出的异常都要有文档

    1. 始终要单独的声明受检的异常,并且利用Javadoc的@throws标记,准确的记录下抛出每个异常的条件
    2. 方法被执行成功的precondition
    3. 使用Javadoc的@throws标签记录下一个方法可能抛出的每个未受检异常,但是不要使用throws将未受检的异常包含在方法的声明中
    4. 如果一个类中的许多方法出于同样的原因抛出同一个异常,在该类的文档注释中对这个异常建立文档,这是可以接受的。
    5. 要为编写的每个方法所能抛出的每个异常建立文档
    6. 要为每个受检异常提供单独的throws子句,不要为未受检的异常提供throws子句。

     

     

     

     

     

     

    第六十三条:在细节消息中包含能捕获失败的信息

    1. 异常类型的toString方法应该尽可能多的返回有关失败原因的信息。异常的细节消息应该捕获住失败,便于以后分析
    2. 为了捕获失败,异常的细节信息应该包含所有“对该异常有贡献”的参数和域的值
    3. 堆栈轨迹的用途是与源文件结合起来分析,通常包含抛出该异常的确切文件和行数,以及对战中所有其他方法调用所在的文件和行数。
    4. 异常的字符串表示法,主要是让程序员或域服务人员来分析失败的原因。
    5. 在异常的构造器而不是字符串细节消息中引入这些消息,确保在异常的细节消息中包含足够的能捕获失败的消息。
    6. 为异常的失败捕获信息提供一些访问方法是合适的

     

     

     

     

    第六十四条:努力使失败保持原子性

    1. 一般而言,失败的方法调用应该使对象保持在被调用之前的状态。具有这种属性的方法称为具有失败原子性(failure atomic)

    3.

    4. 获得失败原子性的方法:

    1. 在执行操作之气那检查参数的有效性。
    2. 调整计算处理过程的顺序,使得任何可能会失败的计算部分都在对象状态被修改之前发生。       
    3. 编写一段回复代码(recovery code),拦截操作过程中发生的失败。,并使对象回滚到操作之前的状态。主要用于永久性的(disk-based)数据结构。
    4. 在对象的一份临时拷贝上执行操作,操作完成之后,再用临时拷贝中的结果替代对象的内容。
    1. 错误相对于异常通常是不可恢复的,当方法抛出错误时,不需要努力保持失败原子性。
    2.  

     

     

     

     

    第六十五条:不要忽略异常

    1. 空的catch块会使异常达不到应有的目的。
    2. 至少,catch块也应该包含一条说明,解释为什么可以忽略这个异常。
    3. 关闭FileInputStream时可以忽略异常。                                                                          

    第十章 并发

    1. 线程(Thread)

     

    第六十六条:同步访问共享的可变数据

    1. synchronized:保证在同一时刻,只有一个线程可以执行某一个方法,或某一个代码块
    2. 同步不仅可以组织一个线程看到对象处于不一致的状态之中,还可以保证进入同步方法或同步代码块的每个线程,都看到由同一个锁保护的之前所有的修改效果。
    3. JLS保证读或写一个变量是原子的(atomic),除非这个变量是long或double。
    4. 为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的。
    5. JLS的内存模型规定一个线程所做的变化对其他线程可见
    6. 不要使用Thread.stop
    7. boolean域的读和写操作都是原子的
    8. 如果读和写没有都被同步,同步就不会起作用。
    9. volatile不执行互斥访问,但可以保证任何一个线程在读取该域时将看到最近刚刚被写入的值
    10. 增量操作符(++)不是原子的
    11. safety failure
    12. 将可变数据限制在单个线程中
    13. effectively immutable
    14. safe publication
    15. 当多个线程共享可变数据的时候,每个读或写数据的线程必须执行同步。
    16. liveness failure
    17. safety failure
    18. volatile:只需要线程之间的交互通信,而不需要互斥

     

     

     

     

    第六十七条:避免过度同步

    1. 为了避免活性失败和安全性失败,在一个被同步的方法或代码块中,永远不要放弃对客户端的控制。
    2. Observer模式
    3. 试图在遍历列表的过程中,将一个元素从列表中删除,这是非法的。
    4. executor service
    5. Java程序设计语言中的锁是可重入的(reentrant),这种调用不会死锁。
    6. 并发集合(concurrent collection)。CopyOnWireArrayList。由于内部数组永远不改动,因此迭代不需要锁定,速度也非常快。
    7. open call:在同步区域之外被调用的外来方法
    8. 应该在同步区域内做尽可能少的工作。
    9. 如果一个可变的类要并发使用,应该使这个类变成是线程安全的。
    10. 不确定时,不要同步你的类,而是应该建立文档,注明它不是线程安全的。
    11. 如果方法修改了静态域,那么必须同步对这个域的访问,即使仅仅只用于单个线程。
    12. 为了避免死锁和数据破坏,不要从同步区域内部调用外来方法。
    13. 尽量限制同步区域内部的工作量。                                                                                           

     

     

     

     

    第六十八条:executor和task优先于线程

    1. work queue
    2. java.util.concurrent
    3. Executor Framework
    4. 线程池(thread pool)。
    5. java.util.concurrent.Executors
    6. Executors.newCachedThreadPool
    7. 在缓存的线程池中,被提交的任务没有排成队列,而是直接交给线程执行。
    8. 在大负载的产品服务器,最好使用Executors.newFixedThreadPool
    9. task。分为runnable和callable(有返回值)。
    10. Executor Framework所做的工作是执行,Collections Framework所做的工作是aggregation。
    11. ScheduledThreadPoolExecutor
    12.  

     

     

     

    第六十九条:并发工具优先于wait和notify

    1. java.util.concurrent中更高级的工具分为三类:
      1. Executor Framework
      2. 并发集合(Concurrent Collection)
      3. 同步器(Synchronizer)
    2. 并发集合为标准的集合接口(Set/Queue/Map)提供了高性能的并发实现。
    3. 并发集合中不可能排除并发活动;将其锁定没有什么作用,只会使程序速度变慢。
    4. String.intern必须使用某种弱引用,来避免随着时间的推移发生内存泄漏。
    5. 优先使用ConcurrentHashMap,而不是使用Collections.synchronizedMap或Hashtable
    6. 应该优先使用并发集合,而不是使用外部同步的集合。
    7. blocking operation
    8. producer-consumer queue
    9. Synchronizer:使线程能够等待另一个线程的对象
    10. CountDownLatch和Semaphore
    11. CountdownLatch
    12. thread starvation deadlock
    13. 对于间歇式的定时,始终应该优先使用System.nanoTime,而不是使用System.currentTimeMills。System.nanoTime更加准确也更加精确,不受系统的实时时钟的调整所影响。
    14. wait方法用来使线程等待某个条件,必须在同步区域内部被调用,这个同步区域将对象锁定在了调用wait方法的对象上。
    15. 始终应该使用wait循环模式来调用wait方法,永远不要在循环之外调用wait方法。
    16. spurious wakeup
    17. notify唤醒的是单个正在等待的线程,notifyAll唤醒的是所有正在等待的线程。
    18.  

     

     

     

    第七十条:线程安全性的文档化

    1. 在一个方法声明中出现synchronized修饰符,是个实现细节,不是导出的API的一部分。不一定表明这个方法是线程安全的。
    2. 线程安全有多种级别。一个类为了可被多个线程安全的使用,必须在文档中说明它所支持的线程安全性级别。
    3. 线程安全性的级别:
      1. 不可变的(immutable):类的实例不可变。不需要外部的同步。String/Long//BigInteger。
      2. 无条件的线程安全(unconditionally thread-safe):类的实例可变,但有足够的内部同步。其实例可以被并发使用,无需任何外部同步。Random和ConcurrentHashMap。
      3. 有条件的线程安全(conditionally thread-safe):Collections.synchronize包装返回的集合,其iterator要求外部同步。
      4. 非线程安全(not thread-safe):类的实例可变。为了并发使用,必须利用选择的外部同步包围每个方法调用。通用的集合实现,ArrayList和HashMap。
      5. 线程对立的(thread-hostile):类不能安全的被多个线程并发使用。
    4. private lock object
    5. 私有锁对象模式只能用在无条件的线程安全类上。
    6.  

     

     

    第七十一条:慎用延迟初始化(lazy initialization)

    1. 除非绝对必要,否则不要这么做。
    2. 在大多数情况下,正常的初始化要优于延迟初始化。
    3. 如果利用延迟优化来破坏初始化的循环,就要使用同步访问方法。
    4. 如果出于性能的考虑需要对静态域使用延迟初始化,就使用lazy initialization holder class模式。
    5. 如果出于性能的考虑需要对实例域使用延迟初始化,使用双重检查模式(double-check idiom)。
    6. 单重检查模式(single-check idiom)
    7. racy single-check idiom
    8. 对于实例域,使用double-check idiom;对于静态域,使用lazy initialization holder class idiom;对于可以接收重复初始化的实例域,考虑使用single-check idiom。

     

     

     

     

    第七十二条:不要依赖于线程调度器

    1. 任何依赖于线程调度器来达到正确性或性能要求的程序,很有可能都是不可移植的
    2. 确保可运行线程的平均数量不明显多于处理器的数量。
    3. 如果线程没有在做有意义的工作,就不应该运行。

     

     

     

    第七十三条:避免使用线程组

     

    第十一章:序列化

    第七十四条:谨慎的实现Serializable接口

    1. 类的唯一标识符(stream unique identifier)
    2. 序列版本UID(Serial Version UID)
    3. 实现Serializable接口的代价:
      1. 一旦一个类被发布,就大大降低了改变这个类的实现的灵活性
      2. 它增加了出现Bug和安全漏洞的可能性。反序列化机制没有显式的构造器。
      3. 随着类发行新的版本,相关的测试负担也增加了。
    4. 实现Serializable接口并不是一个很轻松就可以做出的决定。
    5. 代表活动实体的类,比如线程池,一般不应该实现Serializable。
    6. 为了继承而设计的类应尽可能少的去实现Serializable接口,用户的接口也应该尽可能少的继承Serializable接口。
    7. 在为了继承而设计的类中,真正实现了Serializable接口的有Throwable/Component/HttpServlet抽象类。
    8. 如果一个专门为了继承而设计的类不是可序列化的,就不可能编写出可序列化的子类。
    9. 对于为继承而设计的不可序列化的类,应该考虑提供一个无参构造器。
    10. 最好在所有的约束关系都已建立的情况下再创建对象。
    11. inner class不应该实现Serializable
    12. 内部类的默认序列化形式是定义不清楚的。
    13. static member class可以实现Serializable接口

     

     

     

     

    第七十五条:考虑使用自定义的序列化形式

    1. 如果没有先认真考虑默认的序列化形式是否合适,则不要贸然接受。
    2. 对于一个对象来说,理想的序列化形式应该只包含该对象所表示的逻辑数据
    3. 如果一个对象的物理表示法等同于它的逻辑内容,可能就适合使用默认的序列化形式。
    4. 即使确定了默认的序列化形式是合适的,通常还必须提供一个readObject方法以保证约束关系和安全性。
    5. 当一个对象的物理表示法与他的逻辑数据内容有实质性的区别时,使用默认序列化形式会有以下4个缺点:
      1. 使这个类的导出API永远束缚在该类的内部表示法上。
      2. 会消耗过多的空间
      3. 会消耗过多的时间
      4. 会引起栈溢出。
    6. transient表示这个实例域将从一个类的默认序列化形式中省略掉。
    7. 如果所有的实例域都是瞬时的,从技术角度而言,不调用defaultReadObject和defaultWriteObject也是允许的,但不推荐这么做。
    8. 在决定将一个域做成非transient之前,一定要确信它的值将是该对象逻辑状态的一部分
    9. 如果在读取整个对象状态的任何其他方法上强制任何同步,必须在对象序列化上强制任何同步。
    10. 不管选择那种序列化形式,都要编写每个可序列化的类声明一个显式的序列版本UID。
    11. serialver

     

     

     

     

    第七十六条:保护性的编写readObject方法

    1. 当一个对象被反序列化的时候,对于客户端不应该拥有的对象引用,如果哪个域包含了这样的对象引用,必须做保护性拷贝。
    2. 不要使用writeUnshared和readUnshared方法
    3. 序列化代理模式(serialization proxy pattern)
    4. 指导方针:
      1. 对于对象引用域必须保持为私有的类,要保护性的拷贝这些域中的每个对象。不可变类的可变组件属于这一类别。
      2. 对于任何约束条件,如果检查失败,则抛出一个InvalidObjectException异常。这些检查动作跟在所有的保护性拷贝之后。
      3. 如果整个对象试图在被反序列化之后必须进行验证,就应该使用ObjectInputValidation接口。
      4. 无论是直接方式还是间接方式,都不要调用类中任何可被覆盖的方法。

     

     

     

     

    第七十七条:对于实例控制,枚举类型优先于readResolve

    1. 任何一个readObject方法,不管是显式的还是默认的,都会返回一个新建的实例,不同于该类初始化时创建的实例。
    2. readResolve特性允许使用readObject创建的实例代替另一个实例。
    3. 如果依赖readResolve进行实例空hi在,带有对象引用类型的所有实例域必须声明为transient的。
    4. 如果将一个可序列化的实例受控的类编写成枚举,就可以保证除了所声明的常量之外,不会有别的实现。
    5. readResolve的accessibility很重要。

     

     

     

    第七十八条:考虑用序列化代理代替序列化实例

    1. 序列化代理模式(Serialization proxy pattern)
    2. 序列化代理(serialization proxy):为可序列化的类设计的一个私有的静态嵌套类,精确的表示外围类的实例的逻辑状态。
    3. 序列化代理方法可以伪字节流的攻击以及内部域的盗用攻击。
    4. 序列化代理模式允许反序列化实例有着与原始序列化实例不同的类。
    5. EnumSet使用序列化代理模式。
    6. 序列化模式的局限性:
      1. 不能与可以被客户端扩展的类兼容
      2. 不能与对象图中包含循环的某些类兼容

     

     

    1.  

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    你可能感兴趣的:(java)