mirror of
https://github.com/Jueee/effective-Java.git
synced 2025-01-29 21:50:10 +08:00
考虑实现 Comparable 接口
This commit is contained in:
parent
24d84af4a2
commit
879b096a9d
81
ch03所有对象的通用方法/11.重写equals方法时同时也要重写hashcode方法.md
Normal file
81
ch03所有对象的通用方法/11.重写equals方法时同时也要重写hashcode方法.md
Normal file
@ -0,0 +1,81 @@
|
||||
## 重写 equals 方法时同时也要重写 hashcode 方法
|
||||
|
||||
**在每个类中,在重写 equals 方法的时侯,一定要重写 hashcode 方法。**
|
||||
|
||||
如果不这样做,你的类违反了 hashCode 的通用约定,这会阻止它在 HashMap 和 HashSet 这样的集合中正常工作。
|
||||
|
||||
根据 Object 规范,以下时具体约定。
|
||||
|
||||
1. 当在一个应用程序执行过程中,如果在 equals 方法比较中没有修改任何信息,在一个对象上重复调用 hashCode 方法时,它必须始终返回相同的值。从一个应用程序到另一个应用程序的每一次执行返回的值可以是不一致的。
|
||||
2. 如果两个对象根据 equals(Object) 方法比较是相等的,那么在两个对象上调用 hashCode 就必须产生的结果是相同的整数。
|
||||
3. 如果两个对象根据 equals(Object) 方法比较并不相等,则不要求在每个对象上调用 hashCode 都必须产生不同的结果。 但是,程序员应该意识到,为不相等的对象生成不同的结果可能会提高散列表(hash tables)的性能。
|
||||
|
||||
**当无法重写 hashCode 时,所违反第二个关键条款是:相等的对象必须具有相等的哈希码( hash codes)。** 根据类的 equals 方法,两个不同的实例可能在逻辑上是相同的,但是对于 Object 类的 hashCode 方法,它们只是两个没有什么共同之处的对象。因此, Object 类的 hashCode 方法返回两个看似随机的数字,而不是按约定要求的两个相等的数字。
|
||||
|
||||
**示例代码**:[Item11Example01.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item11/Item11Example01.java):将对象作为 HashMap 的键。
|
||||
|
||||
```java
|
||||
Map<PhoneNumber1, String> m = new HashMap<>();
|
||||
m.put(new PhoneNumber1(707, 867, 5309), "Jenny");
|
||||
System.out.println(m.get(new PhoneNumber1(707, 867, 5309))); // null
|
||||
```
|
||||
|
||||
你可能期望 `m.get(new PhoneNumber(707, 867, 5309))` 方法返回 `Jenny` 字符串,但实际上,返回了 null。
|
||||
|
||||
注意,这里涉及到两个 `PhoneNumber` 实例:一个实例插入到 HashMap 中,另一个作为判断相等的实例用来检索。
|
||||
|
||||
`PhoneNumber` 类没有重写 hashCode 方法导致两个相等的实例返回了不同的哈希码,违反了 hashCode 约定。
|
||||
|
||||
put 方法把 `PhoneNumber` 实例保存在了一个哈希桶( hash bucket)中,但 get 方法却是从不同的哈希桶中去查找,即使恰好两个实例放在同一个哈希桶中,get 方法几乎肯定也会返回 null。
|
||||
|
||||
因为 HashMap 做了优化,缓存了与每一项(entry)相关的哈希码,如果哈希码不匹配,则不会检查对象是否相等了。
|
||||
|
||||
解决这个问题很简单,只需要为 `PhoneNumber` 类重写一个合适的 hashCode 方法。
|
||||
|
||||
**示例代码**:[Item11Example02.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item11/Item11Example02.java):将对象作为 HashMap 的键并重写 hashCode 方法。
|
||||
|
||||
一个好的 hash 方法趋向于为不相等的实例生成不相等的哈希码。这也正是 hashCode 约定中第三条的表达。理想情况下,hash 方法为集合中不相等的实例均匀地分配 int 范围内的哈希码。实现这种理想情况可能是困难的。 幸运的是,要获得一个合理的近似的方式并不难。 以下是一个简单的配方:
|
||||
|
||||
1. 声明一个 int 类型的变量 result,并将其初始化为对象中第一个重要属性 `c` 的哈希码,如下面步骤 2.a 中所计算的那样。(回顾条目 10,重要的属性是影响比较相等的领域。)
|
||||
|
||||
2. 对于对象中剩余的重要属性 `f`,请执行以下操作:
|
||||
a. 比较属性 `f` 与属性 `c` 的 int 类型的哈希码:
|
||||
|
||||
-- i. 如果这个属性是基本类型的,使用 `Type.hashCode(f)` 方法计算,其中 `Type` 类是对应属性 `f` 基本类型的包装类。
|
||||
|
||||
|
||||
-- ii. 如果该属性是一个对象引用,并且该类的 equals 方法通过递归调用 equals 来比较该属性,并递归地调用 hashCode 方法。 如果需要更复杂的比较,则计算此字段的“范式(“canonical representation)”,并在范式上调用 hashCode。 如果该字段的值为空,则使用 0(也可以使用其他常数,但通常来使用 0 表示)。
|
||||
|
||||
|
||||
-- iii. 如果属性 `f` 是一个数组,把它看作每个重要的元素都是一个独立的属性。 也就是说,通过递归地应用这些规则计算每个重要元素的哈希码,并且将每个步骤 2.b 的值合并。 如果数组没有重要的元素,则使用一个常量,最好不要为 0。如果所有元素都很重要,则使用 `Arrays.hashCode` 方法。
|
||||
|
||||
b. 将步骤 2.a 中属性 c 计算出的哈希码合并为如下结果:`result = 31 * result + c;`
|
||||
|
||||
3. 返回 result 值。
|
||||
|
||||
之所以选择 31,因为它是一个奇数的素数。 如果它是偶数,并且乘法溢出,信息将会丢失,因为乘以 2 相当于移位。 使用素数的好处不太明显,但习惯上都是这么做的。 31 的一个很好的特性,是在一些体系结构中乘法可以被替换为移位和减法以获得更好的性能:`31 * i ==(i << 5) - i`。 现代 JVM 可以自动进行这种优化。
|
||||
|
||||
`Objects` 类有一个静态方法,它接受任意数量的对象并为它们返回一个哈希码。 这个名为 hash 的方法可以让你编写一行 hashCode 方法,其质量与根据这个项目中的上面编写的方法相当。 不幸的是,它们的运行速度更慢,因为它们需要创建数组以传递可变数量的参数,以及如果任何参数是基本类型,则进行装箱和取消装箱。 这种哈希函数的风格建议仅在性能不重要的情况下使用。
|
||||
|
||||
**示例代码**:[Item11Example03.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item11/Item11Example03.java):使用`Objects` 类静态方法编写的 hashCode 。
|
||||
|
||||
如果一个类是不可变的,并且计算哈希码的代价很大,那么可以考虑在对象中缓存哈希码,而不是在每次请求时重新计算哈希码。 如果你认为这种类型的大多数对象将被用作哈希键,那么应该在创建实例时计算哈希码。
|
||||
|
||||
```java
|
||||
// hashCode method with lazily initialized cached hash code
|
||||
private int hashCode; // Automatically initialized to 0
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = hashCode;
|
||||
if (result == 0) {
|
||||
result = Short.hashCode(areaCode);
|
||||
result = 31 * result + Short.hashCode(prefix);
|
||||
result = 31 * result + Short.hashCode(lineNum);
|
||||
hashCode = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
总之,每次重写 equals 方法时都必须重写 hashCode 方法,否则程序将无法正常运行。你的 hashCode 方法必须遵从 Object 类指定的常规约定,并且必须执行合理的工作,将不相等的哈希码分配给不相等的实例。
|
54
ch03所有对象的通用方法/12.始终重写toString方法.md
Normal file
54
ch03所有对象的通用方法/12.始终重写toString方法.md
Normal file
@ -0,0 +1,54 @@
|
||||
## 始终重写 toString 方法
|
||||
|
||||
虽然 Object 类提供了 toString 方法的实现,但它返回的字符串通常不是你的类的用户想要看到的。
|
||||
|
||||
toString 方法由类名后跟一个「at」符号(@)和哈希码的无符号十六进制表示组成,例如 `PhoneNumber@163b91`。
|
||||
|
||||
toString 的通用约定要求,返回的字符串应该是「一个简洁但内容丰富的表示,对人们来说是很容易阅读的」。
|
||||
|
||||
toString 通用约定「建议所有的子类重写这个方法」。
|
||||
|
||||
虽然它并不像遵守 equals 和 hashCode 约定那样重要,但是提供一个良好的 toString 实现使你的类更易于使用,并对使用此类的系统更易于调试。当对象被传递到 println、printf、字符串连接操作符或断言,或者由调试器打印时,toString 方法会自动被调用。即使你从不调用对象上的 toString,其他人也可以。例如,对对象有引用的组件可能包含在日志错误消息中对象的字符串表示。如果未能重写 toString,则消息可能是无用的。
|
||||
|
||||
**示例代码**:[Item12Example01.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item12/Item12Example01.java):重写 toString 方法。
|
||||
|
||||
指定 toString 返回值的格式的缺点是,假设你的类被广泛使用,一旦指定了格式,就会终身使用。程序员将编写代码来解析表达式,生成它,并将其嵌入到持久数据中。如果在将来的版本中更改了格式的表示,那么会破坏他们的代码和数据,并且还会抱怨。但通过选择不指定格式,就可以保留在后续版本中添加信息或改进格式的灵活性。
|
||||
|
||||
在静态工具类中编写 toString 方法是没有意义的。 你也不应该在大多数枚举类型(条目 34)中写一个 toString 方法,因为 Java 为你提供了一个非常好的方法。 但是,你应该在任何抽象类中定义 toString 方法,该类的子类共享一个公共字符串表示形式。 例如,大多数集合实现上的 toString 方法都是从抽象集合类继承的。
|
||||
|
||||
### 使用AutoValue
|
||||
|
||||
AutoValue是一个可以自动为值类(value type)生成诸如equals,hashCode,toString等模板方法的工具。
|
||||
|
||||
**示例代码**:[Item12Example02.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item12/Item12Example02.java):使用Google 的开放源代码 AutoValue 工具
|
||||
|
||||
编写一个 AutoValue 工具类:
|
||||
|
||||
1. 这个类是抽象的。
|
||||
2. 抽象的字段访问函数,没有字段。
|
||||
3. 提供一个静态的创建函数返回该类对象。
|
||||
4. 类上标记@AutoValue注解。
|
||||
|
||||
在这里需要注意的是:
|
||||
* 实体类中AutoValue的前缀不能改变,否则编译不会通过;
|
||||
* 字段名称必须对应一致,实际参数列表和形参列表一致,否则编译不过;
|
||||
* AutoValue_PhoneNumber2 是根据AutoValue的命名格式AutoValue_类名来命名的。
|
||||
|
||||
在 pom.xml 中引入 Jar 包:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
|
81
ch03所有对象的通用方法/13.谨慎地重写clone方法.md
Normal file
81
ch03所有对象的通用方法/13.谨慎地重写clone方法.md
Normal file
@ -0,0 +1,81 @@
|
||||
## 谨慎地重写 clone 方法
|
||||
|
||||
Cloneable 接口的目的是作为一个 mixin 接口 ,公布这样的类允许克隆。
|
||||
|
||||
不幸的是,它没有达到这个目的。它的主要缺点是缺少 clone 方法,而 Object 的 clone 方法是受保护的。
|
||||
|
||||
你不能,不借助反射,仅仅因为它实现了 Cloneable 接口,就调用对象上的 clone 方法。
|
||||
|
||||
即使是反射调用也可能失败,因为不能保证对象具有可访问的 clone 方法。
|
||||
|
||||
Cloneable 接口决定了 Object 的受保护的 clone 方法实现的行为:如果一个类实现了 Cloneable 接口,那么 Object 的 clone 方法将返回该对象的逐个属性(field-by-field)拷贝;否则会抛出 `CloneNotSupportedException` 异常。
|
||||
|
||||
这是一个非常反常的接口使用,而不应该被效仿。 通常情况下,实现一个接口用来表示可以为客户做什么。但对于 Cloneable 接口,它会修改父类上受保护方法的行为。
|
||||
|
||||
### 普通克隆
|
||||
|
||||
假设你希望在一个类中实现 Cloneable 接口,它的父类提供了一个行为良好的 clone 方法。首先调用 super.clone。 得到的对象将是原始的完全功能的复制品。 在你的类中声明的任何属性将具有与原始属性相同的值。 如果每个属性包含原始值或对不可变对象的引用,则返回的对象可能正是你所需要的,在这种情况下,不需要进一步的处理。
|
||||
|
||||
**示例代码**:[Item13Example01.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example01.java):实现 Cloneable 接口的 clone 方法。
|
||||
|
||||
### 对象包含引用可变对象的属性
|
||||
|
||||
如果对象包含引用可变对象的属性,则前面显示的简单 clone 实现可能是灾难性的。
|
||||
|
||||
- [Item13Example02.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example02.java):
|
||||
如果 clone 方法仅返回 super.clone() 调用的对象,那么生成的 Stack 实例在其 size 属性中具有正确的值,但 elements 属性引用与原始 Stack 实例相同的数组。
|
||||
- [Item13Example03.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example03.java):为了使 Stack 上的 clone 方法正常工作,它必须复制 stack 对象的内部。
|
||||
|
||||
### 克隆 final 属性
|
||||
|
||||
如果属性是 final 的,则以前的解决方案将不起作用,因为克隆将被禁止向该属性分配新的值。
|
||||
|
||||
这是一个基本的问题:像序列化一样,Cloneable 体系结构与引用可变对象的 final 属性的正常使用不兼容,除非可变对象可以在对象和其克隆之间安全地共享。
|
||||
|
||||
为了使一个类可以克隆,可能需要从一些属性中移除 final 修饰符。
|
||||
|
||||
- [Item13Example04.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example04.java):
|
||||
只是递归地克隆哈希桶数组。
|
||||
|
||||
虽然被克隆的对象有自己的哈希桶数组,但是这个数组引用与原始数组相同的链表,这很容易导致克隆对象和原始对象中的不确定性行为。
|
||||
|
||||
- [Item13Example05.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example05.java):复制包含每个桶的链表。
|
||||
|
||||
如果哈希桶不是太长,这种技术很聪明并且工作正常。
|
||||
|
||||
但是,克隆链表不是一个好方法,因为它为列表中的每个元素消耗一个栈帧(stack frame)。
|
||||
|
||||
如果列表很长,这很容易导致堆栈溢出。
|
||||
|
||||
- [Item13Example06.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item13/Item13Example06.java):用迭代来替换 deepCopy 中的递归。
|
||||
|
||||
### 复制构造方法或复制工厂
|
||||
|
||||
对象复制更好的方法是提供一个复制构造方法或复制工厂。
|
||||
|
||||
复制构造方法接受参数,其类型为包含此构造方法的类,例如:
|
||||
|
||||
```java
|
||||
// 复制构造方法
|
||||
public Yum(Yum yum) { ... };
|
||||
```
|
||||
|
||||
```java
|
||||
// 复制工厂
|
||||
public static Yum newInstance(Yum yum) { ... };
|
||||
```
|
||||
|
||||
复制构造方法及其静态工厂变体与 Cloneable/clone 相比有许多优点:
|
||||
|
||||
- 它们不依赖风险很大的语言外的对象创建机制;
|
||||
- 不要求遵守那些不太明确的惯例;
|
||||
- 不会与 final 属性的正确使用相冲突;
|
||||
- 不会抛出不必要的检查异常;
|
||||
- 不需要类型转换。
|
||||
|
||||
此外,复制构造方法或复制工厂可以接受类型为该类实现的接口的参数。 例如,按照惯例,所有通用集合实现都提供了一个构造方法,其参数的类型为 Collection 或 Map。 基于接口的复制构造方法和复制工厂(更适当地称为转换构造方法和转换工厂)允许客户端选择复制的实现类型,而不是强制客户端接受原始实现类型。 例如,假设你有一个 HashSet,并且你想把它复制为一个 TreeSet。 clone 方法不能提供这种功能,但使用转换构造方法很容易:`new TreeSet<>(s)`。
|
||||
|
||||
考虑到与 Cloneable 接口相关的所有问题,新的接口不应该继承它,新的可扩展类不应该实现它。 虽然实现 Cloneable 接口对于 final 类没有什么危害,但应该将其视为性能优化的角度,仅在极少数情况下才是合理的。
|
||||
|
||||
通常,复制功能最好由构造方法或工厂提供。 这个规则的一个明显的例外是数组,它最好用 clone 方法复制。
|
||||
|
42
ch03所有对象的通用方法/14.考虑实现Comparable接口.md
Normal file
42
ch03所有对象的通用方法/14.考虑实现Comparable接口.md
Normal file
@ -0,0 +1,42 @@
|
||||
## 考虑实现 Comparable 接口
|
||||
|
||||
`compareTo` 方法是 `Comparable` 接口中的唯一方法。
|
||||
|
||||
与 Object 类的 equals 方法在性质上是相似的,除了它允许在简单的相等比较之外的顺序比较,它是泛型的。
|
||||
|
||||
通过实现 `Comparable` 接口,一个类表明它的实例有一个自然顺序(natural ordering)。
|
||||
|
||||
对实现 `Comparable` 接口的对象数组排序非常简单,如下所示:
|
||||
|
||||
```java
|
||||
Arrays.sort(a);
|
||||
```
|
||||
|
||||
它很容易查找,计算极端数值,以及维护 `Comparable` 对象集合的自动排序。
|
||||
|
||||
**示例代码**:[Item14Example01.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item14/Item14Example01.java):依赖于 String 类实现了 `Comparable` 接口,去除命令行参数输入重复的字符串,并按照字母顺序排序。
|
||||
|
||||
### compareTo 方法的通用约定
|
||||
|
||||
`compareTo` 方法的通用约定与 `equals` 相似:
|
||||
|
||||
将此对象与指定的对象按照排序进行比较。 返回值可能为负整数,零或正整数,因为此对象对应小于,等于或大于指定的对象。 如果指定对象的类型与此对象不能进行比较,则引发 `ClassCastException` 异常。
|
||||
|
||||
下面的描述中,符号 sgn(expression) 表示数学中的 signum 函数,它根据表达式的值为负数、零、正数,对应返回-1、0 和 1。
|
||||
|
||||
- 实现类必须确保所有 `x` 和 `y` 都满足 `sgn(x.compareTo(y)) == -sgn(y. compareTo(x))`。 (这意味着当且仅当 `y.compareTo(x)` 抛出异常时,`x.compareTo(y)` 必须抛出异常。)
|
||||
- 实现类还必须确保该关系是可传递的:`(x. compareTo(y) > 0 && y.compareTo(z) > 0)` 意味着 `x.compareTo(z) > 0`。
|
||||
- 最后,对于所有的 z,实现类必须确保 `x.compareTo(y) == 0` 意味着 `sgn(x.compareTo(z)) == sgn(y.compareTo(z))`。
|
||||
- 强烈推荐 `(x.compareTo(y) == 0) == (x.equals(y))`,但不是必需的。 一般来说,任何实现了 `Comparable` 接口的类违反了这个条件都应该清楚地说明这个事实。 推荐的语言是「注意:这个类有一个自然顺序,与 `equals` 不一致」。
|
||||
|
||||
与 `equals` 方法一样,不要被上述约定的数学特性所退缩。这个约定并不像看起来那么复杂。 与 `equals` 方法不同,`equals` 方法在所有对象上施加了全局等价关系,`compareTo` 不必跨越不同类型的对象:当遇到不同类型的对象时,`compareTo` 被允许抛出 `ClassCastException` 异常。 通常,这正是它所做的。 约定确实允许进行不同类型间比较,这种比较通常在由被比较的对象实现的接口中定义。
|
||||
|
||||
正如一个违反 hashCode 约定的类可能会破坏依赖于哈希的其他类一样,违反 `compareTo` 约定的类可能会破坏依赖于比较的其他类。 依赖于比较的类,包括排序后的集合 `TreeSet` 和 TreeMap 类,以及包含搜索和排序算法的实用程序类 `Collections` 和 `Arrays`。
|
||||
|
||||
### 比较类中的多个重要属性
|
||||
|
||||
如果一个类有多个重要的属性,那么比较他们的顺序是至关重要的。 从最重要的属性开始,逐步比较所有的重要属性。 如果比较结果不是零(零表示相等),则表示比较完成; 只是返回结果。 如果最重要的字段是相等的,比较下一个重要的属性,依此类推,直到找到不相等的属性或比较剩余不那么重要的属性。
|
||||
|
||||
**示例代码**:[Item14Example02.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item14/Item14Example02.java):依次比较判断。
|
||||
|
||||
**示例代码**:[Item14Example03.java](MethodsCommonToAllObjects/src/main/java/com/jueee/item14/Item14Example03.java):使用 Java 8 中 `Comparator` 接口提供了一系列比较器方法。
|
@ -1,25 +1,35 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.github.jueee</groupId>
|
||||
<artifactId>MethodsCommonToAllObjects</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
<groupId>com.github.jueee</groupId>
|
||||
<artifactId>MethodsCommonToAllObjects</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>MethodsCommonToAllObjects</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
<name>MethodsCommonToAllObjects</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value-annotations</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
<version>1.6.2</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -0,0 +1,45 @@
|
||||
package com.jueee.item11;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Item11Example01 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map<PhoneNumber1, String> m = new HashMap<>();
|
||||
m.put(new PhoneNumber1(707, 867, 5309), "Jenny");
|
||||
System.out.println(m.get(new PhoneNumber1(707, 867, 5309))); // null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class PhoneNumber1 {
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber1(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof PhoneNumber1))
|
||||
return false;
|
||||
|
||||
PhoneNumber1 pn = (PhoneNumber1)o;
|
||||
|
||||
return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package com.jueee.item11;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Item11Example02 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map<PhoneNumber2, String> m = new HashMap<>();
|
||||
m.put(new PhoneNumber2(707, 867, 5309), "Jenny");
|
||||
System.out.println(m.get(new PhoneNumber2(707, 867, 5309))); // Jenny
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class PhoneNumber2 {
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber2(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof PhoneNumber2))
|
||||
return false;
|
||||
|
||||
PhoneNumber2 pn = (PhoneNumber2)o;
|
||||
|
||||
return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
|
||||
}
|
||||
|
||||
// 典型的hashCode方法
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Short.hashCode(areaCode);
|
||||
result = 31 * result + Short.hashCode(prefix);
|
||||
result = 31 * result + Short.hashCode(lineNum);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
package com.jueee.item11;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class Item11Example03 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Map<PhoneNumber3, String> m = new HashMap<>();
|
||||
m.put(new PhoneNumber3(707, 867, 5309), "Jenny");
|
||||
System.out.println(m.get(new PhoneNumber3(707, 867, 5309))); // Jenny
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
final class PhoneNumber3 {
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber3(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == this)
|
||||
return true;
|
||||
if (!(o instanceof PhoneNumber3))
|
||||
return false;
|
||||
|
||||
PhoneNumber3 pn = (PhoneNumber3)o;
|
||||
|
||||
return pn.lineNum == lineNum && pn.prefix == prefix && pn.areaCode == areaCode;
|
||||
}
|
||||
|
||||
// 单行hashCode方法-性能中等
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(lineNum, prefix, areaCode);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package com.jueee.item12;
|
||||
|
||||
public class Item12Example01 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
PhoneNumber1 number1 = new PhoneNumber1(707, 867, 5309);
|
||||
System.out.println(number1); // 707-867-5309
|
||||
}
|
||||
}
|
||||
|
||||
final class PhoneNumber1 {
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber1(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package com.jueee.item12;
|
||||
|
||||
import com.google.auto.value.AutoValue;
|
||||
|
||||
public class Item12Example02 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
PhoneNumber2 number1 = PhoneNumber2.create(707, 867, 5309);
|
||||
System.out.println(number1); // PhoneNumber2{areaCode=707, prefix=867, lineNum=5309}
|
||||
System.out.println(number1.toString()); // PhoneNumber2{areaCode=707, prefix=867, lineNum=5309}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 在这里需要注意的是:
|
||||
* 1、实体类中AutoValue的前缀不能改变,否则编译不会通过;
|
||||
* 2、字段名称必须对应一致,实际参数列表和形参列表一致,否则编译不过;
|
||||
* 3、AutoValue_PhoneNumber2 是根据AutoValue的命名格式AutoValue_类名来命名的
|
||||
* @author hzweiyongqiang
|
||||
*/
|
||||
@AutoValue
|
||||
abstract class PhoneNumber2 {
|
||||
|
||||
public static PhoneNumber2 create(int areaCode, int prefix, int lineNum) {
|
||||
return new AutoValue_PhoneNumber2(areaCode, prefix, lineNum);
|
||||
}
|
||||
|
||||
public abstract int areaCode();
|
||||
public abstract int prefix();
|
||||
public abstract int lineNum();
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
public class Item13Example01 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
PhoneNumber1 number1 = new PhoneNumber1(707, 867, 5309);
|
||||
System.out.println(number1); // 707-867-5309
|
||||
|
||||
try {
|
||||
PhoneNumber1 number2 = (PhoneNumber1)number1.clone();
|
||||
System.out.println(number2); // 707-867-5309
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PhoneNumber1 implements Cloneable{
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber1(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
return (PhoneNumber1) super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(); // Can't happen
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EmptyStackException;
|
||||
|
||||
public class Item13Example02 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Stack1 stack1 = new Stack1();
|
||||
stack1.push(1);
|
||||
stack1.push(2);
|
||||
stack1.push(3);
|
||||
System.out.println(stack1.pop()); // 3
|
||||
|
||||
Stack1 stack2 = null;
|
||||
try {
|
||||
stack2 = (Stack1)stack1.clone();
|
||||
System.out.println(stack2.pop()); // 2
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println(stack1.pop()); // null
|
||||
System.out.println(stack2.pop()); // 1
|
||||
}
|
||||
}
|
||||
|
||||
class Stack1 implements Cloneable {
|
||||
|
||||
private Object[] elements;
|
||||
private int size = 0;
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
public Stack1() {
|
||||
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void push(Object e) {
|
||||
ensureCapacity();
|
||||
elements[size++] = e;
|
||||
}
|
||||
|
||||
public Object pop() {
|
||||
if (size == 0)
|
||||
throw new EmptyStackException();
|
||||
Object result = elements[--size];
|
||||
|
||||
elements[size] = null; // Eliminate obsolete reference
|
||||
return result;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return elements.length;
|
||||
}
|
||||
|
||||
// Ensure space for at least one more element.
|
||||
private void ensureCapacity() {
|
||||
if (elements.length == size)
|
||||
elements = Arrays.copyOf(elements, 2 * size + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
return (Stack1)super.clone();
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(); // Can't happen
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EmptyStackException;
|
||||
|
||||
public class Item13Example03 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Stack2 stack1 = new Stack2();
|
||||
stack1.push(1);
|
||||
stack1.push(2);
|
||||
stack1.push(3);
|
||||
System.out.println(stack1.pop()); // 3
|
||||
|
||||
Stack2 stack2 = null;
|
||||
try {
|
||||
stack2 = (Stack2)stack1.clone();
|
||||
System.out.println(stack2.pop()); // 2
|
||||
} catch (CloneNotSupportedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
System.out.println(stack1.pop()); // 2
|
||||
System.out.println(stack2.pop()); // 1
|
||||
}
|
||||
}
|
||||
|
||||
class Stack2 implements Cloneable{
|
||||
|
||||
private Object[] elements;
|
||||
private int size = 0;
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 16;
|
||||
|
||||
public Stack2() {
|
||||
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
|
||||
}
|
||||
|
||||
public void push(Object e) {
|
||||
ensureCapacity();
|
||||
elements[size++] = e;
|
||||
}
|
||||
|
||||
public Object pop() {
|
||||
if (size == 0)
|
||||
throw new EmptyStackException();
|
||||
Object result = elements[--size];
|
||||
|
||||
elements[size] = null; // Eliminate obsolete reference
|
||||
return result;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return elements.length;
|
||||
}
|
||||
|
||||
// Ensure space for at least one more element.
|
||||
private void ensureCapacity() {
|
||||
if (elements.length == size)
|
||||
elements = Arrays.copyOf(elements, 2 * size + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
Stack2 result = (Stack2) super.clone();
|
||||
result.elements = elements.clone();
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError(); // Can't happen
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
public class Item13Example04 {
|
||||
|
||||
public static void main(String[] args) {}
|
||||
}
|
||||
|
||||
class HashTable1 implements Cloneable {
|
||||
|
||||
private Entry[] buckets = new Entry[] {};
|
||||
|
||||
private static class Entry {
|
||||
final Object key;
|
||||
Object value;
|
||||
Entry next;
|
||||
|
||||
Entry(Object key, Object value, Entry next) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
HashTable1 result = (HashTable1)super.clone();
|
||||
result.buckets = buckets.clone();
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
public class Item13Example05 {
|
||||
|
||||
public static void main(String[] args) {}
|
||||
}
|
||||
|
||||
class HashTable2 implements Cloneable {
|
||||
|
||||
private Entry[] buckets = new Entry[] {};
|
||||
|
||||
private static class Entry {
|
||||
final Object key;
|
||||
Object value;
|
||||
Entry next;
|
||||
|
||||
Entry(Object key, Object value, Entry next) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
Entry deepCopy() {
|
||||
return new Entry(key, value, next == null ? null : next.deepCopy());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
HashTable2 result = (HashTable2)super.clone();
|
||||
result.buckets = new Entry[buckets.length];
|
||||
for (int i = 0; i < buckets.length; i++) {
|
||||
if (buckets[i] != null) {
|
||||
result.buckets[i] = buckets[i].deepCopy();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package com.jueee.item13;
|
||||
|
||||
public class Item13Example06 {
|
||||
|
||||
public static void main(String[] args) {}
|
||||
}
|
||||
|
||||
class HashTable3 implements Cloneable {
|
||||
|
||||
private Entry[] buckets = new Entry[] {};
|
||||
|
||||
private static class Entry {
|
||||
final Object key;
|
||||
Object value;
|
||||
Entry next;
|
||||
|
||||
Entry(Object key, Object value, Entry next) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.next = next;
|
||||
}
|
||||
|
||||
Entry deepCopy() {
|
||||
Entry result = new Entry(key, value, next);
|
||||
for (Entry p = result; p.next != null; p = p.next) {
|
||||
p.next = new Entry(p.next.key, p.next.value, p.next.next);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object clone() throws CloneNotSupportedException {
|
||||
try {
|
||||
HashTable3 result = (HashTable3)super.clone();
|
||||
result.buckets = new Entry[buckets.length];
|
||||
for (int i = 0; i < buckets.length; i++) {
|
||||
if (buckets[i] != null) {
|
||||
result.buckets[i] = buckets[i].deepCopy();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (CloneNotSupportedException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package com.jueee.item14;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Item14Example01 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Set<String> s = new TreeSet<>();
|
||||
s.add("c");
|
||||
s.add("a");
|
||||
s.add("b");
|
||||
s.add("e");
|
||||
s.add("a");
|
||||
s.add("d");
|
||||
System.out.println(s); // [a, b, c, d, e]
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.jueee.item14;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Item14Example02 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Set<PhoneNumber1> set = new TreeSet<>();
|
||||
set.add(new PhoneNumber1(707, 867, 5309));
|
||||
set.add(new PhoneNumber1(907, 867, 5309));
|
||||
set.add(new PhoneNumber1(507, 867, 5309));
|
||||
set.add(new PhoneNumber1(707, 667, 5309));
|
||||
set.add(new PhoneNumber1(707, 967, 5309));
|
||||
|
||||
for(PhoneNumber1 phoneNumber1: set) {
|
||||
System.out.println(phoneNumber1.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class PhoneNumber1 implements Comparable<PhoneNumber1>{
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber1(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(PhoneNumber1 pn) {
|
||||
int result = Short.compare(areaCode, pn.areaCode);
|
||||
if (result == 0) {
|
||||
result = Short.compare(prefix, pn.prefix);
|
||||
if (result == 0)
|
||||
result = Short.compare(lineNum, pn.lineNum);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
package com.jueee.item14;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public class Item14Example03 {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Set<PhoneNumber2> set = new TreeSet<>();
|
||||
set.add(new PhoneNumber2(707, 867, 5309));
|
||||
set.add(new PhoneNumber2(907, 867, 5309));
|
||||
set.add(new PhoneNumber2(507, 867, 5309));
|
||||
set.add(new PhoneNumber2(707, 667, 5309));
|
||||
set.add(new PhoneNumber2(707, 967, 5309));
|
||||
|
||||
for(PhoneNumber2 phoneNumber1: set) {
|
||||
System.out.println(phoneNumber1.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class PhoneNumber2 implements Comparable<PhoneNumber2>{
|
||||
|
||||
private final short areaCode, prefix, lineNum;
|
||||
|
||||
public PhoneNumber2(int areaCode, int prefix, int lineNum) {
|
||||
this.areaCode = rangeCheck(areaCode, 999, "area code");
|
||||
this.prefix = rangeCheck(prefix, 999, "prefix");
|
||||
this.lineNum = rangeCheck(lineNum, 9999, "line num");
|
||||
}
|
||||
|
||||
private static short rangeCheck(int val, int max, String arg) {
|
||||
if (val < 0 || val > max)
|
||||
throw new IllegalArgumentException(arg + ": " + val);
|
||||
|
||||
return (short)val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%03d-%03d-%04d", areaCode, prefix, lineNum);
|
||||
}
|
||||
|
||||
private static final Comparator<PhoneNumber2> COMPARATOR =
|
||||
Comparator.comparingInt((PhoneNumber2 pn) -> pn.areaCode)
|
||||
.thenComparingInt(pn -> pn.prefix)
|
||||
.thenComparingInt(pn -> pn.lineNum);
|
||||
|
||||
@Override
|
||||
public int compareTo(PhoneNumber2 pn) {
|
||||
return COMPARATOR.compare(this, pn);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user