Integer\int对比,equals()\hashcode面试

先给你一张总表:

问题 int Integer
类型 基本数据类型 引用类型/包装类
存什么 直接存数值 存对象引用,对象里包着一个 int
能否为 null 不能 可以
默认值 0 null
堆栈区别 局部 int 通常在栈帧局部变量表里 引用变量在栈里,Integer 对象通常在堆里
比较方式 == 比数值 == 比地址/引用,equals() 比数值
泛型能不能用 不能,如 List<int> 可以,如 List<Integer>

1. intInteger 的本质区别

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 在缓存范围内,ab 拿到的是同一个缓存对象。

所以:

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() 是什么原理?

Integerequals() 大概长这样:

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,而 Objectequals() 源码本质上是:

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;

这时 u1u2 确实指向同一个对象。


但如果你关心的是:

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() 可能和对象地址有关,但不能直接说它就是地址。

原因是:

  1. Java 规范没有规定 hashCode() 必须是内存地址;
  2. JVM 可以移动对象,比如 GC 整理内存时可能移动对象;
  3. 对象地址是 JVM 内部细节,Java 程序不能直接拿到真实地址;
  4. 不同 JVM 对默认 hashCode() 的实现可以不同。

所以你应该这样记:

text 复制代码
hashCode 不是地址。
默认 hashCode 可能和对象身份有关,但不是给你当内存地址用的。

更准确地说:

text 复制代码
默认 hashCode 是对象身份哈希码 identity hash code。

它用于区分对象身份,但不等价于内存地址。


14. IntegerhashCode() 是什么?

IntegerhashCode() 很简单,基本就是它的值。

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:Integerint== 比较会自动拆箱

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();

但是 anull,调用方法就空指针了。

类似:

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. 你可以这样建立一套记忆模型

第一层:intInteger

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 的基本数据类型,直接保存整数值;Integerint 的包装类,是引用类型,可以为 null,也可以用于泛型和集合。Integerint 之间可以自动装箱和拆箱,装箱时通常调用 Integer.valueOf()Integer.valueOf() 默认缓存 -128 到 127 的对象,所以 Integer a = 127; Integer b = 127; a == btrue,但 128 通常为 false

对基本类型来说,== 比较值;对引用类型来说,== 比较引用是否指向同一个对象。Integer 重写了 equals(),所以 equals() 比较的是数值。自定义类如果不重写 equals(),默认继承 Object.equals(),本质是 this == obj,比较对象身份,而不是内容。

equals() 不是比较 hashCode()hashCode() 主要服务于 HashMapHashSet 等哈希容器。一般要求如果两个对象 equals()true,它们的 hashCode() 必须相同。因此重写 equals() 时通常也必须重写 hashCode()。默认 hashCode() 不能简单理解成地址,它是对象的身份哈希码,可能和对象地址有关,但不是 Java 层面的真实内存地址。


一句话总结:

int 比值,Integer 是对象;== 看类型,基本类型比值,引用类型比是不是同一个对象;equals() 默认也比对象身份,但很多类会重写成比内容;hashCode() 是哈希容器用来分桶的,不是地址,也不是 equals 本身。

相关推荐
WiChP1 小时前
【V0.1B10】从零开始的2D游戏引擎开发之路
java·数据库·游戏引擎
kyriewen2 小时前
手写虚拟DOM后,我反问面试官:key为什么不能用index?
前端·react.js·面试
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【60】检查点机制原理与全流程剖析
java·人工智能·spring
ForgeAI码匠2 小时前
Maven 多模块项目如何避免越写越乱?Forge Admin 的模块边界实践
java·人工智能·开源·maven
z落落2 小时前
C# 数组 最终完整版全套笔记(一维+多维+交错+引用类型+对象数组)
java·笔记·c#
Access开发易登软件2 小时前
Access 和 SQLite,根本不在一个赛道上
java·jvm·数据库·sqlite·excel·vba·access开发
小马爱打代码2 小时前
Spring源码 第十篇:Spring 5 源码深度拆解 - Spring 类型转换与校验体系
java·spring
长谷深风1112 小时前
Java 面试高频:反射机制与异常体系全面解析
java·开发语言·面试·exception·java 反射·java 异常·class 对象
过期动态2 小时前
【LeetCode 热题 100】盛最多水的容器
java·数据结构·spring boot·算法·leetcode·spring cloud·职场和发展