类型强转
有时候需要将某个类的对象引用转换成另一个类的对象引用,就要进行类型强转。进行类型强转的唯一原因是:要暂时忽略对象的实际类型之后使用对象的全部功能。
首先要明白一点,就是父类型的变量可以引用子类型的对象,而不需要强制类型转换。需要进行强制类型转换的,都是试图让一个父类型的引用转换成子类型的引用,从而能够使用子类型的所有特性。明白这一点很重要,且看下面的代码:
class A {
public void say() {
System.out.println("I am A");
}
}
class B extends A{
public void say() {
System.out.println("I am B");
}
public void greet() {
System.out.println("Hello world.");
}
}
// 下面是测试
B b = new B();
b.say();
b.greet();
A a = new A();
a.say();
a.greet(); // 报错,无法通过编译
A ab = new B();
ab.say();
ab.greet(); //报错,无法通过编译
if (ab instanceof B) ((B) ab).greet(); // 没毛病
其实根本原因是java的一个特性:动态绑定,就是一个变量它到底引用了一个什么类型,是它定义时的类型还是其子类,要等到运行才知道。在编译期,一个引用能够使用哪些特性,是由定义该引用的类型所拥有的特性所决定的。比较拗口,。如上面的代码中,对象ab
(准确来讲ab
是一个引用)在编译期,编译期只知道它是A
类型的,隐藏它只能调用A
类型的方法,而无法调用其子类型B
的greet()
方法。
那么有的时候我又想看看ab
在运行期间到底指向什么类型,从而在写代码的时候去调用其真正指向的类型的特性,又该怎么办呢?这个时候就要用到强制类型转换了。
将一个值存入一个变量的时候,编译期会检查你是否承诺过多,如果将一个子类的引用赋值给一个超类变量,这是没问题的,但是如果将一个超类的引用赋值给一个子类变量时,就承诺过多了。必须进行强制类型转换才能通过运行时的检查。
如果在运行的时候,试图在继承链上进行向下的强制类型转换,会发生什么呢?比如下面这样:
A a = new A(); // case 1
B b = (B) a; //报错,ClassCastException
A ab = new B(); // case 2
B bb = (B) ab; // OK! No problem!
重写equals方法
在Object
类里面定义了一个equals()
方法,它比较的是两个对象的地址是否相等。如果一个类没有重写equals()
方法,那么就自动调用Object
类里的这个方法。但是有时候我们要比较两个类的状态,从而在逻辑上决定这两个类是否相等,就需要重写该方法。假如现在向B
类里面添加两个属性:
class B {
int id;
String name;
}
如果两个对象的id
和name
都相等,我就认为这两个对象相等。
那么实现的逻辑就应该如下:
@Override
public boolean equals(Object o) {
if (o == null) return false; // 这个容易理解
if (o == this) return true; // 对象地址相等,那肯定相等
if (o instanceof B) { // 为什么要先判断一下,上文有讲
B other = (B) o;
return this.id == other.id && this.name.equals(other.name);
}
return false; // 如果B实际指向的对象的类型和本类型不一致,那在这里就不应该判断为相等的对象
}
重写hashCode()方法
首先要明白该方法存在的意义是什么。在Object
类里面有相关的描述,该方法主要是为哈希表
服务的。哈希表在判断两个对象是否相等的时候,不仅仅调用其equals()
方法,而是首先查看这两个对象的hashcode
是否相等,相关内容可以查看源码,比如HashMap
的contains()
。
如果两个对象通过其equals方法判定为相等,那么这两个对象的hashcode也应该要相等
所以,如果重写了equals()
方法,如果涉及到哈希表的使用,那么请一定要重写hashCode()
方法,并且在生产哈希码的时候要用上equals()
方法中涉及到的所有特性。比如上面的例子中,用到了id
和name
两个特性,那么重写hashCode()
方法时,就应该用上这两个特性。如果说重写的equals()
方法里只用了id
这一个属性,那么重写hashCode()
的时候就可以只用id
属性而没必要使用name
属性了。