关于Java中的“==”、“equals”和“compareTo”的区别(结合源码分析)

关于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,非常的简单。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值