|
| 1 | +--- |
| 2 | +title: effectivejava |
| 3 | +draft: false |
| 4 | +authors: [huyi] |
| 5 | +date: 2024-09-25 |
| 6 | +slug: effectivejava |
| 7 | +description: Effective-Java阅读笔记 |
| 8 | + 以下所言,或来自读书笔记,或来自书籍的勾画,或只是回忆...作为参考。 |
| 9 | +categories: |
| 10 | + - 技术书籍 |
| 11 | + - 笔记 |
| 12 | + - Java |
| 13 | +--- |
| 14 | + |
| 15 | +阅读effective-java总结的一些笔记,先把书读厚,再把书读薄! <!-- more --> |
| 16 | + |
| 17 | +# Chapter 3. Methods Common to All Objects |
| 18 | + |
| 19 | +## Item10. 覆盖equals方法时应遵守的约定 |
| 20 | + |
| 21 | +> 本条目讨论是否覆盖equals时应该注意的点,即什么情况下不需要覆盖equals,若覆盖equals需要遵守的约定;以及覆盖不合理所导致的后果。 |
| 22 | +
|
| 23 | +--- |
| 24 | + |
| 25 | +`java.lang.Object`类提供了`equals`的默认实现(比较对象引用是否相等),**类的每个实例只等于它自己**。 |
| 26 | + |
| 27 | +绝大部分情况下,值类都是需要覆盖equals实现,如果不需要覆盖equals,类应该符合以下几种条件: |
| 28 | + |
| 29 | +- **类的每个实例都是唯一的**,对于像Thread这类表示活动实体类也是如此,Object的默认实现就满足此要求 |
| 30 | +- 对于**不需要提供【逻辑相等】的类** |
| 31 | +- **超类已经实现equals,并且对于子类完全适用** |
| 32 | +- 类是私有的,并且你**确保该equals方法永远不会被调用** |
| 33 | + |
| 34 | + |
| 35 | + |
| 36 | +如果需要类需要实现**逻辑相等**这类概念时(e.g. `Map`中作为key存在时),则需要覆盖其`equals`方法。以下是覆盖equals时遵守的**通用约定**: |
| 37 | + |
| 38 | +- **反身性**:x.equals(x)必须返回true |
| 39 | +- **对称性**:x.equals(y)必须在y.equals(x)返回true时返回true |
| 40 | + - e.g.` java.sql.TimeStamp`继承自`java.util.Date`,如果在同一个集合中使用时间戳和日期对象,或者以其他方式混合使用时间戳和日期对象,那么时间戳的 equals 实现确实违反了对称性 |
| 41 | +- **传递性**:x.equals(y),y.equals(z),则x.equals(z)必须满足 |
| 42 | + - 除非你愿意放弃面向对象的抽象优点,否则**无法**继承一个可实例化的类并添加新属性时,并同时保留equals约定(传递性) |
| 43 | + - 可以考虑使用抽象类的子类添加一个值组件或者不采用继承转而采用组合的方式实现 |
| 44 | +- **一致性**:x.equals(y)的多次调用结果必须一致 |
| 45 | + - 无论一个类是否不可变,都**不要编写依赖于不可靠资源的equals方法**,如果违反则很难满足一致性。e.g. `java.net.URL#equals()`方法依赖于与URL相关联的主机的IP地址的比较 |
| 46 | +- **x.equals(null)必须false** |
| 47 | + - `instanceof`自带判定null的能力,即 obj=null, obj instanceof Object 一定是false |
| 48 | + |
| 49 | + |
| 50 | + |
| 51 | +综上所述,构建高质量equals方法的秘诀(按照顺序): |
| 52 | + |
| 53 | +1. 使用`==`检查参数是否是该对象的引用 |
| 54 | + |
| 55 | +2. 使用instanceof检查参数类型是否正确 |
| 56 | + |
| 57 | +3. 将参数转为正确类型,并对类中的每个【重要】字段检查对应的字段值是否匹配 |
| 58 | + |
| 59 | + > 注:重要字段可以理解为类继承结构中,使用最多,最通用的字段。对于float或double等字段,使用`Float.compare(float, float)`方法比较 |
| 60 | +
|
| 61 | + |
| 62 | +不比较不属于对象逻辑状态的字段,例如同步操作的锁字段,优先比较那些更可能不同、比较成本更低的字段。 |
| 63 | + |
| 64 | +写完问自己三个问题:它具备对称性吗?具备传递性吗?具备一致性吗?并编写UT来检查 |
| 65 | + |
| 66 | + |
| 67 | + |
| 68 | +--- |
| 69 | + |
| 70 | +## Item11. hashCode总是同equals一起覆盖 |
| 71 | + |
| 72 | +> 讨论hashCode()方法的一般约定,为什么hashCode要同equals一起覆盖?有什么比较好的hash值生成方法?hash生成中要避免的点有哪些? |
| 73 | +
|
| 74 | +`java.lang.Object#hashCode`中明确指出:使用hashCode方法是为了便于使用哈希表(类实例如果有使用到hash表,则需要使用此方法生成hashCode) |
| 75 | + |
| 76 | +由于在hash表中,总是结合使用equals和hashCode方法来判断对象是否相等,所以,hashCode方法的一般约定总是与equals有关,具体如下: |
| 77 | + |
| 78 | +1. 同一对象多次调用`hashCode()`,**必须**始终返回**相同**的hash值(前提是用于equals比较的信息未修改) |
| 79 | +2. 若两个对象通过equals比较相等(true),则它们的hash值必须一致 |
| 80 | +3. 若两个对象通过equals比较不相等,则不强制要求hash值必须一致;但程序员必须意识到,对**不相等的对象产生不同的hash值可以提供hash表的性能** |
| 81 | + |
| 82 | +理想情况下,一个散列算法应该在所有int值上均匀合理分布所有不相等实例集合。实现该理想很困难,但幸运的是实现一个类似的并不太难,方法如下: |
| 83 | + |
| 84 | +1. 声明一个result的int变量,并将其初始化为对象中第一个重要字段的散列码c |
| 85 | + |
| 86 | +2. 对象中剩余的重要字段f,执行以下操作 |
| 87 | + |
| 88 | + 1. 为字段计算一个整数散列码c |
| 89 | + |
| 90 | + 1. 若字段为基本类型,计算`Type.hashCode(f)`,其中`type`是与f类型对应的包装类 |
| 91 | + 2. 若字段为对象引用,调用其hashCode,若字段值为null,则使用0 |
| 92 | + 3. 若字段是一个数组,则将其每个重要元素都视为一个单独的字段,循环的计算每个元素的hash值,若数组中没有重要元素,则使用常量(最好不是0),如果所有元素都重要,则使用`Arrays.hashCode` |
| 93 | + |
| 94 | + 2. 将步骤1中计算的散列码c合并到result变量 |
| 95 | + |
| 96 | + ```java |
| 97 | + //31 是一个常用的质数,它能在哈希值的计算中产生较好的分布效果,避免哈希冲突 |
| 98 | + result = 31 * result + c; |
| 99 | + ``` |
| 100 | + |
| 101 | +3. 返回result变量 |
| 102 | + |
| 103 | +4. 完成hashCode方法的编写后,尝试相同的实例是否具有相同的hash值,并编写UT来验证 |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | +hash值的生成还要考虑以下几点: |
| 108 | + |
| 109 | +1. **不要试图从散列码计算中排除重要字段**,以提高性能;这**会导致不好的hash分布,影响散列表的性能** |
| 110 | + |
| 111 | +2. 若一个类是**不可变**的,并且**散列码的计算成本很高**,可以考虑在对象中**缓存散列码** |
| 112 | + |
| 113 | + ```java |
| 114 | + private int hashCode; // Automatically initialized to 0 |
| 115 | + @Override |
| 116 | + public int hashCode() { |
| 117 | + int result = hashCode; |
| 118 | + //no cache |
| 119 | + if (result == 0) { |
| 120 | + result = Short.hashCode(areaCode); |
| 121 | + result = 31 * result + Short.hashCode(prefix); |
| 122 | + result = 31 * result + Short.hashCode(lineNum); |
| 123 | + hashCode = result; // set cache |
| 124 | + } |
| 125 | + |
| 126 | + return result; |
| 127 | + } |
| 128 | + ``` |
| 129 | + |
| 130 | +3. **不要为hashCode返回的值提供详细的规范,这样客户端就不能理所应当的依赖它,这也为你以后可能的更改留有余地。**Java库中很多类,e.g. String和Integer,都将hashCode返回的确切值指定为实例值,这不是一个好主意,它阻碍了未来版本中提高散列算法的能力 |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | + |
| 135 | + |
0 commit comments