[软件构造]05-从多态理解ADT与OOP的等价性

本文主要包括以下几个方面:

1.特殊多态与重载

2.参数多态与泛型

3.子类型多态

4.equals()和==

5.hashCode()

6.变量的等价性

7.克隆

正文:

一、各种角度的多态

特殊多态与重载:多种方法具有同样的名字,但有不同的参数列表(必须,这里的不同强调类型而非参数名)或返回值类型。重载是一种静态多态,根据参数列表进行最佳匹配,进行静态类型检查,在编译时决定用哪个方法(重写是动态检查)。在具体使用特殊多态时,编译器会先检查左侧变量定义类型(reference)是否有该方法,然后运行时使用右侧实际对象中的方法。例如:

        注意,重写与重载是两个完全不同的概念。重写时父子类型有相同的签名(名称+参数列表+返回类型),签名不同则重载,而且子类重载父类方法后仍然继承父类这个方法,而重写不能。例如:

//父类
class A
{
    Object function(type a){...}
}
//子类
class B extends A
{
    //重写
    @Override
    Object function(type a){...}
    //重载
    Object function(ParentType a){...}
    Object function(type a, type b){...}
    int function(type a){...}
}

参数多态与泛型(generics):参数多态性是指方法针对多种类型时具有同样的行为,所以可用统一的表达式表达多种类型,称为泛型。在运行时根据具体指定类型擦除泛型。泛型编程是一种编程风格,其中数据类型和函数是根据待指定的类型编写的,随后在需要时根据参数提供的特定类型进行实例化。

        注意:类型变量与泛型类:使用泛型变量可以有泛型类、泛型接口和泛型方法。类中如果包含泛型变量则为泛型类,这些变量是类的类型参数,用<L,T,…>指示,可用通配符?。不能构造泛型数组。

子类型多态:一个类只有一个父类,却可以实现多个接口。B是A的子类型意味着任意B满足A的规约,即B的规约强度比A高。子类型的规约不能弱化超类型对的规约。从而,子类型多态就是不同类型的对象可以统一处理而无需区分,从而隔离了变化。

二、两个等价关系:equals和==

等价关系:自反、对称且传递的集合。在ADT中,如果AF映射到同样的结果则等价,即AF(a)=AF(b)。站在观测者的角度,对两个对象调用任何相同的操作都会得到相同的结果,则两个对象是等价的。

等价号:==表示引用等价性,即左右两边指向相同内存,在快照中就是指向同一圆圈的两个变量。equals()是对象等价性,比较对象成员是否都相等。因此,对基本数据类型要用==判定,对对象要用equals。具体地,如果用==就是在判断两个对象身份标识ID是否相等;而本身Object.equals()就是用==实现的,所以应该总是使用equals判断相等(如果只是判断内存地址相等则无需重写它,若想用特定逻辑判断等价则需要重写)。

instanceof:判断某个对象是不是特定类型(或子类型),是一种动态检查。建议除了重写equals之外在OOP不要用它和getClass(),要用多态接口来避免instanceof。

对象等价的性质:除了自反、对称、传递外,除非等价对象被修改了,否则调用多次equals应是同样的结果(一致性),而且equals的对象的hashCode也相等。x.equals(null)总是返回false。equals()还必须对所有对象都生效。

三、哈希码与哈希表

哈希表实现了键值之间的映射,键值对中的key被映射为hashcode,对听到数组的index,正是hashcode决定数据被存储在哪里。所以,哈希表的RI中基本要求就是key在slot中的位置由hashcode唯一指定。程序中多次调用同一对象的hashCode应不变,但不要求多次程序中值相同。等价对象必须有相同的hashCode。应注意不相等的对象可能映射同样的hashCode。默认的hashCode值是内存地址,与equals在一起。重写hashCode最简单的方法是让所有对象的hashCode位同一常量(性能低),标准的是通过equals计算中用到的所有信息的hashCode组成新的hashCode。现代java允许使用objects.hash()来计算复域的hashCode(但仍然基于内存地址)。所以重写时必须保证等价对象hashCode相等。在具体重写时可以这样写:

class A{
    //类A包含基本型成员a,b,c
    @Override
    public int hashCode()
    {
        int result=17;
        result=result+31*a.hashCode();
        result=result+31*b.hashCode();
        result=result+31*c.hashCode();
        return result;
    }
}

四、可变类型与不可变类型的等价性

可变类型等价性:一方面,观察等价性,在不改变状态的情况下,两个mutable对象看起来是否一致。另一方面,行为等价性,调用对象的任何方法都有一致的结果(对于不可变类型这两方面等价,因为没有变值器)。可变类型倾向于实现严格的观察等价性(但有时可能导致bug甚至破坏RI),java对其大部分可变数据类型(如collection)使用观察等价性,如两个list包含相同顺序的相同元素则equals返回true。一些可变类型可用行为等价性。但有时可能导致bug甚至破坏RI,例如list ls=new set加入多个元素后,对任何元素的访问在set.contains里都找不到。这是因为变值器影响了equals和hashCode的结果,当我们将第一个元素放入hashset后被存储在它hashCode值指向的散列桶(bucket)位置,之后变异ls导致对象的hashCode变了,但是hashSet没有更新其在bucket的位置,查找时在新hashCode的位置找不到元素。因此,当equals和hashCode结果可能被可变影响时,哈希表的RI会遭到破坏。如果某个可变对象包含在Set中,当其发生改变后集合类的行为是不确定的。在JDK中,不同的可变类使用不同的等价性标准(例如collections使用观察等价性,StringBuilder使用行为等价性)。

举例:

        Date的equals的规约是getTime返回相同long值(观察等价性)。

        List的规约是两个List同长且各个元素equals(观察等价性)。

        StringBuilder的equals继承自Object类(行为等价性)。

对可变类型实现行为等价性即可,即只有指向相同内存地址的对象才是相等的,所以对于可变类型无需重写equals和hashCode,直接继承Objects即可。如果一定要判断两个可变对象观察等价性,最好定义一个新方法。

equals和hashCode综述:对于不可变类型,equals应比较抽象值(检查行为等价性),而hashCode将抽象数据转化为整型,所以必须重写这两个函数;对于可变类型,equals只起到==作用(检查行为等价性),hashCode()将引用转化为整型,所以不能重写它们,而且应该仅仅使用由Object定义的默认操作。具体情形如下:

                

五、克隆

克隆对象:创建并返回对象的副本,但是这个副本取决于对象类,不过一般满足:

x.clo ne()!=x。

x.clone.getClass()==x.getClass()。

x.clone().equals(x)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值