软件构造学习笔记第八章——ADT和OOP中的等价性

目录

1.不可变类型的等价性(两个角度)

1.1AF函数的角度

1.2观察器的角度

1.3举个例子

1.3.1例一

1.3.2例二

1.3.3例三

 2.引用等价性(reference equality)和对象等价性(value equality)

3.可变类型的等价性

3.1观察等价性带来的bug

3.2JDK中可变类型的等价性标准

3.3我们如何来为可变类型编写等价性

1&3.番外篇——对(不)可变类型的等价性总结

4.instanceof

4.1基本用法

4.2注意事项

4.3应用场景

4.4avoid instanceof

5.重写equals和hashcode

5.1编写equals的原则

5.2hashcode

5.2.1Object中的hashcode

5.2.2hashcode的作用

5.2.3重写hashcode(以Duration为例)

5.3重写equals

6.Object里的clone()

7.包裹类型等价性

7.1Integer的两种封装方法


1.不可变类型的等价性(两个角度)

1.1AF函数的角度

两个不可变类型a和b等价当且仅当AF(a)=AF(b),注意,此处AF对应的是具体的值。

1.2观察器的角度

两个不可变类型a和b等价当且仅当它们两个调用所有的观察器得到的结果相同。

例如:对象A是集合{a,b},对象B是集合{b,a},他们两只是存储的顺序不同,如果调用a和b的观察器如A.size()和B.size(),或者判断a和b属于A和属于B都一样。

1.3举个例子

1.3.1例一

从AF角度来看,d_1_2的AF(1,2)=62s,d_1_3的AF(1,3)=63s,d_0_62的AF(0,62)=62s,e_1_2的AF(1,2)=62s,因此d_1_2和d_0_62和e_1_2是等价的。

从观察器角度来看,由于只有一个观察器length,d_1_2和d_0_62和e_1_2返回的值相等,因此d_1_2和d_0_62和e_1_2是等价的。

1.3.2例二

 ①通过AF角度判等价

②下图给出了生产器和观察器,然后让你选生产器和构造器的组合,问哪一个组合得到的从观察器角度判处的等价和从AF判出来的等价是一样的。

 从上至下为A~E。对于A选项,此时只包含一个观察器contains,如果从contains得到的两个对象等价,那么显然AF角度也等价。对于B选项,此时只包含一个观察器size,如果从size得到的两个对象等价如{a,b}和{c,d},但是显然从AF角度观察是不等价的。对于C选项,此时包含观察器size和contains,同上分析,显然。对于D选项,由于length是对属性s进行观察,这是不允许的,因为我们不允许客户访问类的具体属性,s是R空间的值,而客户只能知道抽象空间的值。 对于E选项,此选项都没观察器。

1.3.3例三

①通过观察器角度看等价

 ②选出相应的AF,使得观察器角度等价当且仅当AF角度等价,和之前的题一样。

 2.引用等价性(reference equality)和对象等价性(value equality)

        引用等价性指的是两个变量指向内存中同一物理位置的元素,那么这两个变量等价;

        对象等价性指的是两个变量指向的对象的属性在数值上是相等的,那么这两个变量等价。

        对基本数据类型,用‘==’来判定相等,对于对象类型,用‘equals’来判定相等,由于object中的equals是引用等价性,我们一般使用的是对象等价性,因此想判断两个对象是否具有对象等价性时,应该要重写类中的equals,并且equals要满足自反性、等价性、传递性

3.可变类型的等价性

        可变类型和不可变类型的差别是,可变类型具有变值器,即能对内部属性进行改变,不可变类型没有变值器。可变类型的两个对象可能在某一时刻属性是一样的,但是其中一个对象使用变值器后,可能属性就不相等了。因此判断可变类型的等价性也有两个方面,一个是观察等价性,一个是行为等价性。

        观察等价性即判断某一时刻两个变量指向的对象的属性是否相等,行为等价性是两个变量指向的对象在使用变值器后仍相等。通俗点说就是如果两个变量指向的对象的属性数值相等,那么说这两个变量具有观察等价性,如果两个变量指向的是同一个内存上的对象,那么说这两个变量具有行为等价性。不可变类型的观察等价性和行为等价性是一致的,因为没有变值器。

3.1观察等价性带来的bug

List<String> list = new ArrayList<>();
list.add("a");
//完成下两句后Set会为list分配一个对应的哈希值。
Set<List<String>> set = new HashSet<List<String>>();
set.add(list);
set.contains(list);//true

list.add("b");
set.contains(list);//false
//此时list明明还在set中,输出false的原因是list在使用变值器时会改变它的hashcode()的结果。

