[软件构造] 8 Equality in ADT and OOP

[软件构造] 8 Equality in ADT and OOP

1 Equivalence Relation

没什么好写的,自反对称传递-等价。

2 Equality of Immutable Types

f: R->A 建立了ADT的某个实例与它所对应的抽象类型的关系,f即为AF
a equals b <=> f(a)=f(b) AF映射到同样的结果,则等价

站在外部观察者角度:对两个 对象调用任何相同的操作,都 会得到相同的结果,则认为这 两个对象是等价的。 反之亦然!如集合{1,2} 和集合{2,1} 操作:求基数(元素个数)、∈关系
In terms of ADT, “observation” means calling operations on the objects.

当除了 constructor外无其他操作时,不能使用 observational equality

3 == vs. equals()

==:引用等价性,指向内存中的同一块存储区域时,在snapshot diagrams里,箭头指向同一个object bubble
equals:对象等价性,对比对象的内容。

在自定义ADT时,需要重写Object的equals()

For primitives you must use ==

For object reference types:

  • 如果用==,是在判断两个对象身份标识 ID identity semantics是否相等(指向内存里的同一段空间)
  • 应该(几乎)总是使用.equals()
  • 对对象引用使用==是不好的。if (input == "yes") // A bug!!!

4 Implementing equals()

Object.equals()的默认实现:

实际是默认使用了==判断引用等价性-指向内存中同一块空间才相等。
大部分时候,我们想要实现当两个对象的内容相等时,就判断这两个对象相等,因此我们需要重新.equals()方法。

重写时小心写成了重载方法:

method signature不一样,对这里是参数类型不同,一个是Duration一个是Object
在你想重写父类方法的时候,一定要用annotation @Override来做标记!
写了这个,编译器就会检查父类中的带有同样的signature的方法,如果你的signature和父类的不一样,就会给你一个compiler error

Static Type Checking的时候,java编译器通过使用compile-time type来选择用哪个重载的操作。
equals()重写的一个例子:
如果传进来的对象不是我的类,返回错误。
如果是我的类,逐个用==比较类中的域(域一般使用基本变量,可直接用==比较)。

instanceof:判断一个对象是不是某个特殊类型的实例。
dynamic type checking,不是static
除了在实现equals()的时候,其他时候最好不要用。
其他的在运行时查看一个对象的类型的方法也不要用,如getClass()

想要实现某些针对实现同一个接口的不同类,对每个不同的类采用不同的方法来获取同一个数据,可以用多态来实现,用同一个方法名,在每个实现同一个接口的类中重写方法,而不要用好多使用instanceofif判断,判断类型之后强转成对应类型再执行对应语句。

注:x instanceof c:如果x为null,此式不会产出异常,只是返回false,因为null没有引用任何对象,一般情况下,应尽量少用类型转换和instanceof运算符。

5 The Object contract契约

for a non-null reference x , x.equals(null) should return false;

hashCode() must produce the same result for two objects that are deemed equal by the equals() method.

global equivalence relation over all objects:感觉没什么好记的
如果不满足则会造成许多bug

哈希表冲突时的处理:存储在 原索引 位置,用 链表 访问。详见数据结构相关内容。

image-20220609100750486

if two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.

不相等的对象,也 可以 映射为同样的hashCode,但使用hashTable的性能会变差,最好还是确保不相等的对象有不同的hashCode.

为什么相等的对象必须有相同的hashcode?
If two equal objects had distinct hashcodes, they might be placed in different slots. – So if you attempt to lookup a value using a key equal to the one with which it was inserted, the lookup may fail.

重写hashCode():
简单粗暴的方法:能满足contract,返回某个常数值,这样每个对象的hashcode值都一样了。a disastrous performance effect,since every key will be stored in the same slot, and every lookup will degenerate to a linear search along a long list.
标答:compute a hash code for each component of the object that is used in the determination of equality (usually by calling the hashCode method of each component), and then combining these, throwing in a few arithmetic operations. 如调用 Objects.hash()组合各个元素。

Always override hashCode() when you override equals() 除非你能保证你的ADT不会被放入到Hash类型的集合类中😅

重载hashCode()的一个例子:
image-20220609102638120

使用 hashCode()方法的另一个例子:
image-20220609102713931

6 Equality of Mutable Types

Equality: two objects are equal when they cannot be distinguished by observation.

