比较两个值是否相等

250 阅读7分钟

判断两个对象是否相等时,有两种方法,==equals()

  • == : 它的作用是判断两个对象的地址是不是相等。对于基本类型,比较的是值,对于对象,比较的是对象的存放地址。
  • equals() : 它的作用也是判断两个对象是否相等。

    • 如果类中覆盖了该方法,那么通常是比较两个对象的内容是否相等。如果相等,则返回 true;如果不等,则返回 false
    • 如果类中没有覆盖该方法,那么比较的是两个对象的地址是否相等,等价于==

在举例子之前,先说一下基本类型和包装类型的东西。

1. 基本类型和包装类型

1.1 包装类型

基本类型有8种,都有各自的默认值,默认值均不为null,分别是:

  • 6 种数字类型 :byteshortintlongfloatdouble
  • 1 种字符类型:char
  • 1 种布尔型:boolean

1.2 包装类型

包装类型是基本类型所对应的对象。由于包装类型是对象,所以可以用于泛型,

1.3 信息总结

这 8 种基本数据类型的默认值以及所占空间的大小如下:

基本类型位数字节默认值对应的包装类型包装类的默认值
数字型short1620Shortnull
int3240Integernull
long6480LLongnull
字符型byte810Bytenull
char162'u0000'Characternull
数字型float3240fFloatnull
double6480dDoublenull
布尔型boolean1FALSEBooleannull

1.4 在哪里放着

  • 基本类型的局部变量在栈中的局部变量表中存放,基本类型的成员变量在堆中存放。
  • 包装类型是对象类型,几乎所有的对象类型都在中。

image.png

小纸条

如果类中成员变量使用基本类型(如 int),并且不被static修饰的话,会存放在堆中。

这种情况,使用基本类型对应的包装类型更好。

 class Student {
     private int age;
 }
 ​
 // 改为包装类型
 class Student {
     private Integer age;
 }

1.5 包装类型的缓存机制

包装类型会用缓存机制来提高性能。各个包装类型的缓存能数据范围如下:

包装类型缓存数据范围
数字型Short[ -128, 127 ]
Integer[ -128, 127 ]
Long[ -128, 127 ]
字符型Byte[ -128, 127 ]
Character[ 0, 127 ]
布尔型Booleantrue / false

如果要创建的包装类型的数据在缓存中能找到,那么会直接使用缓存中的数据,而不会创建新的对象。比如

 Integer i1 = 10;    // 10 < 127,i1使用缓存中的数据
 // 判断 i1 的情况
 // 如果 i1 使用缓存中的数据,没有创建新对象,那么再创建一个值为10的对象,
 // 两个对象的地址应该是相同的,都是缓存中的数据的地址
 Integer i12 = 10;
 System.out.println(i1 == i12);   // 输出结果为 true    
 ​
 Integer i2 = 128;    // 128 > 127,i2创建新的对象
 // 判断 i2 的情况
 // 如果 i2,创建了新对象,没有使用缓存中的数据,那么再创建一个值为128的对象,
 // 两个对象的地址应该是不同的,因为两个对象的地址不会相同
 Integer i22 = 128;
 System.out.println(i2 == i22);    // 输出结果为 false  

1.6 自动拆箱与装箱

自动装箱和拆箱,是自动在基本类型和包装类型之间进行转换,这种转换是在赋值、方法调用时等情况下发生的,比如

 // 基本类型 转为 包装类型
 Integer a = 200;   
 // 200是基本类型,但是可以直接赋给 Integer 变量a ,而不用进行类型转换
 // 如果没有自动装箱,就只能使用 Integer a = new Integer(200); 来创建变量
  
 // 基本类型 转为 包装类型
 int b = a;    // a 是包装类型,但是可以直接赋给int类型 ,而不用进行类型转换
 // a 是 Integer类型,b 是 int 类型,如果没有自动拆箱,就需要自己进行类型转换

自动拆箱和装箱,是在本来需要自己进行类型转换的时候,交给Java进行,而不用自己强制转换。

2. 比较

  • 基本类型和基本类型比较,使用 == 即可。
  • 基本类型和包装类型比较,使用 ==
  • 对象和对象比较,使用equal()

2.1 数字比较

基本类型和基本类型比较,比较的是值是否相等。

 int a = 10;
 int b = 10;    // b == a
 int c = new Integer(10);    // c == a

基本类型和包装类型比较,比较的也是值是否相等。

 int a = 10;
 Integer aa = new Integer(10);    // aa == a

包装类型和包装类型比较,比较的是地址是否相等。

 Integer a = new Integer(10);
 Integer b = new Integer(10);    // b != a,不是同一个对象,地址不同

2.2 String 比较

String是一个常量,存放在运行时常量池(在方法区/元空间)。是不可变的(在创建之后无法更改),它的底层是由char[]实现的。比如:

 String str = "abc";
 // 等价于
 char[] data = {'a', 'b', 'c'};
 String str = new String(data);

由于String对象是不可变的,所以它们是可以被共享的。在创建新的String对象时,如果常量池中存在该对象的值,那么不会创建新的字符串,而是会直接使用常量池中存在的字符串。如果常量池中不存在该对象的值,那么会在常量池中创建字符串。

