本文主要阐述代码中的判等问题。
equals
== 和 equals
概述
比较值的内容,基本类型使用 == ,引用类型使用 equals 。
整型
测试用例
1 | /**1**/ |
分析
Integer a = 127转换为Integer.valueOf(127),转换在内部做了缓存,使得两个 Integer 指向同一个对象,所以 == 返回 true。1
2
3
4
5public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}由于默认情况下会缓存[-128, 127]的数值,而 128 处于这个区间之外,故此时 == 返回false。
实例化的对象与缓存,以及实例化的对象之间, == 都会返回false。
装箱的 Integer 会先拆箱再比较,即数值比较,故 == 返回true。
结论
- 比较 Integer 的值请使用 equals,而不是 ==。
- 注意 Integer 的 == 比较返回为真的范围是[-128, 127]。
字符串类型
测试用例
1 | /**1**/ |
字符串常量池机制
字符串常量池机制的设计初衷是为了节省内存。当代码中出现双引号形式创建字符串对象时,JVM 会先对这个字符串进行检查,如果字符串常量池中存在相同内容的字符串对象的引用,则将这个引用返回;否则,创建新的字符串对象,然后将这个引用放入字符串常量池,并返回该引用。这种机制,就是字符串驻留或池化。
分析
- 由于字符串驻留机制,故用双引号赋值的两个内容相等的字符串,== 返回true。
- 实例化的两个对象引用不同,故 == 返回false。
- 使用 String 提供的 intern 方法也会走常量池机制,所以同样能得到 true。
注意
虽然使用 new 声明的字符串调用 intern 方法,也可以让字符串进行驻留,但在业务代码中滥用 intern,可能会产生性能问题。
原因:字符串常量池是一个固定容量的 Map。如果容量太小(Number of buckets=60013)、字符串太多(1000 万个字符串),那么每一个桶中的字符串数量会非常多,所以搜索起来就很慢。
解决:解决方式是,设置 JVM 参数 -XX:StringTableSize,指定更多的桶。
结论:没事别轻易用 intern,如果要用一定要注意控制驻留的字符串的数量,并留意常量表的各项指标。
equals 的实现
概述
equals 在 Object 类中的实现方式,其实就是 == 判断;而 String 和 Integer 都重写了 equals 方法。因此,新定义的类如果不重写 equals 方法,则默认使用 == 判等,比较的是对象的引用。
实现方式
如何实现 equals?
- 指针判等,若相等则返回true;
- 判空,若比较对象为空则返回false;
- 判断两个对象类型,若不同则返回false;
- 进行类型转换,逐一判断所有字段。
1 | @Override |
hashCode
概述
由源码注释可知,hashCode 会返回对象的哈希值,该方法会影响散列表,如HashTable。
注意
注意 hashCode 要与 equals 配对实现。
散列表需要使用 hashCode 来定位元素放到哪个桶。如果自定义对象没有实现自定义的 hashCode 方法,就会使用 Object 超类的默认实现,得到的两个 hashCode 是不同的,导致无法满足需求。
实现
重写 hashCode 方法:
1 | @Override |
compareTo
概述
该方法由Comparable接口定义,用于做排序时的比较,返回值为负数、零和正数,分别代表少于、等于和多于。
注意
如果要使用 Collections 的排序,则自定义类就要实现 Comparable。
而重写 compareTo 时要注意其逻辑与 equals 保持一致。
实现
重写compareTo方法:
1 |
|
结论
对于自定义的类型,如果要实现 Comparable,请记得 equals、hashCode、compareTo 三者逻辑一致。
还有点啥
- 建议在 IDE 中安装阿里巴巴的 Java 规约插件。
- getClass 和 instanceof 的区别:
- instanceof进行类型检查规则是:判断是否为该类或者该类的子类;
- getClass获得类型信息采用==来进行检查是否相等的操作是严格的判断。不会存在继承方面的考虑。