Java中的==和equals详解

一、三个例子

1.==和equals用于String对象的比较

  • 代码
    public static void main(String[] args) {
        String s = new String("abc");
        String  s1  = new String("abc");
        System.out.println(s == s1);
        System.out.println(s.equals(s1));
    }
  • 输出:
    在这里插入图片描述
  • 这很容易解释嘛,因为我们都知道==比较的是地址,equals比较的是字符串内容

2.==和equals用于Object对象的比较

  • 代码
    public static void main(String[] args) {

        Object s = new Object();
        Object s1 = new Object();
        System.out.println(s == s1);
        System.out.println(s.equals(s1));
    }
  • 结果
    在这里插入图片描述

  • 第一个是false还可以理解,为什么第二个也是false,这两个内容不是相同吗?后面会详细分解

3.==和equals用于自定义对象的比较

  • 代码
    public static void main(String[] args) {
        Person s = new Person("xpt");
        Person s1 = new Person("xpt");
        System.out.println(s == s1);
        System.out.println(s.equals(s1));
    }
class Person{
    String name;
    public Person(String name){
        this.name = name;
    }
}
  • 结果
    在这里插入图片描述

  • 第二个居然也是false,这两个对象的内容是完全一致的啊

二、 ==和equals的本质区别

1.==比较的永远是地址

2.equals本质上也比较的是地址,但是有些类重写了equals方法,达到比较对象内容的目的

3.分析

  • 因为Object类是所有类的父类,我们先观察它的源码

在这里插入图片描述

  • 很清楚的可以看到,这里的equals调用的就是==,所以这里可以认为equals本质上也是比较地址
  • 下面我们看String类的源码
    在这里插入图片描述
  • 这也很清楚的说明问题了,String类的equals是因为被重写成了比较两个字符串的内容是否相等
  • 那么这也就可以解释上面第三个例子中,两个自定义对象内容相同,但是调用equals比较时却返回的是false了,因为没有重写equals方法,那么在调用equals方法的时候本质上是在调用父类Objectequals,而我们通过源码发现Objectequals方法比较的就是两个对象的地址
  • 那么可以模仿String类,来重写自定义类的equals方法,达到按我们的目的就行比较
 public static void main(String[] args) {
        Person s = new Person("xpt");
        Person s1 = new Person("xpt");
        System.out.println(s == s1);
        System.out.println(s.equals(s1));
    }


}
class Person{
    String name;
    public Person(String name){
        this.name = name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o instanceof Person){
            //定义Person类中name属性相等 则认为这两个对象相等
            //那么如果还有多的属性,则可以按我们想要的方式来定义这两个对象是否相等
            return this.name.equals(((Person) o).name);
        }
        return false;
    }
}

  • 结果
    在这里插入图片描述

三、 ==在比较基本数据类型的时候也是在比较地址吗?

  • 是的,本质上也是在比较地址

  • 但是对于基本数据类型来说,值相同的变量地址也一定相同

  • 但是为什么呢?这就跟基本数据类型在Java虚拟机中的具体存储方式有关系了

  • 这里我们可以简单了解下(下面内容来自网络)

  • java的基本数据类型共有8种,即int,short,long,byte,float,double,boolean,char(注意,并没有String的基本类型 )。这种类型的定义是通过诸如int a = 3;long b = 255L;的形式来定义的。如int a = 3;这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

  • 另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。比如: 我们同时定义:

  • int a=3; int b=3;

  • 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b这个引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

  • 定义完a与b的值后,再令a = 4;那么,b不会等于4,还是等于3。在编译器内部,遇到时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

四、equals使用的注意事项

1. 避免null调用equals

  • Object类的equals方法容易抛出空指针异常,应使用常量或确定值的对象来调用equls

下面来看一个空指针异常的例子

