HIT-SC-Chapter Eight
HIT-SC-Chapter Eight
Equality in ADT and OOP
ADT和OOP中的“等价性”
如何来认定对象之间的相似性?在很多场景下,需要判定两个对象是否
“相等”,例如:判断某个
Collection中是否包含特定元素。
==和equals()有何区别?如何为自定
义ADT正确实现equals()?
1 Equivalence Relation
Equality operation on an ADT
- ADT是对数据的抽象,体现为一组对数据的操作
- 抽象函数AF:内部表示$\rightarrow$抽象表示
- AF说明如何将具体表示值解释为抽象类型的值
- 抽象函数的选择如何决定如何编写代码实现ADT的每个操作
- 基于抽象函数AF定义ADT的等价操作
Equality of values in a data type?
- 现实中的每个对象实体都是独特的(地址/名称)
- 所以无法完全相等,但有“相似性”
- 在数学中,“绝对相等”是存在的
Equivalence Relation 数学中的等价关系
- 等价关系是E⊆T x T,即:
- reflexive: E(t,t) ∀t∈T 自反
- symmetric: E(t,u) ⇒ E(u,t) 对称
- transitive: E(t,u) ∧ E(u,v) ⇒ E(t,v) 传递
- 等价关系:自反、对称、传递
- 对于像==或equals()这样的布尔值二进制操作,等价性E是操作返回true的对(x,y)的集合。
2 Equality of Immutable Types不可变类型的相等性
为什么讨论“等价性”时,要区分两种类型?
Using AF to define the equality
- 回想一下,抽象函数f: R→A将数据类型的具体实例映射到它们对应的抽象值。
- 用f作为等式的定义,我们说a等于b当且仅当f(a)=f(b)。
- AF映射到同样的结果,则等价。
- 这应该有个条件:即AF-->是双射。但AF并不要求一定是双射,那如何理解 a equals b?
- 等价关系产生抽象函数
- 一个等价关系引出一个抽象函数(这个关系划分了T,所以f将每个元素映射到它的划分类)。
- 抽象函数导出的关系是等价关系。
Using observation to define the equality
- 局外人(客户端)可以观察到什么
- 站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这
两个对象是等价的。反之亦然!
- 站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这
3 == vs. equals()
- Java有两种不同的测试相等性的操作,具有不同的语义。
==
- Two references are == if they point to the same storage in memory.
- 根据快照图,如果两个引用的箭头指向相同的对象气泡,则为==。
- 引用等价性
equals()
- 操作比较对象内容
- 对象等价性
- 必须为每个抽象数据类型适当地定义equals操作
- 自定义ADT时,需要重写Object的equals()
==
vs.equals()
- 基本数据类型(无引用),使用
==
判定相等 - 对象类型,使用
equals()
- 如果用
==
是在判断身份标识ID是否相等(指向内存里的同一段空间)- Exactly as implemented by
Object.equals()
- 即若未重写,与
==
效果一样
- Exactly as implemented by
- 如果用
- 在对象引用上使用
==
在代码中是很糟糕的
- 基本数据类型(无引用),使用
Tips for overriding a method
- Make sure signatures match
- Use @Override so compiler has your back
- Do copy-and-paste declarations (or let IDE do it for you)
4 Implementing equals()
Equality of Immutable Types
-
The equals() method is defined by Object , and its default implementation looks like this:
- 即equals()的默认含义与引用等价性相同
- 我们必须重写equals()方法,用我们自己的实现替换它。
-
但重写不能重载
- Java编译器使用参数的编译时类型在重载操作之间进行选择。
- It’s easy to make a mistake in the method signature, and overload a method when you meant to override it.
- Java’s annotation @Override should be used whenever your intention is to override a method in your superclass.
- With this annotation, the Java compiler will check that a method with the same signature actually exists in the superclass, and give you a compiler error if you’ve made a mistake in the signature.
-
hashCode()
- hashCode()方法存在的主要目的就是提高效率,但是如果你想把对象放到散列存储结构的集合中时,是必须要重写的。
-
-
instanceof
-
instanceof操作符测试对象是否是特定类型的实例。
-
使用instanceof是动态类型检查,而不是静态类型检查。
-
instanceof只能在equals方法上用。其他地方禁用。
-
禁用的还包括检查对象运行时类型的其他方法。
-
永远不要(?)在超类中使用instanceof来检查子类的类型
-
-
5 The Object contract
- The contract of equals() in Object 重写equals()方法时必须遵守一般约定
- an equivalence relation
- 这是自反的,对称的,传递的;
- consistent
- 除非被修改,多次调用应该返回同样的结果
- for a non-null reference x , x.equals(null) should return false
- hashCode()必须为equals方法认为相等的两个对象产生相同的结果(必要条件)。
- an equivalence relation
- The equals method implements an equivalence relation:
- 一致:对于任何非空的引用值x和y,多次调用x.equals(y)一致地返回true或一致地返回false,前提是在对象的equals比较中使用的信息没有被修改。
- Reflexive: For any non-null reference value x, x.equals(x) must return
true. - Symmetric: For any non-null reference values x and y, x.equals(y) must
return true if and only if y.equals(x) returns true - Transitive: For any non-null reference values x, y, z, if x.equals(y)
returns true and y.equals(z) returns true, then x.equals(z) mus return
true. - For any non-null reference value x, x.equals(null) must return false
- Equals是所有对象的全局等价关系。
- bed example:
- HashTables
- 哈希表提供固定的查找时间,因此它们往往比树或列表表现得更好。除了提供等号和hashCode之外,键不需要排序,也不需要有任何特定的属性。
- A hash table is a representation for a mapping: an abstract data
type that maps keys to values. - How it works?
- 哈希表的ri中包含一个基本约束,即键位由器哈希码确定
- 它包含一个数组,该数组初始化后的大小与预期插入的元素数量相对应。
- 当一个键和一个值要插入时,我们计算键的哈希码,并将其转换为数组范围内的索引(例如,通过模除法)。然后将该值插入到该索引处。
- hash bucket
- 哈希表不是在索引中保存单个值,而是保存键/值对的列表,通常称为
- 键/值对在Java中被简单地实现为具有两个字段的对象。
- 在插入时,向由散列代码确定的数组槽中的列表中添加一对。
- 对于查找,您对键进行散列,找到正确的槽,然后检查每个对,直到找到一个键等于查询键的对。
- 哈希表不是在索引中保存单个值,而是保存键/值对的列表,通常称为
-
The hashCode contract
- 只要在应用程序同一次执行期间在同一个对象上多次调用hashCode方法,只要不修改对象上的等号比较中使用的信息,该方法就必须始终返回相同的整数。
- 在应用程序的一次执行和同一应用程序的另一次执行之间,这个整数不必保持一致。
- 如果根据equals(Object)方法,两个对象相等,那么在这两个对象上调用hashCode方法必须产生相同的整数结果
- 如果根据equals(Object)方法,两个对象是不等的,那么对这两个对象中的每一个调用hashCode方法都必须产生不同的整数结果,这一点是不需要的。
- 只要在应用程序同一次执行期间在同一个对象上多次调用hashCode方法,只要不修改对象上的等号比较中使用的信息,该方法就必须始终返回相同的整数。
-
do:
- If you override equals you must override hashCode
- 不相等的对象应该有不同的哈希码
- Take all value fields into account when constructing it 在构建它时,要考虑所
有的值字段 - 除非对象发生变化,否则哈希码不能改变
-
Why the Object contract requires equal objects to have the same hashcode?
- 如果两个相等的对象具有不同的哈希代码,则它们可能被放置在不同的插槽中。
- 因此,如果试图使用与插入值相同的键查找值,查找可能会失败。
-
对象的默认hashCode()实现与默认的equals()一致:依赖地址
-
Overriding hashCode()
- 确保契约得到满足的一种简单而激进的方法是,hashCode总是返回一些常量值,因此每个对象的hash代码都是相同的。
- 这满足了对象契约,但会对性能造成灾难性的影响,因为每个键都将存储在同一个插槽中,并且每个查找都将退化为沿长列表的线性搜索。
- 标准做法是为用于确定相等性的对象的每个组件计算一个哈希代码(通常通过调用每个组件的hashCode方法),然后将它们结合起来,加入一些算术运算。
- 确保契约得到满足的一种简单而激进的方法是,hashCode总是返回一些常量值,因此每个对象的hash代码都是相同的。
-
请注意,如果您根本不重写hashCode(),您将从Object获取一个基于它对象的地址的值。
-
-
6 Equality of Mutable Types
-
当两个物体不能通过观察加以区分时,它们就是相等的。
-
With mutable objects, there are two ways to interpret this:
- 观察等价性:
- 在不改变状态的情况下,两个mutable对象是否看起来一致
- 行为等价性
- 调用对象的任何方法都展示出一致的结果
- 观察等价性:
-
对于不可变对象,观察相等和行为相等是相同的,因为没有任何mutator方法
-
对于可变类型,往往倾向于实现严格的观测等价性。
- Java对其大多数可变数据类型(如集合)使用观察相等,但其他可变类(如StringBuilder)使用行为相等。
- 如果两个不同的List对象包含相同的元素序列,那么equals()报告它们是相等的。
-
但观察等价性可能会造成bug乃至于破坏RI
-
-
-
The Final Rule for equals() and hashCode()
- For immutable types :
- equals() should provide behavioral equality.
- hashCode() should map the abstract value to an integer.
- So immutable types must override both equals() and hashCode()
- For mutable types :
- equals() should compare references, just like == . Again, this is the same
as saying equals() should provide behavioral equality. - hashCode() should map the reference into an integer.
- So mutable types should not override equals() and hashCode() at all,
and should simply use the default implementations provided by Object .
Java doesn’t follow this rule for its collections, unfortunately, leading to
the pitfalls that we saw above.
- equals() should compare references, just like == . Again, this is the same
- For immutable types :
- clone() in Object
7 Autoboxing and Equality
- Primitive types and their object type equivalents, e.g., int and Integer .
- If you create two Integer objects with the same value, they’ll be equals() to each other.
- But what if x==y? ----- False
- 如果存在,则直接返回引用,不再重新开辟内存空间。
如果不存在,就创建一个新的对象。
利用缓存,这样做既能提高程序执行效率,还能节约内存。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】