HIT软件构造第三章四五节知识点总结


    本节的内容为ADT的实现的技术基础——面向对象编程,以及ADT和OOP中的等价性研究

一.OOP基础

    这里只是对需要注意的地方做一些常识性总结,毕竟这些都是java初学者必须要熟练掌握的内容。

1.OOP的一些基本概念

    在java语言中,我们最经常遇到的OOP概念即为类,对象和接口。
    现实世界中的对象可映射为编程中的对象,其中状态即映射为字段,行为则映射为方法。
    类的概念是在对象的概念产生后产生的。类是对一组对象统一的表达,是一个抽象的概念,现实世界中并不存在类的实体;而对象则是类的实例化。
    类中的字段和方法分为属于类的和属于对象的。加static即为属于类,在类的外部用类名.字段/方法名进行操作;而不加static即为属于对象,在类的外部用对象名.字段/方法名进行操作。
    注:静态方法中无法直接调用非静态成员和方法,但是可以new一个对象,之后再使用。
    接口不能有构造方法,也要写规约,实现接口的类对相应的方法可以不写规约。要注意如果接口的规约表示为不可变类,那相应的实现中也要是不可变的,这点常常被忽视。
    以前使用接口并不能完全隐藏实现,但现在接口中可以有静态方法,从而实现完全的隐藏,例如 实验二中的graph接口。
在这里插入图片描述

2.信息隐藏与封装

    实现方式:尽量使用接口声明变量,客户端仅使用接口中定义的方法,让客户端不能直接访问属性。可采用不同的权限修饰词来进行约束,以下为四个修饰词各自的权限。
在这里插入图片描述
    总的来说,不需要用户使用的方法都应尽量是private的,或者作用域能尽量小就尽量小。况且作用域越小,容错率越高。

3.继承与覆写

    子类覆写的方法的可见性要大于等于父类,因为子类的对象应可以转型为父类对象,可见性的降低就会出现错误。同理,子类抛出的异常必须是父类的子集。
    覆写的方法是在运行时判断类型来调用的,因此以new构造的类型为准。
    在子类中,用super关键字来调用父类的各种方法,用的最多的是构造方法。

4.多态

    多态主要分为三种,我们下面分别来介绍。
    A.特殊多态:一个方法可以有多个同名的实现(方法重载)。要求参数列表必须有不同的地方,其他的并没有要求,总之就是把重载的方法看作和之前完全不同的方法。
    重载是编译时确定,属于静态检查。而覆写是运行时确定。因此对于类A 变量名 = new 类B(参数)而言,编译时类型为A,运行时类型为B。具体例子如下图所示。
在这里插入图片描述
    另外,还需注意这种变量作为参数时多态的用法,如下图所示。
在这里插入图片描述
    B.参数化多态:一个类型名字可以代表多个类型(泛型化编程)
    泛型类/接口的格式:关键字 类名/接口名<占位符>(){…} 占位符其实就相当于额外的参数,代表着对应的类型。实际调用的时候使用类型参数,转化为对应的类名(因此如果希望是整型参数,要用Integer而不是int),因此运行阶段不存在泛型。泛型接口可以有泛型或非泛型的实现类,取决于自己的需求。
    泛型方法的格式:关键字 <占位符> 返回值 方法名(参数列表){…},注意,参数列表中有泛型变量,并不会导致这是一个泛型方法,因为这其实是属于泛型类的泛型变量。
    对于静态方法,如果使用泛型参数,那么必须也定义为泛型方法。
    C.子类型/包含多态:一个变量名字可以代表多个类的实例。其实就如同上面的例子用到的一样。子类型的实例也可以说是父类型的一个实例(当然反之不成立),因此子类型的规约不能弱化超类型的规约。

二.ADT和OOP中的等价性

1.三种判断等价性的方法

    <1>.利用数学上等价的定义:满足自反性,对称性和传递性。
    <2>.利用AF来定义ADT的等价判断,如果两个值的AF映射值相同,则说明他们等价。
    <3>.在调用者角度,如果调用两个对象的所有任意操作,效果都完全一样,那么则说明他们等价。

2.“==”与equals

    对于“==”,代表着判断两个对象的地址空间是不是在同一个位置,也被称为引用等价性;而equals则代表着判断两个对象的内容,也被称为对象等价性。
    object类的equals默认为判断两者的地址空间是不是在同一位置。总的来说,对于基本数据类型,我们应该用“==”,而对于对象数据类型,我们应该用equals判断(当然,要根据需要判断是否应该覆写),当然,我们要明确知道我们写的方法是覆写还是重载。
    除了写equals方法,尽量不要用instanceof和getclass(),因为他们不符合OOP的思想,不是好的书写风格。

3.hashcode与equals

    hashcode方法就类似于数据结构中的哈希函数一样,功能就是把一个个对象映射到一个地址(也就是桶)。默认的方式是按照地址映射,一般地址不同则值不同。
    当我们使用HashSet等类似的数据结构的时候,这时再进行寻找时,就如同正常哈希表一样了,是通过判断哈希值是否相等先进行判断,哈希值相同是equals成立的前提,也是首先判断的方法。此时如果我们只重写equals而不重写hashcode,则hashcode还是默认的按地址算出一个值,一般而言只要地址不同则哈希值就不同了,导致了我们equals方法看似失效。
    综上,equals()应满足如下条件:首先满足等价性(数学意义上),其次保证在不被修改的情况下,每次调用的结果应该相同,同时要处理对象为null的情况还有如果这个类使用了哈希表进行查询(比如放入了HashSet中),则也要覆写hashcode方法。

4.可变类型的等价性

    有两种判断方式:观察等价性与行为等价性;分别代表不改变状态的前提下,通过观察器来判断两者是否相同以及调用任何方法是否有相同的结果,但由于是可变类型,也就相当于判断地址是否相同了。
    虽然很多时候我们倾向于判断观察等价性是否相同,但是可能引起bug甚至破坏RI,尤其是使用了Set的时候。比如经典的Set<List< T >>引起的bug。
    因此,总结起来来说,对于可变类型,我们不应该覆写equals方法和hashcode方法,如果想“比较”,我们可以使用其他自定义的方法;而对于不可变类型,如果我们想要“比较”,那么我们是一定要覆写这两个方法的。

5.装箱及其等价性

    在使用集合类时,我们前面也提到过,会进行隐式的装箱操作。因此,装箱完毕后,如果是两个不同对象,即使是整数的装箱,使用“==”进行判断也会返回false,要进行equals的覆写。
    但是下面有一个特例,那就是如果是-128到127之间的整数,进行两次装箱操作,那得到的地址是一样的(得益于java的缓存机制)。当然,上面也是要求只装箱,不能为两次new的操作,不然肯定地址值不同。总的来说,如下图所示。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值