本文主要包括以下几个方面:
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)。