所以,比较两个类型为 String的字符串是否相等,最好使用equals()

 // 使用常量池的数据
 String s1 = "abc";    // 在常量池中创建一个 abc 字符串
 String s2 = "abc";    // 使用常量池中的数据
 System.out.println(s1 == "abc");    // true。s1 和 "abc" 的地址相同
 System.out.println(s2 == "abc");    // true。s2 和 "abc" 的地址相同
 System.out.println(s1 == s2);    // true。s1 和 s2 指向的是同一个对象
 System.out.println(s1.equals(s2));    // true.s1 和 s3 的值相等
 ​
 // 使用 new 创建新对象
 String s3 = new String("abc");    // 创建一个新对象
 System.out.println(s3 == "abc");    // false。s3 和 "abc" 的地址相同,s3 使用的不是常量池中的数据
 System.out.println(s3 == s1);    // false。s1 和 s3 不是一个对象
 System.out.println(s1.equals(s3));    // true。s1 和 s3 的值相等
 ​
 String s4 = "def";    // 在常量池中创建一个 def 字符串
 System.out.println(s1 == s4);

字符串和对象引用的示意图如下:

字符串和对象引用

2.3 对象比较

比较两个对象的地址是否相同,如果相同则返回 true,如果不同则返回 false

 // 创建一个 Integer 对象
 Integer a = new Integer(10);
 // 使用两个变量作为对比
 Integer b = new Integer(10);    // b != a,不是同一个对象
 Integer c = a;    // c == a,是同一个对象

对象和对象引用的示意图如下:

对象和对象引用

3. HashMap相关

由于 HashMap要说的内容和上面的比较情况稍有不同,这里把 HashMap中想要说的东西单独拎出来。

1)containsKey()

HashMap中常常需要判断某个值是否在HashMap中,会用到containsKey()。该方法判断某个值在HashMap中是使用equals()来判断的, 也就是说,如果一个类覆盖了这个方法,那么是并根据值来判断的;如果一个类没有覆盖这个方法,那么是根据地址来判断的。

containsKey()有说到,key==null ? k==null : key.equals(k)(其中,keyHasMap中的键,k是要差值的值)当哈希表中键不为空并且等于其中一个键值时,返回true。简短的看,containsKey()就是使用 equal()方法比较keyk是否相等。

举个String例子:

 Map<String, Integer> map = new HashMap<>();
 map.put("a", 1);
 map.put("b", 2);
 map.put("c", 3);
 ​
 String s = new String("a");    // 创建一个对象
 // 比较的是s和map中的键值是否有相等的。因为String重写了 equals() 方法
 System.out.println(map.containsKey(s));  // true

举个一维数组的例子:

 Map<int[], Integer> map = new HashMap<>();
 map.put(new int[]{1, 2}, 1);
 map.put(new int[]{2, 3}, 2);
 map.put(new int[]{3, 4}, 2);
 ​
 int[] array = new int[]{1, 2};    // 创建了一个新数组
 // 比较的是array和map中的键值的地址是否有相等的。因为 int[]没有重写 equals()方法
 System.out.println(map.containsKey(array));    // false

小纸条

如果需要比较两个数组是否相等时,需要使用 Arrays类的 equals()方法,比如

 int[] array1 = {1, 2};
 int[] array2 = {1, 2};
 Arrays.equals(array1, array2);  // true

2)value比较时的一个情况

值value 和 基本类型比较

先说一种比较常见的情况,我们在创建一个 HashMap对象为 map1后,有时会需要比较 HashMap中是否包含某个值,一般使用 containsValue() 判断是否数据包含在 HashMap中(containsValue()的使用和 containsKey()类似)。比如

 Map<Character, Integer> map1 = new HashMap<>();
 map1.put('a', 1);
 map1.put('b', 200);
 // 一般使用 containsValue() 判断是否数据包含在 HashMap 中。比如
 System.out.println(map1.containsValue(1));     // true
 System.out.println(map1.containsValue(200));     // true

为了说明下一步的例子,这里使用 == 进行比较,给出比较的结果比如

 Map<Character, Integer> map1 = new HashMap<>();
 map1.put('a', 1);
 map1.put('b', 200);
 // 但是为了说明下面的例子,这里使用 == 进行比较,比如
 System.out.println(map1.get('a') == 1);    // true
 System.out.println(map1.get('b') == 200);   // true

这里,map.get()得到的数据类型是Integer,在和基本类型 int比较时,相当于两个基本类型int比较,比的是值是否相等。

值value 和 包装类型比较

下面说的一种情况,我不知道什么时候用到是合适的,一个朋友遇到时,也是挺想不到的。

创建一个 HashMap对象为 map1,存储一些数据。

创建另一个 HashMap对象为 map2,存储和map1相同的数据。

 Map<Character, Integer> map1 = new HashMap<>();
 map1.put('a', 1);
 map1.put('b', 200);
 // 创建另一个 HashMap ,存储相同的数据
 Map<Character, Integer> map2 = new HashMap<>();
 map2.put('a', 1);
 map2.put('b', 200);
 // 比较
 System.out.println(map2.get('a') == map1.get('a'));    // true
 System.out.println(map2.get('b') == map1.get('b'));    // false
 // 如果要比较,需要使用 equals() 
 System.out.println(map2.get('a').equals(map1.get('a')));    // true
 System.out.println(map2.get('b').equals(map1.get('b')));    // true

这里出现这样的结果,是一个对象和一个对象的比较(本章2.1),比较的是map中存储对象的地址是否相等。