Effective Java 2nd 阅读 第三章 所有对象都适用的方法

本文详细解析了Java中对象比较方法equals、hashCode的使用规则与注意事项,包括如何正确实现equals方法以遵守通用约定,以及在覆盖equals方法时为何需要同时覆盖hashCode方法以确保HashMap等集合类能正常工作。同时,强调了始终覆盖toString方法的重要性,以方便对象内容的展示,以及谨慎地覆盖clone方法以避免深拷贝和浅拷贝的问题。

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

转载请注明来自:http://blog.youkuaiyun.com/huntersjm/article/details/43340257

这一章,主要是讲述了和Object对象相关的一些内容,包括,equals,hashCode,toString,clone,finalize,Comparable,compareTo方法

第八条:覆盖equals请遵守通用约定

一般来说,是不需要覆盖equals方法的,但是某些情况还是需要的,比如1:当一个类和另一个类,判断是否相同的标准是类中的某个成员变量是否相等。这种成员变量叫做,关键域。或者2:当一个类不允许被比较,则定义为私有的方法或者 3:两个类引用的地址完全一样才相等,即== 或者4:从父类继承过来的方法,不适用当前的类

简单的来说主要是:对象相等的逻辑与父对象的逻辑不同,这时便需要覆盖。

所谓遵守通用约定,那么有哪些呢?

学过离散数学,可能知道如下几点:

1:自反性  2:对称性 3:传递性 4:一致性 5:null不等性(暂且这么叫吧~)

所谓自反性,很简单,就是自己和自己肯定相等

所谓对称性,也很简单,A和B相等=》B和A相等

所谓传递性,就是A与B相等,B和C相等=》A也和C相等

所谓一致性,就是说A和B相等,只要A和B不变=》A和B一直相等

所谓null不等性,就是说对象肯定不等于null(注意,对象意味着本身不是null)

最常见的问题出现在,继承中,比如:一个正方形类A,另一个带颜色的正方形类B,继承了A,那么A a,B b两个对象,怎么比较相等呢?这本书就存在矛盾,是否需要考虑颜色,很多时候,很容易出现上述的问题。A类比较,只比较了边长,而B类还需要比如颜色,那么a.equals(b)  和 b.equals(a) 就不一定一样了。类似的问题还很多。所以Ej中推荐使用复合代替继承,就是说,将一个A a对象包含在B类中,就不存在这个问题了,因为A和B类肯定就不相等了。也不存在上述的问题。

总的说起来,有以下的方法来避免上述的问题:

1:使用==操作符来检查,是否引用相等,如果引用相等,那么肯定相等啊,是吧

2:使用instanceof来检查类型是否相等,特别地:null不属于任何类型

3:把参数转化为正确的类型,在进行比较(经过instanceof比较,这肯定没问题)

4:检查关键域,这里注意:按以下顺序比较比较合适 a.比较容易不一致的域,这样会提高性能,b.比较开销低的域,这样效率比较高 c.冗余域不需要比较,比如长方形类中的周长。

5:equals方法应该尽量遵守通用的约定

注意:容易出错的点

public boolean equals(Oval oval){
    //...        
}
如果使用这样的方式,是不行的,因为需要覆盖的方法是:equals(Object o) 而这里尝试覆盖的方法是Oval,实际上起的效果只是overload重载,提示,可以使用@Override来检查

第九条:覆盖equals方法时尽量覆盖hashcode方法

如果不覆盖hashcode方法可能会造成诸如HashMap,HashSet,Hashtable等类无法正常工作

Java中约定,1:只要equals用到的关键域没有被修改,那么hashCode值应该不变 2:如果equals返回true,那么两个对象的hashCode应该相同

举个例子证明按要求覆盖hashcode的重要性

如果我需要定义一个类,长方形类,而我们只关注其周长,我们认为周长相等的即相等

package com.sjm.hunter.effectivejava;

import java.util.HashMap;
import java.util.Map;

/**
 * Created by jiamin.sjm on 15/1/31.
 */
public class Perimeter {
    private int l;
    private int w;
    private int perimeter;
    private boolean change;
    public Perimeter(int l,int w){
        this.l = l;
        this.w = w;
        this.change = true;
    }
    //获取周长
    public int getPerimeter(){
        if(this.change){
            this.perimeter = 2*(this.l+this.w);
        }
        return this.perimeter;
    }
    //我们只关注周长
    @Override
    public boolean equals(Object o){
        if(!(o instanceof Perimeter)){
            return false;
        }
        return (this.getPerimeter() == ((Perimeter) o).getPerimeter());
    }
    public static void main(String []args){
        Map<Perimeter,String> pers = new HashMap<Perimeter, String>();
        pers.put(new Perimeter(2,3),"周长为10");
        pers.put(new Perimeter(1,4),"周长为10");
        System.out.println(new Perimeter(2,3).equals(new Perimeter(1,4)));
        System.out.println(pers.size());
        System.out.println(pers.get(new Perimeter(2,3)));
    }
}
返回为:true  2  null  (省略了回车)
我们改造一下,增加:

@Override
    public int hashCode(){
        return this.getPerimeter();
    }
那么返回值变成:true   1  周长为10

而如果将,equals去掉,那么会怎么样呢?返回为:false 2 null

EJ中又推荐的方法来获得hashCode(),这里不赘述,建议看原文

注意一定要将关键域用到,用且只用是最合适的

第十条:始终要覆盖toString()方法

很多时候,特别是调试的时候,经常需要看一个对象的具体内容,通常需要点开慢慢看,想想,如果可以直接将关键信息直接展示出来多好啊,类似字符串那样,而不是类名@hashCode的方式,是吧。

那么就覆盖toString()方法吧,把关键的信息展示出来,都不用那么麻烦的点点点,而且记录到日志也很方便了,既可以展示重要的信息,也不至于把隐私信息显示出来。

第十一条:谨慎地覆盖clone <不赘述>

clone()并不怎么使用,想到深拷贝,浅拷贝的问题,应该很容易理解这里的谨慎地覆盖,太容易出现无法预期的问题。

建议的方式是使用拷贝构造器或者拷贝工厂来获得(即方法的参数为源对象),同时如果加上参数,还可以更加灵活的使用该类

第十三条:考虑实现Comparabl接口

我想说,我用过很多次Comparable接口,当一个类的内部有很明确的顺序关系,同时外部需要调用到排序之类的,便需要实现该接口,同时该接口也需要和equals一样的,符合相应的原则,同时,鼓励当且仅当 : compare结果为0时,equals方法返回true

注意返回值:如果计算的结果超过int,可能符号会变得不一样,也就导致了错误的结果哦~


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值