3.2JDK中可变类型的等价性标准

集合类如List、Set、Data使用的都是观察等价性,StringBuffer使用的是行为等价性。

3.3我们如何来为可变类型编写等价性

        一般而言,我们对可变类型的等价性采用行为等价性,即只需继承Object中的hashcode和equals即可,但如果实在想采用观察等价性,建议重新编写一个similar函数,通过similar函数来判断观察等价性。

1&3.番外篇——对(不)可变类型的等价性总结

对于不可变类型:

        ①不可变类型必须重写equals和hashcode。

        ②equals比较的是两个对象的A空间的值

        ③hashcode是建立A空间的值到整数的映射

        ④对不可变类型,有两种判断等价性的方式,一个是从AF角度观察,即观察两个不可变类型的内部属性映射的A空间的值是否一致,另一个是从观察器角度观察,即调用两个不可变类型的观察器,看结果是否一致。

对于可变类型:

        ①可变类型一般实现行为等价性,无非重写equals和hashcode,如果实在想比较观察等价性,可以添加一个similar函数比较。

        ②hashcode建立的是地址引用到整数的映射(Object中就如此)

4.instanceof

4.1基本用法

//基本用法:对象 instanceof 类
//判断对象是否是该类在堆上实际存在的实例。
//Parent<--son
//Parent<--sonson
son Ason = new son();
Parent fakeson = Ason;
//尽管fakeson是Parent型变量,但其实际指向堆上的对象是son型
System.out.println(fakeson instanceof son);//true,运行时检测

4.2注意事项

int a = 1;
System.out.println(a instanceof int);//编译失败,不支持基本类型,只支持对象
/*****************************************/
//Parent<--son
//Parent<--sonson
son Ason = new son();
sonson Asonson = new sonson();
System.out.println(Ason instanceof sonson);//编译失败,对象和类必须是同一继承分支
System.out.println(null instanceof sonson);//对于null,总是返回false

4.3应用场景

        用于判断某一用父类型变量指向的对象在堆上是否属于某一子类型,如果是则可以向下造型并且使用子类型中所含有且父类型未含有的方法。 

4.4avoid instanceof

5.重写equals和hashcode

        如果你无法确保你写的类之后不会被放到Hash类型的集合类中来,那么就必须同时重写equals和hashcode。

5.1编写equals的原则

①满足等价关系:自反、传递、对称

②保持性:除非对象被修改,否则多次调用equals得到的结果是一样的

③对于任意一个非空对象x,x.equals(null)总是返回false

④如果对象相等,那么它对应的hashcode()结果必须相等,但是两个对象的hashcode()相等,不意味着这两个对象相等。

5.2hashcode

5.2.1Object中的hashcode

        Object中的hashcode返回的值是该对象对应的地址。

5.2.2hashcode的作用

 摘自hashcode相等的两个对象一定相等吗_hashCode为什么存在?_008972的博客-CSDN博客

5.2.3重写hashcode(以Duration为例)

 重写hashcode时要抓住对象的特征,根据判定相等的特征来重写hashcode。

5.3重写equals

6.Object里的clone()

        clone()函数创造和返回一个对象的副本。

        Object里的clone()完成的是对对象的一个浅拷贝。

        浅拷贝:新构建的对象属性仍然是指向原来的属性,并没有构建新属性的内存空间。

        深拷贝:新构建的对象属性有自己的内存空间,且有和原来属性一样的值。

7.包裹类型等价性

包裹类型是不可变类型对象

7.1Integer的两种封装方法

Integer x = new Integer(3);//使用new封装
Integer x = 3;//使用valueof封装

        如果使用的是new,那么它就会在堆上生成一个新的对象;如果使用的是valueof,对于值是-128~127时,调用valueof时首先会检索系统中是否存在该值的对象,如果存在则指向同一对象,对于值不在该区间的时候,每次调用valueof都会创建新的对象。

Integer x = new Integer(3);
Integer y = new Integer(3);
System.out.println(x.equals(y));//true,值相等
System.out.println(x==y);//false,堆上的对象不同
Map<String,Integer> a = new HashMap<>();
Map<String,Integer> b = new HashMap<>();
a.put("c",1);//注意,此时1是Integer类型
b.put("c",1);
a.put("d",130);
b.put("d",130);
//true,由于put调用的是valueof且1在-128~127之间,因此两个Integer类型的1其实在堆上是同一个对象。
System.out.println(a.get("c")==b.get("c"));
//false,由于put调用的是valueof且130不在-128~127之间,因此两个Integer类型的1其实在堆上不是同一个
//对象。
System.out.println(a.get("d")==b.get("d"));

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值