ADT和OOP的等价性

大纲

  • 站在观察者角度,利用AF,定义不可变对象之间的等价关系
  • 引用等价性和对象等价性
  • 可变数据类型的观察等价性和行为等价性
  • 理解Object的契约,正确实现等价关系判定

在很多场景下,需要判定两个对象是否“相等”,例如:判断某个Collection中是否包含特定元素。

1.等价关系

ADT是对数据的抽象,体现为一组对数据的操作
抽象函数AF:内部表示 抽象表示
基于抽象函数AF定义ADT的等价操作
在这里插入图片描述

2 Equality of Immutable Types

利用AF定义等价性:

  • a equals b if and only if f(a)=f(b). AF映射到同样的结果,则等价

用等价关系引入抽象函数,用抽象函数定义的关系是等价关系

用观察法定义等价性

我们可以应用的每个操作都会对这两个对象产生相同的结果。
站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!

3.== vs equal

= = 引用等价性
equals 对象等价性
在自定义时,需重写equals
= = 对基本数据类型,使用==判定相等
对对象类型,使用equals()
如果用 = =,是在判断两个对象
身份标识 ID是否相等(指向内存里的同一段空间)

4 Implementing equals()

equal()方法由对象定义,其默认实现是这样的
在这里插入图片描述
在Object中实现的缺省equals()是在判断引用等价性
在这里插入图片描述

Java编译器使用参数的编译时类型在重载操作之间进行选择。静态类型检查
在这里插入图片描述
一个更好地实现equals的方法:在这里插入图片描述
instanceof:

  • 运算符instanceof测试对象是否为特定类型的实例。
  • 使用的instanceof是动态类型检查,而不是静态类型检查。
  • 一般来说,在面向对象编程中使用instanceof是一种问题。除了实现平等之外,应该在任何地方都不允许使用它。

使用多态性来避免使用instanceof

5 The Object contract

等价关系:自反、传递、对称
除非对象被修改了,否则调用多次equals应同样的结果
如果x不为空,x.equals(null)应返回false
“相等”的对象,其hashCode()的结果必须一致
equals方法实现了等价性关系
Reflexive – every object is equal to itself
Symmetric – if a.equals(b) then b.equals(a)
Transitive – if a.equals(b) and b.equals©, then a.equals©
Consistent– equal objects stay equal unless mutated
“Non-null” – a.equals(null) returns false
这些因素结合在一起确保equals是所有对象的全局等价关系
用“是否为等价关系”检验你的equals()是否正确

违反等价关系:

在这里插入图片描述

违反哈希表:

哈希表是映射的一种表示形式:将键映射到值的抽象数据类型。
哈希表的rep不变量包含一个基本约束,即键位于由其哈希代码确定的槽中。

  • hashcode的设计使得密钥将均匀地分布在索引上。
  • 但有时会发生冲突,两个键放在同一索引中。
  • 因此,哈希表不是在索引中保存单个值,而是实际保存一个键/值对列表,通常称为 哈希桶 。
  • 一个键/值对在Java中实现为一个包含两个字段的对象。
  • 插入时,将一对添加到由哈希代码确定的数组槽中的列表中。
    在这里插入图片描述
    在应用程序执行期间,每当对同一对象多次调用它时,hashCode方法必须始终返回相同的整数,假设不修改在该对象的等比较中使用的信息。
    从一个应用程序的一个执行到同一应用程序的另一个执行,这个整数不需要保持一致。
    == 等价的对象必须有相同的hashCode ==
    程序员应该知道,为不相等的对象生成不同的整数结果可能会提高哈希表的性能,不相等的对象,也可以映射为同样的hashCode,但性能会变差。
    为什么对象契约要求相同的对象具有相同的哈希码?
    如果两个相等的对象具有不同的哈希码,则它们可能被放置在不同的插槽中。
    因此,如果尝试使用与插入值相同的键来查找值,则查找可能会失败。
    对象的默认hashCode()实现与其默认值等于()相一致:
    在这里插入图片描述

Overriding hashCode()

一个简单而有效的方法来确保契约得到满足,就是hashCode总是返回一些常量值,所以每个对象的hash码都是相同的。
这满足了对象契约,但会对性能产生灾难性的影响,因为每个键都将存储在同一个槽中,并且每个查找都将退化为沿长列表的线性搜索。
标准是为用于确定相等性的对象的每个组件计算一个哈希码(通常通过调用每个组件的hashCode方法),然后将它们组合起来,抛出一些算术运算。

. Java的最新版本现在有了一个实用方法Objects.hash(),使得实现包含多个字段的哈希代码变得更容易。
Always override hashCode() when you override equals()
除非你能保证你的ADT不会被放入到Hash类型的集合类中 。
hashCode Override实例

public final class PhoneNumber {
 private final short areaCode;
 private final short prefix;
 private final short lineNumber;
 
 @Override 
 public int hashCode() {
 int result=17;
 result+=31*result+areaCode;//constant must be odd
 result==31*result +prefix;
 result+=31*result+lineNumber;
 return result;
 }
 ...
}

替代的hashCode覆盖:

public final class PhoneNumber {
 private final short areaCode;
 private final short prefix;
 private final short lineNumber;
 
 @Override 
 public int hashCode() {
 short[] hashArray = {areaCode, prefix, lineNumber};
 return Arrays.hashCode(hashArray); 
 }
 ...
}

6 Equality of Mutable Types

观察等价性:在不改变状态的情况下,两个mutable对象是否看起来一致
行为等价性:调用对象的任何方法都展示出一致的结果。
对可变类型来说,往往倾向于实现严格的观察等价性。
但在有些时候,观察等价性可能导致bug,甚至可能破坏RI。
如果将可变对象用作集合元素,则必须非常小心。
如果某个mutable的对象包含在Set。

List<String> list=new ArrayList<>();
 list.add("a");
 Set<List<String>> set=new HashSet<>();
 set.add(list);
 System.out.println(set.contains(list));
 list.add("goodbye");
 System.out.println(set.contains(list));
 for(List<String> l:set)
 {
	 System.out.println(set.contains(l));
 }

结果为:

true
false
false

集合类中,当其发生改变后,集合类的行为不确定  务必小心。
在JDK中,不同的mutable类使用不同的等价性标准。
在这里插入图片描述
对可变类型,实现行为等价性即可。
也就是说,只有指向同样内存空间的objects,才是相等的。
所以对可变类型来说,无需重写这两个函数,直接继承Object的两个方法即可。
如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。e.g. similar()。
在这里插入图片描述

7 Autoboxing and Equality

在这里插入图片描述
x==y false 引用不相等
int(x)==int(y) True
在这里插入图片描述
在这里插入图片描述

Map<String, Integer> a = new HashMap<>();
 Map<String, Integer> b = new HashMap<>();
 a.put("c", 1);
 b.put("c", 1);
 System.out.println (a.get("c")== b.get("c"));
 Integer x = new Integer(2);
 Integer y = new Integer(2);
 System.out.println(x==y);
 Integer x1 = 2;
 Integer y1 = 2;
 System.out.println(x1==y1);

结果:
true
false
true

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值