public class temp {
    public static void main(String[] args) {

        String s = null;
        if (s.equals("aaa")){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
}

  • 输出
    在这里插入图片描述

原因就是null调用了equals

  • 改进
public class temp {
    public static void main(String[] args) {

        String s = null;
        if ("aaa".equals(s)){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
}
  • 输出
    在这里插入图片描述

2. 推荐使用java.util.Objects.equals

public class temp {
    public static void main(String[] args) {

        String s = null;
        if (Objects.equals(s, "Aaa")){
            System.out.println(true);
        }else {
            System.out.println(false);
        }
    }
}
  • 为何这种方式值得推荐呢?
  • 一看源码便知
 public static boolean equals(Object a, Object b) {
 //0. 如果a == null,那么a.equals(b)就不会被执行,空指针异常得以避免
        return (a == b) || (a != null && a.equals(b));
    }

3. 所有整型包装类型值的比较都需要用equals

这里需要注意几个限定词,整型包装类型,值的比较

先来看一个例子,尝试猜一下下面的输出

public class temp {
    public static void main(String[] args) {

        Integer a = new Integer(3);
        Integer b = new Integer(3);


        System.out.println(a == b);
        //   equals 比较对象的值 所以true
        System.out.println(a.equals(b));
        System.out.println("--------------");
    
        Integer x = 3;
        Integer y = 3;
        System.out.println(x == y);
        System.out.println(x.equals(y));
        System.out.println("--------------");
    
        Integer w = 128;
        Integer v = 128;
        System.out.println(w == v);
        System.out.println(w.equals(v));
    }
}
  • 输出
    在这里插入图片描述

  • 分析

public class temp {
    public static void main(String[] args) {

        Integer a = new Integer(3);
        Integer b = new Integer(3);

        //0. a b是通过直接new的方式创建的,也就是在堆中产生了两个不同的对象
        //   == 比较对象地址,二者当然不同 false
        System.out.println(a == b);
        //   equals 比较对象的值 所以true
        System.out.println(a.equals(b));
        System.out.println("--------------");
        //1. x y通过自动装箱创建,在 -128~127会有一个缓存机制
        //   也就是这个范围内的数在内存中只有一份
        //   所以== 为true equals 为true
        Integer x = 3;
        Integer y = 3;
        System.out.println(x == y);
        System.out.println(x.equals(y));
        System.out.println("--------------");
        //2. 超过了 -128~127的范围,当然也是都需要新建对象
        //   所以 == false equals false
        Integer w = 128;
        Integer v = 128;
        System.out.println(w == v);
        System.out.println(w.equals(v));
    }
}

  • 所以在比较整型包装类型的值的时候,避免出错,都统一使用equals

4. 浮点数之间的比较

浮点数的比较都需要确定一个精度

4.1 Double包装类型不能用==来比较

public class temp {
    public static void main(String[] args) {

        float a = 0.1f;
        float b = 0.1f;
        System.out.println(a == b);
        Double c = 0.1;
        Double d = 0.1;
        System.out.println(c == d);
        System.out.println(c.equals(d));
    }
}
  • 输出

在这里插入图片描述

  • 原因也很简单,Double就没有整型包装类型的那种对某个范围内的数有缓存机制了,当然也是由于精度的问题。

4.2 BigDecimal的引入和用处

  • 浮点数之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用 equals 来判断

实例

public class temp {
    public static void main(String[] args) {
        float a = 1.0f - 0.9f;
        float b = 0.9f - 0.8f;
        System.out.println(a);
        System.out.println(b);
        System.out.println( a== b);
    }
}
  • 输出
    在这里插入图片描述

4.3 使用BigDecimal来定义浮点数,然后再操作

public class temp {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("1.0");
        BigDecimal b = new BigDecimal("0.9");
        BigDecimal c = new BigDecimal("0.8");

        BigDecimal x = a.subtract(b);
        BigDecimal y = b.subtract(c);
        System.out.println(x);
        System.out.println(y);
        System.out.println(x.equals(y));

    }
}

  • 输出
    在这里插入图片描述

4.4 BigDecimal大小比较:a.compareTo(b)

public class temp {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("1.0");
        BigDecimal b = new BigDecimal("0.9");
        BigDecimal c = new BigDecimal("0.9");
        // > 1
        System.out.println(a.compareTo(b));
        // < -1
        System.out.println(b.compareTo(a));
        // = 0
        System.out.println(b.compareTo(c));
    }
}

在这里插入图片描述

4.5 BigDecimal设置小数点精确位数

public class temp {
    public static void main(String[] args) {
        BigDecimal a = new BigDecimal("1.01313232131");
        BigDecimal b = a.setScale(3,BigDecimal.ROUND_HALF_DOWN);
        System.out.println(b);
        BigDecimal d = a.setScale(10,BigDecimal.ROUND_HALF_DOWN);
        System.out.println(d);
    }
}

在这里插入图片描述

4.7 BigDecimal建议用" "作为构造函数的参数

  • 推荐使用它的 BigDecimal(String) 构造方法来创建对象
        BigDecimal a = new BigDecimal("0.1");
        BigDecimal b = BigDecimal.valueOf(0.1);
        

版权声明:本文为fat_cai_niao原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。