Effect Java 读书笔记 — 引言与创建和销毁对象

第1章 引言

  1. 代码应该被重用,而不是被拷贝
  2. 模块之间的依赖性尽可能降到最小
  3. Java语言支持四种类型:接口、类、数据和基本类型
  4. 类的成员由它的field、method、member class和member interface组成
  5. 方法签名(signature)由它的名称和所有参数类型组成,签名不包括它的返回类型
  6. 类、接口、构造器、成员以及序列化形式被统称为API元素(API Element)。导出的API由所有可在定义该API的包之外访问的API元素组成
  7. 一个包的导出API(exported API)是由该包中的每个公有(public)类或者接口中所有公有的或者受保护的(protected)成员和构造器组成

第2章 创建和销毁对象

第1条:考虑用静态工厂方法代替构造器
一个类可以通过静态工厂方法而非构造器来创建其实例,例如Boolean.valueOf(boolean b)方法。需要注意的是,静态工厂方法与设计模式中的工厂方法模式不同。

  1. 静态工厂方法的优势
    (1)它们有名称
    a. 当一个类需要多个带有相同签名的构造器时,就用静态工厂方法代替构造器,并且慎重地选择名称以便突出它们之间的区别
    (2)不必在每次调用它们的时候都创建一个新对象
    a. 如果程序经常请求创建相同的对象,并且创建对象的代价很高,则这项技术可以极大地提升性能
    b. 借助静态工厂方法,我们可以创造实例受控的类(instance-controlled class),这样有助于类总能严格控制在某个时刻哪些实例应该存在
    (3)它们可以返回原返回类型的任何子类型的对象(这样使得我们在选择返回对象的具体类型时有了更大的灵活性)
    a. 该灵活性的一个应用场景是,一个API可以返回一个对象,同时又可以隐藏该对象所对应的具体类(可以是private类型的类),以这种方式隐藏实现类会使API变得非常简洁
    b. 这项技术适用于基于接口的框架(interface-based framework)
    c. Java Collections Framework的集合接口有32个便利实现,分别提供了不可修改的集合、同步集合等等。几乎所有这些实现都通过静态工厂方法在一个不可实例化的类(java.util.Collections)中导出。所有返回对象对应的类都是非公有的。
    d. 静态工厂方法返回的对象所属的类,在编写包含该静态工厂方法的类时可以不必存在。这种灵活的静态工厂方法构成了服务提供者框架(Service Provider Framework)的基础,例如JDBC API
    PS:Service Provider Framework
    该框架包含四个组件:Service Interface、Provider Registration API、Service Access API和Service Provider Interface(optional),其中Service Access API就是“灵活的静态工厂”,具体可参考书籍描述
    (4)在创建参数化类型实例的时候,它们使代码变得更加简洁
    a. 有了静态工厂方法,编译器可以替你找到类型参数,可称之为类型推导(type inference)。例如
    Map> m = new HashMap>();
    可改进为:
    Map> m = HashMap.newInstance();(假设HashMap提供了"public static HashMap newInstance(){return new HashMap();}"方法)
  2. 静态工厂方法的缺点
    (1)类如果不含公有的或者受保护的构造器,就不能被子类化
    (2)它们与其他的静态方法实际上没有任何区别
    a. 这导致的结果是,对于提供了静态工厂方法而不是构造器的类来说,要查明如何实例化一个类,是非常困难的
    b. 通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,可以在一定程度上弥补这一劣势
    第2条:遇到多个构造器参数时要考虑用构建器
    静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数。遇到许多构造器可选参数时,有3中解决方案
  3. 重叠构造器模式
    提供第一个只有必要参数的构造器,第二个构造器有一个可选参数,第三个有两个可选参数,以此类推。缺点如下:
    (1)当有许多参数时,客户端代码会很难编写,且难以阅读
  4. JavaBeans模式
    调用一个无参构造器来创建对象,然后调用setter方法来设置每个必要的参数,以及每个相关的可选参数。缺点如下:
    (1)构造过程被分到了几个调用中,在构造过程中JavaBean可能处于不一致的状态
    (2)JavaBeans模式阻止了把类做成不可变(即对象一旦生成,则不能再设置其属性值,简单来说就是不提供setter方法)的可能,这就需要程序员付出额外的努力来确保它的线程安全性。
  5. Builder模式
    不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法(可设计builder的setter方法返回builder本身,以实现链式调用),来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。这个builder是它构建的类的静态成员类。优点如下:
    (1)使得客户端代码很容易编写,且易于阅读。builder模式模拟了具名的可选参数,就像Ada和Python中的一样
    (2)Java中传统的抽象工厂实现是Class对象,用newInstance方法充当build方法的一部分。但是,Class.newInstance方法破坏了编译时的异常检查,而Builder模式则可弥补这些不足
    简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder模式就是中不错的选择,特别是当大多数参数都是可选的时候。
    第3条:用私有构造器或者枚举类型强化Singleton属性
    使类成为singleton会使它的客户端测试变得十分困难,因为无法给singleton替换模拟实现,除非它实现一个充当其类型的接口。为了使Singleton类变成是可序列化的,仅仅在声明中加上“Implements Serializable”是不够的。为了维护并保证Singleton,必须声明所有实例域都是瞬时(transient)的,并提供一个新的实例。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。使用单元素的枚举类型来实现Singleton可以解决该问题
    第4条:通过私有构造器强化不可实例化的能力
    有时候,我们可能需要编写只包含静态方法和静态域的类。这样的类不希望被实例化,实例化对它没任何意义。如果企图通过将这样的类做成抽象类来强制该类不可被实例化,这是行不通的。因为该类可以被子类化,并且其子类也可以被实例化。那么,想编写这样的类,我们可以使用私有构造器来实现其不可实例化的特性。
    第5条:避免创建不必要的对象
    对于同时提供了静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器来创建对象,以避免创建不必要的对象。除了可以重用不可变的对象之外,也可以重用那些已知不会被改变的可变对象。
  6. 当你应该重用现有对象的时候,请不要创建新的对象
  7. 要优先使用基本类型而不是装箱基本类型,要担心无意识的自动装箱
  8. 由于小对象的构造器只做很少的显式工作,所以实际上小对象的创建和回收动作是非常廉价的。为了提升程序的清晰性、简洁性和功能性,有时候创建一些附加的小对象是件好事
  9. 通过维护自己的对象池(object pool)来避免创建对象并不是一种好的做法,除非池中的对象是非常重量级的。真正正确使用对象池的典型对象示例就是数据库连接池。
    第6条:消除过期的对象引用
    在支持垃圾回收的语言中,内存泄漏是很隐蔽的,称这类内存泄漏为“无意识的对象保持(unintentional object retention)”更为恰当。如果一个对象引用被无意识地保留起来了,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象所引用的所有其他对象。
  10. 清空对象引用(例如,显式地将一个对象引用置为null)应该是一种例外,而不是一种规范行为
  11. 一般而言,只要类是自己管理内存,那么程序员就应该警惕内存泄漏的问题。一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。
  12. 内存泄漏的另一个常见来源是缓存。一旦你把对象引用放到缓存中,它就很容易被遗忘掉。对于这个问题,有几种可能的解决方案
    (1)只要在缓存之外存在对某个项的键的引用,该项就有意义,那么可以使用WeakHashMap代表缓存
    当缓存中的项过期之后,它们就会自动被删除。记住,只有当所要的缓存项的生命周期是由该键的外部引用而不是由值决定时,WeakHashMap才有用处。
    (2)针对“缓存项的生命周期是否有意义”并不是很容易确定,且随着时间的推移,其中的项会变得越来越没有价值的场景,应该使用一个后台线程来完成缓存清除工作。
  13. 内存泄漏的第三个常见来源是监听器和其他回调。如果你实现了一个API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非你采取某些动作,否则它们将会积聚。确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用(weak reference)
    第7条:避免使用finalize方法
  14. finalize方法通常是不可预测的,也是很危险的,所以一般情况下不要使用
  15. finalize方法的缺点在于不能保证会被及时地执行
  16. java语言规范不仅不保证finalize方法会被及时地执行,而且根本就不保证它们会被执行。因此,不应该依赖finalize方法来更新重要的持久状态。
  17. 使用finalize方法有一个非常严重的性能损失
  18. 如果类的对象中封装的资源(例如文件或者线程)确实需要终止,应该怎么做才能不用编写finalize方法呢?
    只需提供一个显式的终结方法,并要求该类的客户端在每个实例不再有用时调用这个方法。例如InputStream、java.sql.Connection的close方法以及java.util.Timer方法等等。另外,需要注意的是,显式的终结方法必须在一个私有域中记录下“该对象已经不再有效”,如果这些方法是在对象已经终止之后被调用,其他的方法就必须检查这个域,并抛出IllegalStateException异常
  19. 显式的终止方法通常与try-finally结构结合起来使用,以确保及时终止
  20. finalize方法有两种合法用途
    (1)当对象的所有者忘记调用前面段落中建议的显式终止方法时,终结方法可以充当“安全网”。如果finalize方法中发现资源还未被终止,则应该先终止资源,并在日志中记录一条警告,表示客户端代码中存在bug
    (2)用来终止非关键的本地资源
    因为本地对等体(native peer)不是一个普通对象,所以垃圾回收器不会知道它,当它的java对等体被回收的时候,它不会被回收。

你可能感兴趣的:(Effect Java 读书笔记 — 引言与创建和销毁对象)