effective java

第2章 创建和销毁对象

条目7:清除过期的对象引用

在某些场景下,如果不清除过期的对象的引用会存在内存泄露的风险,表现为随垃圾收集器活动增加或内存占用增加而导致的性能下降。

清除过期的对象引用的场景:

  1. 每当出现类自己管理的自己的内存的情形时,应该警惕内存泄露。

    比如类自管理的栈,如果栈新增后减,弹出去的元素并不会被垃圾收集器清理,因为垃圾收集器并不会感知到该元素已经无用。

  2. 缓存。对于一条缓存项(包括键和值), 只有在缓存外有对其键的引用时,它才有存在的意义,可以用weekHashMap实现。缓存项在过期后会被自动删除。

    只有当缓存项预期的生命周期由指向其键的外部引用而不是由值决定时,WeekHashMap才有用

  3. 监听器和其他回调。如果实现了一个api,客户端注册了回调,但是没有显式地注销,否则回调对象就会不断累积。确保回调及时被垃圾收集器处理的方法是只存储对他们的弱引用。

条目8: 避免使用终结方法和清理方法

终结方法(finalizer)是不可预测的,java9开始终结方法已经被废弃了。java9引入了清理方法(cleaner)替代终结方法,危险性要比终结方法小,但是仍然是不可预测的,且运行很慢,一般来说也是不必要的。

如果类的对象封装了需要终止的资源,只须让这样的类实现AutoCloseable接口,并要求客户端在每个实例不需要时就调用其close方法,通常可以用try-with-resources来确保即使存在异常也能正常终止。

条目9:与try-finally相比,首选try-with-resources

java类库中有很多通过调用close方法手动关闭资源,这样的雷子包括inputstream、outputstream和java.sql.connection。

当有try-finally打开多个资源时,代码就变得很难维护,同时首先抛出异常的代码很容易被吞掉,第一个异常不会被记录在异常栈轨迹信息中。

在java7引入try-with-resources,配合该语句使用,资源必须实现AutoCloseable接口,该接口仅包含一个返回类型为void的close方法。

如果read和close方法都抛出了异常,后者的异常会被抑制,也可能会抑制多个异常。这些抑制异常并没有被简单丢弃,而是会被打印到轨迹信息中,并表明它们被抑制了。

第3章 对所有对象都用通用的方法

条目10:在重写equals方法时要遵守通用约定

重写equals看似简单,但是很容易犯错,如果满足下列的任意条件,就不应重写equals:

  • 该类的每个实例本质上都是唯一的
  • 该类没必要提供一个“逻辑”相等的测试
  • 超类已经重写了equals方法,而且其行为适合这个类
  • 类是私有的或包私有的,我们可以确信其equals方法绝不会被调用

当一个类在对象相同之外还存在逻辑相等的概念,而且其上层超类都没有重写equals方法时,应该重写equals方法。

重写equals,必须遵循其通用约定:

  • 自反性:对于任何非null引用值,x.equals(x)=true
  • 对称性:对于非null的引用值x和y,有且仅当y.equals(x)返回true时,x.equals(y)必须返回true
  • 传递性:对于非null的引用值x、y和z,如果x.equals(y)返回true时,且y.equals(y)返回true时,x.equals(z)必须返回true
  • 一致性:对于非null的引用值x和y,只要equals比较中用到的信息没有修改,多次调用x.equals(y)必须一致地返回true或false
  • 非空:对于非null的引用值x,x.equals(null)必须返回false

里氏替换原则:一个类型的任何重要属性都应该适用于其所有子类型,以便为该类型编写的任何方法在其子类型上同样有效。

示例:比如Point的某个子类的实例仍然是一个Point,且仍然需要表现得和Point一样。

重写equals的技巧:

  • 使用==运算符检查参数是否为指向当前对象的引用

  • 使用instanceof运算符检查参数是否具有正确的类型

  • 将参数强制转换为正确的类型

  • 对于类中的每个“重要”字段,检查参数的这一字段和当前对象的相应字段是否匹配

    1. Float.equals和Double.equals性能很差,每次都会存在自动装箱

    2. 为了避免npe,可以使用Objects.equals(Object, Object)来检查字段是否相等

    3. 比较顺序也可能会影响性能。首先比较更可能不同的字段,或比较开销不那么高的字段

条目11:重写equals方法时应该总是重写hashCode方法

重写equals方法的每个类都必须重写hashCode。否则,使实例无法正常使用诸如HashMap和HashSet等集合,应为相等的对象必须有相等的哈希码。

计算哈希码的注意事项:

  • 如果一个字段的值可以通过其他字段计算出来,那么在计算哈希码的时候可以不考虑它
  • 在写equals比较中没用到的任何字段,也必须排除在外,否则两个相等对象可能会产生不同哈希码

计算哈希码时为什么常用31,因为31是一个奇素数,31有个好处是可以将乘法替换为位移和减法:31*i == (i << 5) - i

posted @ 2025-07-06 15:44  小小灰迪  阅读(9)  评论(0)    收藏  举报