一、Object类

1.核心了解

        Object类是最顶层,万物之父,提供了通用基础方法,供所有子类重写或直接使用

2.常用核心方法

(1)equals

默认实现:本质是利用==来比较对象内存地址

作用:判断两个对象逻辑是否相等

重写实现:有一些子类会重写该方法,例如String:会先用==比较地址是否相同,如果地址不同,再比较值是否相同

(2)hashCode

默认实现:根据对象内存地址生成整数哈希值,本质是将生成的整数存到哈希结构里面

作用:配合哈希集合(HashSet / HashMap),提升查询,去重效率

官方规定:若要重写equals,必须重写hashCode

  • equals() == true → 两个对象 hashCode 必须相同
  • hashCode 相同 → equals 可不同(哈希冲突)

(3)toString

默认实现:类名@十六进制哈希码 例:User@15db9742

作用:打印对象信息

重写实现:一般都会重写,返回对象属性内容

二、为什么重写equals方法必须要重写hashCode方法

1.官方规定

Java 官方在 Object 类的文档里明确规定了 3 条规则:

  1. 如果两个对象调用 equals() 相等
  2. 那么它们调用 hashCode() 必须返回相同的值
  3. 反过来不要求:hashCode 相同,equals 不一定相等(因为会有哈希冲突,同一个映射地址到哈希结构里面的可能是多个)

不遵守这个规则,会出现bug

2.为什么会出现bug(核心原理)

HashSet / HashMap 这类哈希集合判断两个对象是否重复的逻辑是:

  1. 先比较 hashCode()
  2. 如果 hashCode 不同 → 直接判定不是同一个对象
  3. 如果 hashCode 相同 → 再用 equals() 比较

如果不理解,这里举个实际场景:你想要通过id来判断是否是一个用户

前提:只重写了equals,不重写hashCode

假设你有一个 User 类,只重写了 equals(按 id 判断相等),没重写 hashCode

User u1 = new User(1);
User u2 = new User(1);

这时调用equals方法:u1.equals(u2)  会返回 true(因为你重写了)

那调用hashCode方法:u1.hashCode()u2.hashCode() 会发现两者不同(默认是内存地址)

若将两个对象存进去:set.add(u1); set.add(u2);   发现两个对象都存进去了!

但是我们要的效果是id相同就是同一个用户,那么存到hashSet,不应该只存一个吗?

原因就是:HashSet 会先看 hashCode ,发现不一样,直接认为是两个不同对象,根本不会调用你的 equals。

这就违背了你的业务逻辑:你认为 id 相同就是同一个人,但集合不认。

因此,我们在重写了equals之后,一定要重写hashCode

3.默认的hashCode / equals是什么?

Java 所有对象默认继承 Object 的方法:

  • equals():默认比较 内存地址
  • hashCode():默认返回 内存地址计算的数字

所以默认情况下:

  • equals 相等 → 地址相同 → hashCode 一定相等 

但一旦你重写 equals 改变了相等规则(比如按属性相等),默认的 hashCode 就不再遵守约定了

更形象点的说法:本来我判断两个东西是否长得一样,是通过看整体一不一样equals--->那么就只有一个东西可以存进hashSet,但是当你改了equals,改成通过看这两个东西是不是都有个尾巴来判断是不是一样的--->那么本来逻辑上是只能存一个,但是hashSet通过hashCode发现这两东西其实并不一一样,就会把两个东西都存到hashSet里面

三、包装类

1.为什么会有包装类的存在?

  • java说好了万物皆对象,但是基本数据类型不是
  • 基本数据类型不能进行运算

2.定义

        java为八种基本数据类型提供对应的引用类型,就是包装类(基本类型:简单、没有方法;包装类:对象,有方法,可存集合)

基本类型 包装类(java.lang)
byte Byte
short Short
int Integer(高频)
long Long
float Float
double Double
char Character
boolean Boolean

3.自动装箱 & 自动拆箱

(1)装箱

        基本类型--->包装类

Integer a = 10; // 自动装箱,等价于 Integer.valueOf(10)

(2)拆箱

        包装类--->基本类型

Integer a=20;
int b = a; // 自动拆箱,等价于 a.intValue()

4.128陷阱(重点)

        讲128陷阱之前先来说一下:Integer常量池

        Integer常量池:Java为了减少对象创建,节省内存,对Integer类型的部分数值做了缓存,缓存范围是-128~127之间的Integer对象

        若创建Integer对象在这个范围内,直接会返回缓存里面已有的对象(若数值本身一致,多个对象会指向同一个地址),若超出这个范围就会创建新对象

        基于常量池的知识来说一下128陷阱:

        i==j 为 true:因为 127 在缓存范围内,i 和 j 指向同一个缓存对象,内存地址相同。

  k==l 为 false:因为 128 超出缓存范围,k 和 l 是两个不同的 new Integer(128) 对象,内存地址不同。

        但是如果用equals来判断的话,无论是否在缓存范围里,只要数值相同,equals就会返回true

5.拆装箱练习

public class Main {
    public static void main(String[] args) {
        int a = 10;
        int b = 10;
        Integer a1 = 10;
        Integer b1 = 10;
        Integer a2 = new Integer(10);
        Integer b2 = new Integer(10);
        
        System.out.println(a==b); 
        System.out.println(a1==b1); 
        System.out.println(a2==b2); 
        System.out.println(a1 == a); 
        System.out.println(a1.equals(a)); 
        System.out.println(a1 == a2); 
        System.out.println(a == a2); 
    }
}

输出结果+分析:

(1)==在基本数据类型里面比较的是数值,两者数值一样因此是true

(2)==在引用数据类型里面比较的是地址,但是在常量池里面,两者指向同一个地址,因此返回true

(3)==在引用数据类型里面比较的是地址,a2和b2都是通过new来创建的,每次new都会创建一个全新的对象,和缓存无关,两个对象的地址不同,所以结果返回false

(4)==两边类型不一致时,Integer会进行自动拆箱,变成int类型,因此变成了比较基本数据类型,因此返回true

(5)equals方法是Integer重写过的,用来比较数值是否相同,Integer会进行自动拆箱,变成int类型,因此比较两者数值。所以返回true

(6)==在引用数据类型里面比较的是地址值,a1是缓存中的Integer对象(堆里共享区域),a2是new出来的新对象(堆里面普通区域),两者内存地址不一样,因此返回false

(7)==两边类型不一致时,Integer会进行自动拆箱,变成int类型,比较后数值相等,因此返回true

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