一、三个例子
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方法的时候本质上是在调用父类Object的equals,而我们通过源码发现Object的equals方法比较的就是两个对象的地址 - 那么可以模仿
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);