怎么观察?
对于可变类型,判断相等有两种标准,观察等价性和行为等价性。
When they cannot be distinguished by observation that doesn’t change the state of the objects, i.e., by calling only observer, producer, and creator methods. This is often strictly called observational equality, since it tests whether the two objects “look” the same, in the current state of program.
When they cannot be distinguished by any observation, even state changes. This interpretation allows calling any methods on the two objects, including mutators. This is called behavioral equality , since it tests whether the two objects will “behave” the same, in this and all future states.
对于非可变类型,observational and behavioral equality are identical, because there aren’t any mutator methods.

可变类型 来说,往往 倾向于 实现严格的 观察等价性
– Java uses observational equality for most of its mutable data types (such as Collections), but other mutable classes (like StringBuilder ) use behavioral equality.
– If two distinct List objects contain the same sequence of elements, then equals() reports that they are equal.

But using observational equality leads to subtle bugs, and in fact allows us to easily break the rep invariants of other collection data structures.
一个例子:

image-20220609103957076

解释:
image-20220609104206297

Great care must be exercised if mutable objects are used as set elements.
The Java library is unfortunately inconsistent about its interpretation of equals() for mutable classes.
Collections use observational equality, but other mutable classes (like StringBuilder) use behavioral equality. 在JDK中,不同的mutable类使用不同的等价性标准…

用不同的等价性的spec的例子:
image-20220609104723656

教训:
equals() should implement behavioral equality。
也就是说,只有指向同样内存空间的objects,才是equals()的。
所以对 可变类型 来说,无需重写这两个函数(equals(),hashCode()),直接继承 Object的两个方法即可。
§ For clients that need a notion of observational equality (whether two mutable objects “look” the same in the current state), it’s better to define a new method, e.g., similar().

如题:

image-20220609105620623

我的错题:
Reading 15: Equality (mit.edu)
记得关闭ad blocker浏览器扩展,否则无法在线做题。

Suppose Bag<E> is a mutable ADT representing what is often called a multiset, an unordered collection of objects where an object can occur more than once. It has the following operations:

/** make an empty bag */
public constructor();

/** modify this bag by adding an occurrence of e, and return this bag */
public add(e:E):Bag<E>;

/** modify this bag by removing an occurrence of e (if any), and return this bag */
public remove(e:E):Bag<E>;

/** return number of times e occurs in this bag */
public count(e:E):number;

/** use === to compare two Bags for behavioral equivalence. */

/** return true iff this and that are observationally equivalent */
public similar(that:Bag<E>):boolean;

Suppose we run this code:

const b1 = new Bag<string>().add("a").add("b");
const b2 = new Bag<string>().add("a").add("b");
const b3 = b1.remove("b");
const b4 = new Bag<string>().add("b").add("a"); // swap!

image-20220609110436110

解析:我错的原因在于 b3 = b1.remove("b")没注意到这里会修改b1的内容。

b1 and b3 are aliases for the same Bag, which ends up containing just one occurrence of “a” and none of “b”.

b2 and b4 are both references to different Bags, each of which has one occurrence of “a” and one of “b”.
image-20220609110700599

snapshot diagrams还是好,过程清楚明白。

clone() in Object:
image-20220609111538154

剪贴板突然坏了??只是网页有道词典的bug,把剪贴板搞坏了,关掉就好了。

7 Autoboxing and Equality

直接上图了:Autoboxing and Equality

此处缺讲解结果

image-20220609111950203

image-20220609112027124

image-20220609112158087

Summary

电脑快没电了,就先水一水。

此处缺整理

Equality is one part of implementing an abstract data type (ADT).

– Equality should be an equivalence relation (reflexive, symmetric, transitive).

– Equality and hash code must be consistent with each other, so that data structures that use hash tables (like HashSet and HashMap ) work properly.

– The abstraction function is the basis for equality in immutable data types.

– Reference equality is the basis for equality in mutable data types; this is the only way to ensure consistency over time and avoid breaking rep invariants of hash tables.

§ Safe from bugs – Correct implementation of equality and hash codes is necessary for use with collection data types like sets and maps. It’s also highly desirable for writing tests. Since every object in Java inherits the Object implementations, immutable types must override them.

§ Easy to understand – Clients and other programmers who read our specs will expect our types to implement an appropriate equality operation, and will be surprised and confused if we do not.

§ Ready for change – Correctly-implemented equality for immutable types separates equality of reference from equality of abstract value, hiding from clients our decisions about whether values are shared. Choosing behavioral rather than observational equality for mutable types helps avoid unexpected aliasing bugs

posted @ 2022-06-11 10:57  Matrix_250  阅读(24)  评论(0编辑  收藏  举报