判断两个对象是否相等时,有两种方法,== 和 equals()。
== : 它的作用是判断两个对象的地址是不是相等。对于基本类型,比较的是值,对于对象,比较的是对象的存放地址。
equals() : 它的作用也是判断两个对象是否相等。
true;如果不等,则返回 false。==。在举例子之前,先说一下基本类型和包装类型的东西。
基本类型有8种,都有各自的默认值,默认值均不为null,分别是:
6 种数字类型 :byte、short、int、long、float、double
1 种字符类型:char
1 种布尔型:boolean
包装类型是基本类型所对应的对象。由于包装类型是对象,所以可以用于泛型,
这 8 种基本数据类型的默认值以及所占空间的大小如下:
| 基本类型 | 位数 | 字节 | 默认值 | 对应的包装类型 | 包装类的默认值 | |
|---|---|---|---|---|---|---|
| 数字型 | short | 16 | 2 | 0 | Short | null |
| int | 32 | 4 | 0 | Integer | null | |
| long | 64 | 8 | 0L | Long | null | |
| 字符型 | byte | 8 | 1 | 0 | Byte | null |
| char | 16 | 2 | ‘u0000’ | Character | null | |
| 数字型 | float | 32 | 4 | 0f | Float | null |
| double | 64 | 8 | 0d | Double | null | |
| 布尔型 | boolean | 1 | — | FALSE | Boolean | null |
基本类型的局部变量在栈中的局部变量表中存放,基本类型的成员变量在堆中存放。
包装类型是对象类型,几乎所有的对象类型都在堆中。

小纸条
如果类中成员变量使用基本类型(如 int),并且不被static修饰的话,会存放在堆中。
这种情况,使用基本类型对应的包装类型更好。
class Student {
private int age;
}
// 改为包装类型
class Student {
private Integer age;
}
包装类型会用缓存机制来提高性能。各个包装类型的缓存能数据范围如下:
| 包装类型 | 缓存数据范围 | |
|---|---|---|
| 数字型 | Short | [ -128, 127 ] |
| Integer | [ -128, 127 ] | |
| Long | [ -128, 127 ] | |
| 字符型 | Byte | [ -128, 127 ] |
| Character | [ 0, 127 ] | |
| 布尔型 | Boolean | true / 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
自动装箱和拆箱,是自动在基本类型和包装类型之间进行转换,这种转换是在赋值、方法调用时等情况下发生的,比如
// 基本类型 转为 包装类型
Integer a = 200;
// 200是基本类型,但是可以直接赋给 Integer 变量a ,而不用进行类型转换
// 如果没有自动装箱,就只能使用 Integer a = new Integer(200); 来创建变量
// 基本类型 转为 包装类型
int b = a; // a 是包装类型,但是可以直接赋给int类型 ,而不用进行类型转换
// a 是 Integer类型,b 是 int 类型,如果没有自动拆箱,就需要自己进行类型转换
自动拆箱和装箱,是在本来需要自己进行类型转换的时候,交给Java进行,而不用自己强制转换。
基本类型和基本类型比较,使用 == 即可。
基本类型和包装类型比较,使用 == 。
对象和对象比较,使用equal()。
基本类型和基本类型比较,比较的是值是否相等。
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,不是同一个对象,地址不同
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);
字符串和对象引用的示意图如下:

比较两个对象的地址是否相同,如果相同则返回 true,如果不同则返回 false。
// 创建一个 Integer 对象
Integer a = new Integer(10);
// 使用两个变量作为对比
Integer b = new Integer(10); // b != a,不是同一个对象
Integer c = a; // c == a,是同一个对象
对象和对象引用的示意图如下:

由于 HashMap要说的内容和上面的比较情况稍有不同,这里把 HashMap中想要说的东西单独拎出来。
HashMap中常常需要判断某个值是否在HashMap中,会用到containsKey()。该方法判断某个值在HashMap中是使用equals()来判断的, 也就是说,如果一个类覆盖了这个方法,那么是并根据值来判断的;如果一个类没有覆盖这个方法,那么是根据地址来判断的。
containsKey()有说到,key==null ? k==null : key.equals(k)(其中,key是HasMap中的键,k是要差值的值)当哈希表中键不为空并且等于其中一个键值时,返回true。简短的看,containsKey()就是使用 equal()方法比较key和k是否相等。
举个String例子:
Map 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 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
值value 和 基本类型比较
先说一种比较常见的情况,我们在创建一个 HashMap对象为 map1后,有时会需要比较 HashMap中是否包含某个值,一般使用 containsValue() 判断是否数据包含在 HashMap中(containsValue()的使用和 containsKey()类似)。比如
Map 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 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 map1 = new HashMap<>();
map1.put('a', 1);
map1.put('b', 200);
// 创建另一个 HashMap ,存储相同的数据
Map 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中存储对象的地址是否相等。