HIT软件构造 blog3

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

现在已经做完lab2了,这篇博客里总结一下在lab2中用到的一些知识,顺带复习一下


提示:以下是本篇文章正文内容

LAB2学习之重写equals hashcode

(一)前言复习——什么是等价性

等价性的划分

不可变对象的引用等价性==和对象等价性equals()
可变对象的观察等价性和行为等价性

引用等价性与对象等价性的概念—— == 和 equals()
  • 双等号 比较的是索引。更准确的说,它测试的是指向相等(referential equality)。若是两个索引指向同一块存储区域,那它们就是==的。对于咱们以前提到过的快照图来讲,这双等号就意味着它们的箭头指向同一个对象。多用于基本数据类型。
  • equals()操做比较的是对象的内容,换句话说,它测试的是对象值相等(object equality)。在每个ADT中,equals操操作须合理定义。
对象等价性equals()

equals()操作比较对象内容——换句话说,对象相等。
对对象类型,使用equals()

然而在Object 中实现的缺省equals() 是在判断引用等价性,所以应该重写equals()方法。我们在判断对象是否相等时往往使用一些属性,而地址不一定完全相同。

 public boolean equals(Object obj) {
        return (this == obj);
    }

但其实在一些JAVA提供的类库当中这个方法被覆盖掉了,如String,Integer,Date在这些类当中equals有其自身的实现,而再也不是比较类在堆内存中的存放地址了。

这里String这个常用的类里面有些有意思的事情,不过也很容易理解,注意区分字符串和对象:
字符串

(二)、可变类型与不可变类型的判相等

  • 不可变类型:

equals() 应该比较抽象值是否相等。这和 equals() 比较行为相等性是一样的。hashCode() 应该将抽象值映射为整数。
所以不可变类型应该同时覆盖 equals() 和 hashCode().

  • 可变类型:
    equals() 应该比较索引,就像 ==一样。同样的,这也是比较行为相等性。hashCode() 应该将索引映射为整数
    所以可变类型不应该将 equals() 和 hashCode() 覆盖,而是直接继承 Object中的方法。Java没有为大多数聚合类遵守这一规定,这也许会导致上面看到的隐秘bug。
    ps:可变类型在快照图中一直都指向那个对象,只更改内容,需要比较

注意:equals判定为相同, hashCode一定相同。equals判定为不同,hashCode不一定不同。(考试重点)
即:hashCode必须为两个被该equals方法视为相等的对象产生相同的结果。
不相等的对象产生不相同的hashCode可以提高哈希表的性能。

(三)、重写而不是重载!

举一个例子:

public class Duration {
    ...   
    // Problematic definition of equals()
    public boolean equals(Duration that) {
        return this.getLength() == that.getLength();        
    }
}

然后我们对他进行测试:

Duration d1 = new Duration (1, 2);
Duration d2 = new Duration (1, 2);
Object o2 = d2;
d1.equals(d2)true
d1.equals(o2)false

这是为什么呢?
即使d2和o2最终参照相同的对象在内存中,对他们来说你仍然得到不同的结果。
在这里我们对equals方法实际上是进行了重载而不是重写,Object是一切类的父类,Duration中实际上有两个equals方法
隐式equals(Object)继承Object,和新的equals(Duration)。因此这里的两个调用实际上是调用两个方法:

  • d1.equals(o2)我们最终会调用equals(Object)实现。
  • d1.equals(d2),我们最终调用equals(Duration)版本。

正确的重写方法如下:

 @Override
public boolean equals (Object thatObject) {
    if (!(thatObject instanceof Duration)) return false;
    Duration thatDuration = (Duration) thatObject;
    return this.getLength() == thatDuration.getLength();
}
instanceof 是什么?

instanceof 是 Java 的保留关键字,它的作用是测试它左边的对象是否是它右边的类的实例,返回 boolean 的数据类型。

  • 基本类型不能用于 instanceof 判断.
  • 对于 null,它只能用来判断是否是引用类型,并且返回的总是 false,表明它不是任意一个引用类型的对象实例。而且想要编译通过null 只能放在 instanceof 关键字的左边
  • instanceof 关键字一般用于强制转换,在强转之前用它来判断是否可以强制转换:
  public B convert(A a) {
    if (a instanceof B) {
        return (B) a;
    }
    return null;
}

这里作为JAVA初学者,大佬跟我说,这个参数传进来的是Object类,他虽然更大,但是仍然可能是Duration的一个实例,Object类可以传入更多的东西,直接上贝爷的图:(感谢学校的大佬们!)
大佬解惑

(四)、可变类型的等价性

对于可变对象来说,它们多了一种新的可能:通过在观察前调用改造者,我们可以改变其内部的状态,从而观察出不同的结果。

观察等价性和行为等价性
  • 观察等价性: 两个索引在不改变各自对象状态的前提下不能被区分。即通过只调用observer,producer和creator的方法,它测试的是这两个索引在当前程序状态下“看起来”相等。
  • 行为等价性: 两个索引在任何代码的情况下都不能被区分,即使有一个对象调用了改造者。它测试的是两个对象是否会在未来所有的状态下“行为”相等。
  • 对于不可变对象 ,观察相等和行为相等是完全等价的,因为它们没有改造者改变对象内部的状态。
  • 对于可变对象 ,Java通常实现的是观察相等。例如两个不同的 List 对象包含相同的序列元素,那么equals() 操作就会返回真。

PS:在有些时候,观察等价性可能导致bug,甚至可能破坏RI。

在Java对可变对象的实现中,改造操作通常都会影响 equals() 和 hashCode()的结果。以Set为例,在刚开始放入HashSet,它是存储在此时 hashCode() 对应的索引位置。但是如果发生了改变,计算 hashCode() 会得到不一样的结果,但是 HashSet 对此并不知道,所以我们调用contains时候就会找不到这个对象。

当 equals() 和 hashCode() 被改动影响的时候,我们就破坏了哈希表利用对象作为键的不变量。

所以对可变类型来说,无需重写这两个函数,直接继承 Object对象的两个方法即可。 如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。这个在lab2中也有体现,在构建Vertex类和Edge类的时候,我对可变的类重新实现了一个check方法。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值