先给你一张总表:
| 问题 | int |
Integer |
|---|---|---|
| 类型 | 基本数据类型 | 引用类型/包装类 |
| 存什么 | 直接存数值 | 存对象引用,对象里包着一个 int |
能否为 null |
不能 | 可以 |
| 默认值 | 0 |
null |
| 堆栈区别 | 局部 int 通常在栈帧局部变量表里 |
引用变量在栈里,Integer 对象通常在堆里 |
| 比较方式 | == 比数值 |
== 比地址/引用,equals() 比数值 |
| 泛型能不能用 | 不能,如 List<int> 错 |
可以,如 List<Integer> |
1. int 和 Integer 的本质区别
int 是基本类型:
java
int a = 10;
它直接表示一个整数值。
Integer 是对象类型:
java
Integer b = 10;
它本质上类似于:
java
Integer b = Integer.valueOf(10);
也就是说,Integer 是把 int 包装成了一个对象。
所以你可以这样理解:
text
int:直接是数字
Integer:装数字的盒子
2. 堆栈上有什么区别?
看这段代码:
java
public void test() {
int a = 10;
Integer b = new Integer(10);
}
粗略理解:
text
栈:
a = 10
b = 0x001 // 引用地址
堆:
0x001 -> Integer 对象,里面有 value = 10
所以:
java
int a = 10;
a 这个局部变量直接存数值。
java
Integer b = new Integer(10);
b 是引用,真正的 Integer 对象在堆里。
但是注意,现代 JVM 有逃逸分析、标量替换等优化,所以实际运行时不一定每个对象都真的分配到堆上。初学阶段你先记"对象通常在堆,局部变量/引用通常在栈"就够用。
3. 自动装箱和自动拆箱
Java 为了让基本类型和包装类用起来方便,引入了自动装箱、自动拆箱。
自动装箱
java
Integer a = 10;
编译器大概会变成:
java
Integer a = Integer.valueOf(10);
也就是把 int 变成 Integer。
自动拆箱
java
Integer a = 10;
int b = a;
编译器大概会变成:
java
int b = a.intValue();
也就是把 Integer 里面的 int 拿出来。
4. Integer 缓存是什么?
你记得没错,Integer 有缓存。
看这个经典例子:
java
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
但是:
java
Integer a = 128;
Integer b = 128;
System.out.println(a == b); // false
为什么?
因为 Integer.valueOf() 默认会缓存:
text
-128 到 127
也就是说:
java
Integer a = 127;
Integer b = 127;
底层大概是:
java
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
由于 127 在缓存范围内,a 和 b 拿到的是同一个缓存对象。
所以:
text
a -> 同一个 Integer(127)
b -> 同一个 Integer(127)
因此:
java
a == b
结果是 true。
但是:
java
Integer a = 128;
Integer b = 128;
128 超过默认缓存范围,每次可能都会创建新对象:
text
a -> Integer(128)
b -> Integer(128)
它们值相同,但不是同一个对象,所以:
java
a == b
结果是 false。
5. == 和 equals() 的区别
这个非常重要。
对基本类型来说
java
int a = 10;
int b = 10;
System.out.println(a == b); // true
== 比较的是数值。
对引用类型来说
java
Integer a = new Integer(10);
Integer b = new Integer(10);
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
对于对象:
java
a == b
比较的是两个引用是不是指向同一个对象。
也就是:
text
a 和 b 是不是同一个盒子?
而:
java
a.equals(b)
对于 Integer 来说,比较的是里面的数值是否相等。
也就是:
text
两个盒子里面装的数字是不是一样?
6. Integer.equals() 是什么原理?
Integer 的 equals() 大概长这样:
java
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer) obj).intValue();
}
return false;
}
也就是说,Integer.equals() 比较的是内部的 int value。
所以:
java
Integer a = new Integer(1000);
Integer b = new Integer(1000);
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
因为虽然不是同一个对象,但是里面的值一样。
7. equals() 默认是比较什么?
如果你自己写一个类:
java
class User {
private String name;
private int age;
}
然后:
java
User u1 = new User("张三", 18);
User u2 = new User("张三", 18);
System.out.println(u1 == u2);
System.out.println(u1.equals(u2));
如果你没有重写 equals(),那么结果通常是:
text
false
false
为什么?
因为所有类默认继承 Object,而 Object 的 equals() 源码本质上是:
java
public boolean equals(Object obj) {
return this == obj;
}
也就是说,默认的 equals() 其实还是比较两个引用是不是指向同一个对象。
所以不重写时:
java
u1.equals(u2)
等价于:
java
u1 == u2
8. 比较两个对象需要重写 equals() 吗?
看你的需求。
如果你关心的是:
text
是不是同一个对象
那不需要重写。
比如:
java
User u1 = new User("张三", 18);
User u2 = u1;
这时 u1 和 u2 确实指向同一个对象。
但如果你关心的是:
text
两个对象的内容是否相同
那就应该重写 equals()。
例如你希望:
java
User u1 = new User("张三", 18);
User u2 = new User("张三", 18);
u1.equals(u2) == true
那你就要重写 equals()。
9. 重写 equals() 的例子
java
import java.util.Objects;
public class User {
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
// 中文注释:重写 equals,用于判断两个 User 对象的内容是否相同
@Override
public boolean equals(Object o) {
// 中文注释:如果两个引用指向同一个对象,直接返回 true
if (this == o) {
return true;
}
// 中文注释:如果对方是 null,或者类型不同,直接返回 false
if (o == null || getClass() != o.getClass()) {
return false;
}
// 中文注释:把 Object 强制转换为 User,方便比较字段
User user = (User) o;
// 中文注释:比较 age 是否相等,name 是否相等
return age == user.age && Objects.equals(name, user.name);
}
// 中文注释:重写 equals 时,通常必须同时重写 hashCode
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
这样:
java
User u1 = new User("张三", 18);
User u2 = new User("张三", 18);
System.out.println(u1 == u2); // false
System.out.println(u1.equals(u2)); // true
10. equals() 是不是比较 hashCode()?
不是。
这是你这个问题里最容易混淆的点。
equals()不是默认比较hashCode()。
默认 Object.equals() 是:
java
return this == obj;
而不是:
java
return this.hashCode() == obj.hashCode();
hashCode() 主要是给哈希容器用的,比如:
java
HashMap
HashSet
Hashtable
ConcurrentHashMap
11. hashCode() 是干嘛的?
你可以把 hashCode() 理解成对象的"分组编号"。
比如 HashSet 判断一个对象是否已经存在,大概流程是:
text
第一步:先看 hashCode,决定放在哪个桶
第二步:如果桶里有对象,再用 equals 判断是否真的相等
举个例子:
java
Set<User> set = new HashSet<>();
set.add(new User("张三", 18));
set.add(new User("张三", 18));
System.out.println(set.size());
如果你正确重写了 equals() 和 hashCode(),结果是:
text
1
因为 HashSet 认为这两个 User 内容相同。
12. 为什么重写 equals() 必须重写 hashCode()?
因为 Java 规定:
如果两个对象
equals()返回true,那么它们的hashCode()必须相同。
注意反过来不一定成立:
两个对象
hashCode()相同,equals()不一定为true。
这叫哈希冲突。
举个错误例子,只重写 equals(),不重写 hashCode():
java
class User {
private String name;
private int age;
// 假设这里只重写了 equals,没有重写 hashCode
}
然后:
java
Set<User> set = new HashSet<>();
set.add(new User("张三", 18));
set.add(new User("张三", 18));
System.out.println(set.size());
你可能以为结果是:
text
1
但实际可能是:
text
2
因为 HashSet 先看 hashCode(),如果两个对象的 hashCode() 不一样,它们可能直接被放到不同桶里,后面连 equals() 都不一定比较。
所以规则是:
text
只要重写 equals,就一定要重写 hashCode。
13. hashCode() 是不是地址?
不严谨地说,默认 Object.hashCode() 可能和对象地址有关,但不能直接说它就是地址。
原因是:
- Java 规范没有规定
hashCode()必须是内存地址; - JVM 可以移动对象,比如 GC 整理内存时可能移动对象;
- 对象地址是 JVM 内部细节,Java 程序不能直接拿到真实地址;
- 不同 JVM 对默认
hashCode()的实现可以不同。
所以你应该这样记:
text
hashCode 不是地址。
默认 hashCode 可能和对象身份有关,但不是给你当内存地址用的。
更准确地说:
text
默认 hashCode 是对象身份哈希码 identity hash code。
它用于区分对象身份,但不等价于内存地址。
14. Integer 的 hashCode() 是什么?
Integer 的 hashCode() 很简单,基本就是它的值。
java
Integer a = 10;
System.out.println(a.hashCode()); // 10
因为 Integer.hashCode() 大概是:
java
public int hashCode() {
return Integer.hashCode(value);
}
public static int hashCode(int value) {
return value;
}
所以:
java
Integer.valueOf(100).hashCode()
结果就是:
text
100
15. Integer 比较的几个坑
坑 1:缓存导致 == 结果不一致
java
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
java
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false
原因是 -128 ~ 127 有缓存。
坑 2:new Integer() 一定创建新对象
java
Integer a = new Integer(127);
Integer b = new Integer(127);
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true
即使是 127,只要你显式 new,通常就是两个不同对象。
不过现在不推荐使用:
java
new Integer(127)
推荐:
java
Integer.valueOf(127)
或者直接:
java
Integer a = 127;
坑 3:Integer 和 int 用 == 比较会自动拆箱
java
Integer a = 1000;
int b = 1000;
System.out.println(a == b); // true
为什么是 true?
因为比较时 Integer a 会自动拆箱成 int:
java
System.out.println(a.intValue() == b);
所以比的是数值。
坑 4:自动拆箱可能空指针
java
Integer a = null;
int b = a;
这会报错:
text
NullPointerException
因为它底层相当于:
java
int b = a.intValue();
但是 a 是 null,调用方法就空指针了。
类似:
java
Integer a = null;
if (a == 1) {
System.out.println("等于 1");
}
也会空指针,因为 a == 1 会触发自动拆箱。
16. 一组代码帮你彻底串起来
java
public class Test {
public static void main(String[] args) {
int a = 100;
int b = 100;
System.out.println(a == b); // true,基本类型比较数值
Integer c = 100;
Integer d = 100;
System.out.println(c == d); // true,缓存范围内,引用同一个对象
System.out.println(c.equals(d)); // true,Integer 重写了 equals,比较数值
Integer e = 200;
Integer f = 200;
System.out.println(e == f); // false,超出默认缓存范围,通常是不同对象
System.out.println(e.equals(f)); // true,比较数值
Integer g = new Integer(100);
Integer h = new Integer(100);
System.out.println(g == h); // false,new 出来的是两个对象
System.out.println(g.equals(h)); // true,比较数值
Integer i = 1000;
int j = 1000;
System.out.println(i == j); // true,i 自动拆箱后比较数值
}
}
17. 你可以这样建立一套记忆模型
第一层:int 和 Integer
text
int 是数字本身。
Integer 是装数字的对象。
第二层:==
text
基本类型用 ==:比较值。
引用类型用 ==:比较是不是同一个对象。
第三层:equals()
text
Object 默认 equals:还是比较地址/引用。
很多类重写 equals:改成比较内容。
比如:
text
String.equals():比较字符串内容
Integer.equals():比较数字值
User.equals():看你是否自己重写
第四层:hashCode()
text
hashCode 是哈希分组编号,不是 equals 本身。
HashMap/HashSet 先看 hashCode,再看 equals。
18. 最重要的面试版回答
你可以这样回答:
int是 Java 的基本数据类型,直接保存整数值;Integer是int的包装类,是引用类型,可以为null,也可以用于泛型和集合。Integer和int之间可以自动装箱和拆箱,装箱时通常调用Integer.valueOf()。Integer.valueOf()默认缓存-128 到 127的对象,所以Integer a = 127; Integer b = 127; a == b为true,但128通常为false。对基本类型来说,
==比较值;对引用类型来说,==比较引用是否指向同一个对象。Integer重写了equals(),所以equals()比较的是数值。自定义类如果不重写equals(),默认继承Object.equals(),本质是this == obj,比较对象身份,而不是内容。
equals()不是比较hashCode()。hashCode()主要服务于HashMap、HashSet等哈希容器。一般要求如果两个对象equals()为true,它们的hashCode()必须相同。因此重写equals()时通常也必须重写hashCode()。默认hashCode()不能简单理解成地址,它是对象的身份哈希码,可能和对象地址有关,但不是 Java 层面的真实内存地址。
一句话总结:
int比值,Integer是对象;==看类型,基本类型比值,引用类型比是不是同一个对象;equals()默认也比对象身份,但很多类会重写成比内容;hashCode()是哈希容器用来分桶的,不是地址,也不是 equals 本身。