Java进阶学习——Alibaba Java Coding Guidelines的理解之为什么要重写hashCode()和equals()

本文解析了在Java中为何及如何重写hashCode()和equals()方法,特别是在自定义类作为Set或Map键值时,确保唯一性的关键作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0.前言

在我使用输入这样private Set< Dog> dogs;Alibaba Java Coding Guidelines提示我参数类型Dog没有重写hashCode()和equals(),初学这个的我很懵逼,为啥这样提示我?在我一番搜集资料后才明白这样做的目的,同时也由衷的感觉这样做很有必要。

1.重写hashCode()和equals()方法

1.1提示原因

这种情况常见于一些以xx类(自定义类)为参数且其值只能唯一,比如Set< Student>中的Student。Set为集合,集合中不能有重复的元素,每一个元素是唯一的,以Student为例,这个集合里的每个Student类型的变量必须是唯一的。听到这可能有些懵,但不要紧,先接受,看完后面你就明白了。
提示让我们重写hashCode和equals,但怎么重写?在哪重写?首先明确从哪定义从哪重写,假设Student类在model中定义为含有学号和学校名两个属性,且通过学号和学校名两个变量唯一确定一个Student(一个学号可能在两个学校中有重复,一个学校中有很多学号不同的人,但一个学校中的学号不会有重复,因此需要通过学号和学校名唯一识别一个学生)。

1.2重写equals()方法

此时我定义一个Student的集合Set< Student>students,都知道集合里的元素不能重复,但怎么做到不能重复呢?Java中会调用equals方法(这是一个Objec类的方法,因此所有对象都有这样一个方法)来对新加如的元素如studen2,和之前集合中的元素如student1,进行比较确定是否为同一元素。如果不重写equal方法,只会比较student1和student2的地址是否相等。

//不重写equals方法的时,调用student1.equals(student2)方法的时equals源码
public boolean equals(Object obj) {
//this表示stude1,obj表示student2,==只比较地址
        return (this == obj);
    }

student1和student2为分别实例化的对象,所以地址肯定不等,即使student1和student2的学号和学校名相同,在这种情况下也是会认为不等的。因此我们需要重写equals方法使其具备能唯一识别学生。
代码如下:

@Override
    public boolean equals(Object o) {
        /*
         * this表示调用方法的对象,o表示参数
         * 如student1.equals(student2),student1为this,o为student2
         * 先比较student1和student2的地址是否一致,如果一致就认为相等
         */
        if (this == o) {
            return true;
        }
        //判断o是不是Student类型的变量,如果不是直接返回false
        if (!(o instanceof Student)) {
            return false;
        }
        //把o强制转换成Student类型的变量
        Student student = (Student) o;
        /*
          判断student1和student2的学号和学校名是否分别相等,有一个相等则返回false
          id是int型,schoolName是String类型
          这些基本类型变量都有一套完整的判断相等的代码,我们可以直接调用
         */
        return Objects.equals(id, student.id) &&
                Objects.equals(schoolName, student.schoolName);
    }

每行代码的意思我都在注释里解释清楚了,看注释就可以了。至此我们已经完成了对equals()的重写。通过equals方法我们已经能够确定唯一的值了,那么我们为什么还要重写hashCode()呢?

1.3重写hashCode()方法

这里涉及一部分数据结构的知识,而且还涉及hashMap的底层实现,我只是略知一二,不是很懂,很深入的东西没有探讨,但足够理解了,如果没有数据结构基础的人,建议直接看代码,或者先补习一下数据结构的hash表的知识。这是因为在一些比如类型的变量如map中,我们常用hashMap()作为其实现类(Map<K,V> map=new HashMap()),这里面常用hashCode来比较两个K是否相等。默认的hashCode的返回值是一个将对象地址进行hash运算之后的结果值,所谓hash运算就是将一个数值输入之后,经过一个hash函数的运算最后的出一个结果,不同地址的对象的hashCode的结果不同,用来区分不同对象。因此我们还需要重写hashCode的方法,对不同地址的对象进行进一步区分。代码如下:

@Override
    public int hashCode() {
        int result = id.hashCode();
        result = 31 * result + schoolName.hashCode();
        //这是一个标准的模板,如果还需要name属性才能唯一确定,后面这样写,以此类推
        // result = 31 * result + naame.hashCode();
        return result;
    }

