Effective java笔记(二),所有对象的通用方法

Object类的所有非final方法(equals、hashCode、toString、clone、finalize)都要遵守通用约定(general contract),否则其它依赖于这些约定的类(HashMap,HashSet等)将不能正常工作。

8、覆盖equals时请遵守通用约定

无需覆盖equals的情形:

  • 类的每个实例本质上是唯一的。类代表的是活动实体而不是值的概念。(例如,类Thread
  • 不关心类“逻辑相等”的功能,从Object继承的equals实现已经足够。(例如,类Random
  • 超类的equals实现也适用于子类。(例如,ListAbstractList继承equals实现)
  • 类是private或包级私有(默认)的,可以确定它的equals方法永远不会被调用。
  • “每个值最多有一个实例”的类,逻辑相同与对象相同一样。(例如,枚举类,Singleton类)

需要覆盖equals的情形:

  • 类具有“逻辑相等”的概念,而且超类没有覆盖equals方法。

覆盖equals时必须遵守的约定:

  • 自反性,x非空时,x.equals(x)返回true
  • 对称性,x, y非空时,若x.equals(y)返回true,则y.equals(x)返回true
  • 传递性,x, y, z非空时,若x.equals(y)返回truey.equals(z)返回true,则x.equals(z)返回true
  • 一致性,x, y非空时,多次调用x.equals(y)返回值一致
  • x非空时,x.equals(null)一定返回false

违反约定的例子:

在java类库中,java.sql.Timestamp继承自java.util.Date,并增加了nanoseconds域。但Timestampequals实现违反了对称性(date.equals(timestamp) != timestamp.equals(date)),如果TimestampDate对象被混合在一起使用,将引起不正确的行为。

Date的继承结构

equals实现代码:

    
    //Date中equals实现
    public boolean equals(Object obj) {
        return obj instanceof Date && getTime() == ((Date) obj).getTime();
    }

    //Timestamp中equals实现
    /* 
     * Note: This method is not symmetric with respect to the
     * equals(Object) method in the base class.
     */
    public boolean equals(java.lang.Object ts) {
      if (ts instanceof Timestamp) {
        return this.equals((Timestamp)ts);
      } else {
        return false;
      }
    }

注:在equals实现中,对于obj instanceof Date语句,若obj为null,其将返回false。因此把null传个equals方法,无需进行单独的类型检查(判断obj是否为null)。

若将上面Timestampequals代码改为如下形式:

    //Timestamp改进的equals实现
    public boolean equals(java.lang.Object ts) {
      if (ts instanceof Timestamp) {
        return this.equals((Timestamp)ts);
      } else if (ts instanceof Date){
        return ((Date)ts).equals(this);
      } else {
        return false;
      }
    }

这样确实保证了对称性,但却牺牲了Timestamp类的特征。

Timestamp是Date 类的瘦包装器 (thin wrapper),它允许 JDBC API 将该类标识为 SQL TIMESTAMP 值。它添加保存 SQL TIMESTAMP 毫微秒值和提供支持时间戳值的 JDBC 转义语法的格式化和解析操作的能力。

实现equals方法的诀窍:

  • 使用 == 检查“参数是否为这个对象的引用”,特别是比较操作比较昂贵时。

  • 使用instanceof操作符检查“参数是否为正确的类型”,若不是返回false

  • 检查参数中的域是否与对象中的对应域相匹配

  • 编写完equals方法后检查对称性、传递性、一致性

注意:

  • 覆盖equals时总要覆盖hashCode

  • 不要将equals中的Object替换为其他类型,使用@Override public boolean equals(Object o),参数类型为Object,否则将重载equals方法

9、覆盖equals时总要覆盖hashCode

每个覆盖了equals方法的类中,必须覆盖hashCode方法,否则该类无法用于基于散列表的集合(HashMap,HashSet和HashTable)

对hashCode的约定:

  • 对同一个对象调用多次(用于比较操作的信息未被修改),hashCode返回同一个整数

  • equals比较相等,则hashCode返回的值必须相同

  • equals比较不相等,hashCode返回的值可能相同,也可能不同

相等的对象必须具有相等的hashCode值;hashCode值不同,对象一定不相等,为不相等的对象产生不相等的散列码可以提高散列表的性能。散列函数应该把不相等的实例均匀分配到所有可能的散列值上。

一种计算散列码的方式:

1、保存一个非零的常数值,result = 17

2、为对象中每个域f(equals方法中涉及的域)计算int型的散列码c

  • 域为boolean类型,c = f ? 1 : 0
  • 域为byte,char,short或int类型,c = (int) f
  • 域为long类型,c = (int)(f^(f >>> 32))
  • 域为float类型,c = Float.floatToIntBits(f)
  • 域为double类型,f = Double.doubleToLongBits(f); c = (int)(f^(f >>> 32))
  • 域为一个对象的引用,c = f.hashCode()
  • 域为数组,利用此方法递归计算数组的hashCode值

3、将所有的散列码合并到result,递归调用result = result * 31 + c

例如:


    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + fa;
        result = 31 * result + fb;
        result = 31 * result + fc;
        return result;
    }

使用31的原因:

31是一个奇素数并且31可以使用移位和减法来代替乘法以提高性能,如:31 * i == (i << 5) - i。现代的JVM能够自动完成这种优化

对于不可变类,若每次计算hashCode的开销比较大,可将散列码缓存在对象内部,而不是每次请求时都重新计算散列码。如:

    
    private volatile int hashCode = 0; //volatile

    @Override
    public int hashCode() {
        if (hashCode != 0) {
            return hashCode;
        }
        int result = 17;
        result = 31 * result + fa;
        result = 31 * result + fb;
        result = 31 * result + fc;
        hashCode = result;
        return result;
    }

10、始终要覆盖toString

toString的约定,建议所有的类都实现toString方法。toString实现可以使类用起来更加舒服,有利于调试时信息的诊断。

如果对象太大或信息难以用字符串描述,应该返回一个摘要信息。在文档中标明toString的返回格式。

11、谨慎的覆盖clone

一个类实现了Cloneable接口,表名这个类允许被克隆。Cloneable接口是个空接口,仅仅用来标识这个类可以被复制,具体实现将由JVM调用Object中的原生方法clone()完成。所以一个类要能够复制必须实现Cloneable接口并重写clone()方法。

关于clone方法的实现参见博客java中clone方法的实现

12、考虑实现Comparable接口

实现了Comparable接口的类,表明它的实例具有内在的排序关系,可以使用Arrays.sort(comp)方法对其数组进行排序。一旦类实现了Comparable接口,它就可以与许多泛型算法及依赖于该接口的集合实现进行协作。若编写的类是有非常明显的内在排序关系,那么就应该实现Comparable接口。实现Comparable接口必须重写compareTo方法。

依赖于比较关系(compareTo方法)的类包括有序集合类TreeSet和TreeMap,以及工具类Collection和Arrays,它们内部包含有搜索和排序算法。

compareTo方法的约定:

  • 该对象小于、等于、大于指定对象时,分别返回负整数、零、正整数。若两对象不同,则抛出ClassCastException异常
  • x.compareTo(y) == 0x.equals(y)建议保持一致。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容