关于Java中的“==”、“equals”和“compareTo”的区别(结合源码分析)
在Java的开发中我们经常都会使用到 “==”、“equals”和compareTo,但是但是很多时候我们都是不知其所以然。本文主要结合JDK的源码和例子来进行分析Java中的“==”、“equals”和“compareTo”的区别。
1、“==”比较运算符
“==”是最简单也最容易理解的,如果等号的两边是基本数据类型,比如int,double,那么“==”就用来单纯的比较他们的值大小如果等号两边放的是两个对象,那么就会比较他们在内存当中的地址。
下面我们来看一个实例:
public static void javaDoubleEquals(){
double a = 20.57;
double b = 20.57;
String str1 = "I miss you";
String str2 = "I miss you";
System.out.println(a == b);
System.out.println(str1 == str2);
}
运行结果:
因为相同的字符串内容,在地址上是一样。在Java中,String是有一个String pool的,里面存放了可以共享的字符串对象,在声明一个String对象后,会首先去找是否存在相同的String内容,如果有的话是不会创建新的对象的。在这里str2实际上是引用了str1的对象的值,其自己并没有创建对象,所以这里的答案是true。但是还有另外一种情况,那是什么呢?我们来看看下面这个实例:
public static void javaDoubleEquals(){
String str1 = "I miss you";
String str2 = new String("I miss you");
System.out.println(str1 == str2);
}
运行结果:
这是什么原因呢?相信大家都发现了new这个关键字。这就对了我们这里是重新创建了一个字符串str2,所以其在内存中的物理地址就与str2不相同了,所以运行的结果就为false。
2、“equals”比较运算符
equals比较的是两个字符串中的每个字符是否相同。为了更加形象的理解我们继续来看一看下面的例子和源码:
public static void javaEquals(){
String str1 = "Believe that time will make us more mature";
String str2 = "Believe that time will make us more mature";
String str3 = new String("Believe that time will make us more mature");
System.out.println(str1.equals(str2));
System.out.println(str1.equals(str3));
}
运行结果:
我们可以从运行的结果发现运行的结果是两个true。那么一问就来了,为什么str1.equals(str3)的结果也为true。他们两个的物理地址不相同运行的结果应该是false才对啊。要解决这个疑问我们最好的办法就是查看equals的源码,下面我们来看看这个equals的源码:
@Stable
private final byte[] value;
private final byte coder;
byte coder() {
return COMPACT_STRINGS ? coder : UTF16;
}
byte[] value() {
return value;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
@Native static final byte LATIN1 = 0;
@Native static final byte UTF16 = 1;
static char getChar(byte[] val, int index) {
assert index >= 0 && index < length(val) : "Trusted caller missed bounds check";
index <<= 1;
return (char)(((val[index++] & 0xff) << HI_BYTE_SHIFT) |
((val[index] & 0xff) << LO_BYTE_SHIFT));
}
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (coder() == aString.coder()) {
return isLatin1() ? StringLatin1.equals(value, aString.value)
: StringUTF16.equals(value, aString.value);
}
}
return false;
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
for (int i = 0; i < value.length; i++) {
if (value[i] != other[i]) {
return false;
}
}
return true;
}
return false;
}
public static boolean equals(byte[] value, byte[] other) {
if (value.length == other.length) {
int len = value.length >> 1;
for (int i = 0; i < len; i++) {
if (getChar(value, i) != getChar(other, i)) {
return false;
}
}
return true;
}
return false;
}
我们通过查看equals的源码我们就会发现在Java中equals在比较两个字符串的时候首先使用的是“==”来比较两个字符串的物理地址是否相等,如果两个字符串的物理地址相同那么这两个字符串必定相同。如果两个字符串的物理地址不相同就判断equals中的字符串是否是字符串,如果不是字符串那么必定返回的是false。如果是字符串那么就判断两个字符串的coder(coder是从Java9开始引入的一个变量,value是String的核心属性,String字符串本质上就是一个字符数组嘛,所以String的“值”实际上就在这个数组里。在以前value是char[]型的(至少Java8还是如此),而现在变成了byte[]。coder是value中的存放的byte的编码标识符,coder可能的值是LATIN1或UTF16。详细的讲解可以参考:https://www.kaelli.com/33.html)是否相等,如果coder相等那么必定返回的是false;如果coder相等那么就将两个字符串的长度进行比较,如果长度相等就逐个对比两个字符串的字符并返回结果。这也就是“equals”和“==”的区别。注意:此处的源码对于初学者可能有一点深,所以下面附上简化的理解代码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3、“compareTo”比较运算符
compareTo(Object o)方法是java.lang.Comparable<T>接口中的方法,当需要对某个类的对象进行排序时,该类需要实现Comparable<T>接口的,必须重写public int compareTo(T o)方法,比如MapReduce中Map函数和Reduce函数处理<key,value>,其中需要根据key对键值对进行排序,所以,key实现了WritableComparable<T>接口,实现这个接口可同时用于序列化和反序列化。WritableComparable<T>接口 (用于序列化和反序列化)是Writable接口和Comparable<T>接口的组合。而且compareTo返回的结果与“==”和“equals”的boolean类型不同,其返回的是一个ASCII码值。下面我们还是继续看一个例子和源码:
public static void javaCompareTo(){
String str1 = "Waiting for the results to see who will be free and easy at the end";
String str2 = "Waiting for the results to see who will be free and easy at the end";
String str3 = new String("Waiting for the results to see who will be free and easy at the end");
System.out.println(str1.compareTo(str2));
System.out.println(str1.compareTo(str3));
}
运行结果:
我们从运行的结果可以看到返回的两个结果都是0,这就说明比较的结果是两个字符串是相等的。在这里同样会有为什么str1.compareTo(str3)的结果也为0的疑问。我们继续分析源码来解决这个疑问:
/*
* StringIndexOutOfBoundsException if {@code offset}
* is negative or greater than {@code length}.
*/
static void checkOffset(int offset, int length) {
if (offset < 0 || offset > length) {
throw new StringIndexOutOfBoundsException("offset " + offset +
",length " + length);
}
}
public static int compareToLatin1(byte[] value, byte[] other) {
return -StringLatin1.compareToUTF16(other, value);
}
public static char getChar(byte[] val, int index) {
return (char)(val[index] & 0xff);
}
public static int compareTo(byte[] value, byte[] other) {
int len1 = value.length;
int len2 = other.length;
return compareTo(value, other, len1, len2);
}
public static int compareTo(byte[] value, byte[] other, int len1, int len2) {
int lim = Math.min(len1, len2);
for (int k = 0; k < lim; k++) {
if (value[k] != other[k]) {
return getChar(value, k) - getChar(other, k);
}
}
return len1 - len2;
}
public static int compareToUTF16(byte[] value, byte[] other) {
int len1 = length(value);
int len2 = StringUTF16.length(other);
return compareToUTF16Values(value, other, len1, len2);
}
public static int compareToUTF16(byte[] value, byte[] other, int len1, int len2) {
checkOffset(len1, length(value));
checkOffset(len2, StringUTF16.length(other));
return compareToUTF16Values(value, other, len1, len2);
}
private static int compareToUTF16Values(byte[] value, byte[] other, int len1, int len2) {
int lim = Math.min(len1, len2);
for (int k = 0; k < lim; k++) {
char c1 = getChar(value, k);
char c2 = StringUTF16.getChar(other, k);
if (c1 != c2) {
return c1 - c2;
}
}
return len1 - len2;
}
public int compareTo(String anotherString) {
byte v1[] = value;
byte v2[] = anotherString.value;
if (coder() == anotherString.coder()) {
return isLatin1() ? StringLatin1.compareTo(v1, v2)
: StringUTF16.compareTo(v1, v2);
}
return isLatin1() ? StringLatin1.compareToUTF16(v1, v2)
: StringUTF16.compareToLatin1(v1, v2);
}
我们通过查看源码可以分析出可以观察出,(此处关于coder的判断省略,在前面equals中有讲解;以及一些异常情况省略)这里实际上是获取的字符串(也可以是其他对象)的长度,然后作减法,这里的减法就是ASCII码的减法,所以compareTo()会返回数字,如果两个字符串内容相同,会返回0,字符串a大于字符串b,会返回相差的ASCII码的正数,字符串a小于字符串b,会返回相差的ASCII码的负数。同样这里给出简化的理解代码:
public int compareTo(String anotherString) {
int len1 = value.length;
int len2 = anotherString.value.length;
int lim = Math.min(len1, len2);
char v1[] = value;
char v2[] = anotherString.value;
int k = 0;
while (k < lim) {
char c1 = v1[k];
char c2 = v2[k];
if (c1 != c2) {
return c1 - c2;
}
k++;
}
return len1 - len2;
}
4、equals和compareTo的区别
equals就简单一些,只返回true或者false;compareTo()会返回二者的差值,即返回的是一个数字。
最后,equals()和compareTo()都可以判断其他基本数据类型,比如说Integer,Java的源码中对这两者方法都做了一些重载,可以根据参数的类型去自动匹配相应的方法,这些原理都非常的简单,只是一些简单的减法或者(?:)这类判断。
5、重写对象equals和compareTo方法
重写对象equals和compareTo方法可以参考https://blog.csdn.net/u013591605/article/details/75267629,非常的简单。