继承之上溯造型和下溯造型

前言


我们在平时的开发编码中,都会用到上溯造型和下溯造型,只是我们并不知道他的官方叫法而已, 上溯造型继承多态,以及动态绑定的关系很密切 ,关于这几个概念后面会有涉及到他们的概念。


继承和合成

继承:它的本质就是为了使得代码复用(可以基于已经存在的类构造一个新类。继承已经存在的类就可以复用这些类的方法和域。在此基础上,可以添加新的方法和域,从而扩充了类的功能。)

合成:在新类里创建原有的对象称为合成。这种方式可以重复利用现有的代码而不更改它的形式。


-----继承
关键字extends表明新类派生于一个已经存在的类。已存在的类称为父类或基类,新类称为子类或派生类。例如:
class Dog extends Animal {
}
类Dog继承了Animal,Animal类称为父类或基类,Dog类称为子类或派生类。
---合成
合成比较简单,就是在一个类中创建一个已经存在的类。
class Dog {
Animal animal;
}

上溯造型

这个术语缘于继承关系图的传统画法:将基类至于顶部,而向下发展的就是派生类(子类),发送给父类的消息亦可发给衍生类,父类包含子类。假设把子类赋值给父类,这个过程就称之为上溯造型— 这个时候只能够调用父类父类的方法,子类特有的方法不能够调用,子类变窄


//父类
abstract class Animal {
public abstract void speak();
public void eat(){
}
}
//子类特有方法
interface DoorGod {
void guard();
}
//Dog 子类和 Cat 子类
class Cat extends Animal {
@Override
public void eat() {
try {
Thread.sleep( 1000 );
} catch (InterruptedException e) {
e.printStackTrace();
}
// super .eat();
System.out.println("cat eat");
}
@Override
public void speak() {
System.out.println( " 喵喵 " );
}
}
class Dog extends Animal implements DoorGod{
@Override
public void speak() {
System.out.println( " 汪汪 " );
}
public void guard() {
while ( true ){
System.out.println( " 汪汪 " );
}
}
}

//测试方法
public class TestShangSu
{
public static void upcasting(Animal animal){
animal.speak();
animal.eat();
}
@Test
public void test1(){
Animal dog1 = new Dog();
upcasting(dog1);
Animal cat = new Cat();
upcasting(cat);
}
}
//输出
汪汪
喵喵
cat eat

这个时候为什么输出是:子类覆盖父类的方法,而不是父类的方法,这个涉及到动态绑定。后面再讲


由于upcasting(Animal animal)方法的参数是 Animal类型的,因此如果传入的参数是 Animal的子类,传入的参数就会被转换成父类Animal类型,这样你创建的Dog对象能使用的方法只是Animal中的签名方法;也就是说,在上溯的过程中,Dog的接口变窄了,它本身的一些方法(例如实现了 DoorGod的guard方法)就不可见了。如果你想使用Dog中存在而Animal中不存在的方法(比如guard方法),编译时不能通过的。由此可见,上溯造型是安全的类型转换。

如果Dog在上溯造型过程中想使用 DoorGod的guard方法,那么需要配合下溯造型和安全检查,来进行强制转换,讲Animal 下溯为 Dog类型。

注意的是:下溯是不安全的,由父类转化为子类,所以需要加上判断。


下溯造型

将基类转化为衍生类,不安全的操作,可能会引发ClassCastException。


上面的例子只需要加上这一层判断即可
public static void upcasting(Animal animal){
if( animal instanceof Dog ){//下溯造型判断
Dog dog = (Dog) animal;
dog.guard();
}
animal.speak();
animal.eat();
}

我们在使用注解实现请求方法的登录控制 登录拦截器里面有段关键代码使用的就是下溯造型

下溯整形.png


为什么使用上溯和下溯造型

上面的例子我们发现,关键的代码是upcasting方法,为什么在调用upcasting方法时要有意忽略调用它的对象类型呢?如果让upcasting方法简单地获取Dog句柄似乎更加直观易懂,但是那样会使衍生自Animal类的每一个新类都要实现专属自己的upcasting方法:例如Cat会实现一个重复的upcasting(Cat cat )这样的方法。

实现多态的好处和代码复利用。


动态绑定

在上面的upcasting方法,测试例子输出的是子类的方法,而非是父类的方法,但是我们使用的是父类去调用这些方法,为什么输出不是父类的呢?

upcasting它接收的是Animal句柄,当执行speak和eat方法时时,它是如何知道Animal句柄指向的是一个Dog对象而不是Cat对象呢?编译器是无从得知的,这涉及到接下来要说明的绑定问题。


Java实现了一种方法调用机制,可在运行期间判断对象的类型,然后调用相应的方法,这种在运行期间进行,以对象的类型为基础的绑定称为动态绑定。除非一个方法被声明为final,Java中的所有方法都是动态绑定的。

静态方法的绑定

他跟普通的方法不同,子类和父类方法都是静态的,子类如果去掉父类编译会错误


package Test;
class Person {
static void eat() {
System.out.println("Person.eat()");
}
static void speak() {
System.out.println("Person.speak()");
}
}
class Boy extends Person {
static void eat() {
System.out.println("Boy.eat()");
}
static void speak() {
System.out.println("Boy.speak()");
}
}
class Girl extends Person {
static void eat() {
System.out.println("Girl.eat()");
}
static void speak() {
System.out.println("Girl.speak()");
}
}
public class Persons {
public static Person randPerson() {
switch ((int)(Math.random() * 2)) {
default:
case 0:
return new Boy();
case 1:
return new Girl();
}
}
public static void main(String[] args) {
Person[] p = new Person[4];
for (int i = 0; i < p.length; i++) {
p[i] = randPerson(); // 随机生成Boy或Girl
}
for (int i = 0; i < p.length; i++) {
p[i].eat();
}
}
}
//输出
Person.eat()
Person.eat()
Person.eat()
Person.eat()

对于静态方法而言,不管父类引用指向的什么子类对象,调用的都是父类的方法。


总结

上溯造型和动态绑定实际上就是多态的体现,下溯造型是为了解决因为上溯而导致衍生类功能变小的问题,继承则是上溯和下溯以及动态编译的基础。

如果你感觉文章对你又些许感悟,你可以支持我!!
-------------本文结束感谢您的阅读-------------