这是一个标准的模板,之所以选择31是因为,源码中选择的值也为31,源码选择的原因是因为选择31是因为,首先这个值不能太小,太小容易产生重复值(学名叫冲突或者碰撞),比如52+4=62+2,太大值容易超出计算机能表示的范围,在多次校验中31还算合适,另外31是素数,用素数不易产生重复值(学名叫冲突或者碰撞),最终的出来的结果只能被素数本身和被乘数还有1来整除,比如用2的话就不一样了,还有一点是因为位移运算比较快(位移运算简单理解就是移动小数点,比如在十进制中,×10就可以表示为把小数点左移一位后的值,31=2*5-1,也就是说×31在计算机内部表示为左移5位之后-1,速度比乘快很多。最后一点也是因为上面的原因,很多设备(包括JVM,也就是Java虚拟机)都对31有特别的优化,速度比较快。

1.4使用idea自动生成重写的hashCode()和equals()

仔细观察可以发现重写equals和hashCode方法都是有固定的套路模板,这样的操作只能的idea可能可以帮我们自动生成。确实可以。在需要重写这两个方法的类中右键依次选择Generate,equals and hashCode,之后会跳出选择框,第一个是让你选择生成的模板,一般使用默认即可,第二个是选择在equals()方法中需要包含的属性(本例中的id和schoolName),第三是是选择在hashCode()方法中需要包含的属性(本例中的id和schoolName),有的会有第四个选择框,这是选择这些属性中需要设置为非空的属性值,之后点击finish即可。自动生成的代码和我自己写的会有不同,甚至不同模板的也不同,但原理都是一样的。

2.总结

这个提示感觉非常之有用,如果没有这个提示,我大概率是想不到重写equals()和hashCode()这两个方法的,到时候报错我都很难排查,而且这里面还涉及了一些数据结构的知识,让我对hash有了更深入的理解,越发感觉数据结构的重要性。同时也坚定了认真对待Alibaba Java Coding Guidelines,对于它提示的内容认真学习和改进。

### STM32C8T6 单片机喇叭驱动示例代码 对于STM32C8T6单片机来说,要实现对喇叭的驱动通常会涉及到PWM信号生成以及DAC输出等功能来产生音频信号。下面是一个简单的例子,展示如何利用STM32CubeMX配置并编写一段用于播放简单音调的程序。 #### PWM方式生成方波作为基础频率源 ```c // 初始化TIM3定时器为PWM模式, 输出到PA6引脚上 __HAL_RCC_TIM3_CLK_ENABLE(); htim3.Instance = TIM3; htim3.Init.Prescaler = 79; // 设置预分频系数使得计数频率=APB1时钟/80 (假设系统主频72MHz) htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 999; // 自动重装载值决定周期长度 if (HAL_TIM_PWM_Init(&htim3) != HAL_OK){ Error_Handler(); } // 配置通道1(PA6)为PWM输出 TIM_OC_InitTypeDef sConfigOC; sConfigOC.OCMode = TIM_OCMODE_PWM1; sConfigOC.Pulse = 500; // 初始占空比设置 sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH; sConfigOC.OCFastMode = TIM_OCFAST_DISABLE; if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK){ Error_Handler(); } ``` #### 使用DAC转换模拟电压给扬声器供电 为了获得更好的音质效果,还可以考虑使用内部DAC配合外部低通滤波电路来代替纯数字式的PWM方法: ```c // DAC初始化函数 static void MX_DAC_Init(void) { DAC_ChannelConfTypeDef sConfig; hdac.Instance = DAC; if (HAL_DAC_Init(&hdac) != HAL_OK) Error_Handler(); sConfig.DAC_Trigger = DAC_TRIGGER_NONE; sConfig.DAC_OutputBuffer = DAC_OUTPUTBUFFER_ENABLE; if (HAL_DAC_ConfigChannel(&hdac, &sConfig, DAC_CHANNEL_1) != HAL_OK) Error_Handler(); } void Play_Tone(uint16_t frequencyHz,uint16_t durationMs) { uint32_t i,j; float t,freqScale,sampleValue; freqScale=(float)(SystemCoreClock)/(frequencyHz*2); // 计算每次改变幅度所需循环次数 for(i=0;i<durationMs*freqScale;i++) { sampleValue=sin(2*M_PI*(i%((int)freqScale))/freqScale)*127+128; __HAL_DAC_SET_VALUE(&hdac,DAC_CHANNEL_1,DAC_ALIGN_12B_R,(uint32_t)sampleValue); for(j=0;j<(SystemCoreClock/(1000/frequencyHz));j++); } } ``` 上述代码片段展示了两种不同的方案来驱动喇叭发声,分别是通过PWM生成特定频率的声音[^1] 和借助DAC输出正弦波形以提高声音质量[^2] 。这两种技术都可以应用于基于STM32系列MCU的产品开发当中,在实际应用中可根据具体需求选择合适的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值