JavaSE学习:第十一章、多态

1、定义:

多态(Polymorphism)属于面向对象三大特征之一,它的前提是封装形成独立体,独立体之间存在继承关系,从而产生多态机制。多态是同一个行为具有多个不同表现形式或形态的能力。

现实中,比如我们按下 F1 键这个动作:
● 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
● 如果当前在 Word 下弹出的就是 Word 帮助;
● 如果当前在 Windows 下弹出的就是 Windows 帮助和支持。
多态就是“同一个行为”发生在“不同的对象上”会产生不同的效果。

2、多态重点:

1、一个对象的编译类型和运行类型可以不一致。
2、编译类型再定义对象时就确定了,不可更改;运行类型可以变化。
3、编译类型看 = 号左边,运行类型看 = 右边。

3、转型:前提是有继承关系。

在java中允许这样的两种语法出现:
1、一种是向上转型(Upcasting):向上转型是指子类型转换为父类型,又被称为自动类型转换。
2、一种是向下转型(Downcasting):向下转型是指父类型转换为子类型,又被称为强制类型转换。
在这里插入图片描述

4、运行和编译:

动力节点:多态

Animal a1 = new Cat();
a1.move(); 

java程序包括编译和运行两个阶段,分析java程序一定要先分析编译阶段,然后再分析运行阶段,
编译阶段:
编译器只知道a1变量的数据类型是Animal,那么此时编译器会去Animal.class字节码中查找move()方法,发现Animal.class字节码中存在move()方法,然后将该move()方法绑定到a1引用上,编译通过了,这个过程我们可以理解为“静态绑定”阶段完成了。
运行阶段:
紧接着程序开始运行,进入运行阶段,在运行的时候实际上在堆内存中new的对象是Cat类型,也就是说真正在move移动的时候,是Cat猫对象在移动,所以运行的时候就会自动执行Cat类当中的move()方法,这个过程可以称为“动态绑定”。
但无论是什么时候,必须先“静态绑定”成功之后才能进入“动态绑定”阶段。

Animal a = new Cat();
a.catchMouse();   // 报错

为什么会编译报错呢?
那是因为“Animal a = new Cat();”在编译的时候,编译器只知道a变量的数据类型是Animal,也就是说它只会去Animal.class字节码中查找catchMouse()方法,结果没找到,自然“静态绑定”就失败了,编译没有通过。就像以上描述的错误信息一样:在类型为Animal的变量a中找不到方法catchMouse()。
因此:向上转型不能调用子类的特有成员。因为在编译阶段,能调用哪些成员,是由编译类型来决定的。

4.1ClassCastException:类转换异常:

public static void main(String[] args) {
		Animal a = new Bird();
		Cat c = (Cat)a;
	}

以上代码可以编译通过吗?
答案是可以的,为什么呢?那是因为编译器只知道a变量是Animal类型,Animal类和Cat类之间存在继承关系,所以可以进行向下转型(前面提到过,只要两种类型之间存在继承关系,就可以进行向上或向下转型),语法上没有错误,所以编译通过了。但是运行的时候会出问题。

以上的异常是很常见的ClassCastException,翻译为类型转换异常,这种异常通常出现在向下转型的操作过程当中,当类型不兼容的情况下进行转型出现的异常,之所以出现此异常是因为在程序运行阶段a引用指向的对象是一只小鸟,然后我们要将一只小鸟转换成一只猫,这显然是不合理的,因为小鸟和猫之间是没有继承关系的。为了避免这种异常的发生,建议在进行向下转型之前进行运行期类型判断,这就需要我们学习一个运算符了,它就是instanceof。

instanceof运算符的运算结果是布尔类型,可能是true,也可能是false,假设(c instanceof Cat)结果是true则表示在运行阶段c引用指向的对象是Cat类型,如果结果是false则表示在运行阶段c引用指向的对象不是Cat类型。有了instanceof运算符,向下转型就可以这样写了:

		Animal a = new Bird();
		if(a instanceof Cat){
			Cat c = (Cat)a;
			c.catchMouse();
		}

以上程序运行之后不再发生异常,并且什么也没有输出,那是因为if语句的条件并没有成立,因为在运行阶段a引用指向的对象不是Cat类型,所以(a instanceof Cat)是false,自然就不会进行向下转型了,也不会出现ClassCastException异常了。在实际开发中,java中有这样一条默认的规范需要大家记住:在进行任何向下转型的操作之前,要使用instanceof进行判断,这是一个很好的编程习惯。就像下面的代码:

public static void main(String[] args) {
		Animal a = new Bird();
		if(a instanceof Cat){
			Cat c = (Cat)a;
			c.catchMouse();
		}else if(a instanceof Bird){
			Bird b = (Bird)a;
			b.sing();
		}
	}

5、向上转型:父类的引用指向子类的对象。

当我们需要多个同父的对象调用某个方法时,通过向上转换后,则可以确定参数的统一.方便程序设计。

向上转型:父类的引用指向子类的对象。

Animal animal = new Cat()

注意点:

1、可以调用父类的所有成员,但是要遵守访问权限。
2、不能调用子类的特有成员。因为在编译阶段,能调用哪些成员,是由编译类型来决定的。
即由 animal 来决定。
3、但最终的运行效果看子类的具体实现。即调用方法时,按照从子类开始查找的规则,子类有同名方法,调用子类,子类没有,往上找。

向上转型不能调用子类的方法,编译不通过,但是运行的时候如果子类有和父类同名的方法,那么则运行子类重写的父类方法。

PS:向上转型时,父类只能调用父类方法或者子类覆写后的方法,而子类中的单独方法则是无法调用的.

6、向下转型: 将原来指向子类对象的父类引用(animal),转换为指向子类对象的子类引用(cat)。

类似于基本数据类型的强转

(Son)father;   
(int)1.1;  // 类似于基本数据类型的强转。

在java中,向下转型则是为了,通过父类强制转换为子类,从而来调用子类独有的方法(向下转型,在工程中很少用到)。

为了保证向下转型的顺利完成,在java中提供了一个关键字:instanceof,通过instanceof可以判断某对象是否是某类的实例,如果是则返回true,否则为false。

向下转型:子类类型 引用名 = (子类类型) 父类引用;

Cat cat = (Cat) animal;

注意点:

1、只能强转父类的引用,不能强转父类的对象。 Cat cat = (Cat) Animal; // 错
2、要求父类的引用必须指向当前目标类的对象:就是先有向上转型才能向下转型。
3、向下转型后,可以调用子类中所有成员。

如果要调用子类的特有方法,那么就要向下转型。

instanceOf:比较操作符:

用于判断对象的运行类型是否是某个类或者是某个类的子类。

package com.hsf.extends_;

public class Instance_ {
    public static void main(String[] args) {
        BB bb = new BB();
        System.out.println(bb instanceof BB);  // true
        System.out.println(bb instanceof AA);  // true

        /*
        * aa的运行类型是BB,因此
        * (aa instanceof AA):BB是AA的子类型?   true
        * (aa instanceof BB): BB是BB的类型吗?  true
        * */
        AA aa = new BB();
        System.out.println(aa instanceof AA);  // true
        System.out.println(aa instanceof BB);  // true
    }
}

class AA {}

class BB extends AA {}

运行和编译类型注意点:

1、访问属性看编译类型,访问方法看运行类型。

package com.hsf.extends_.text;

public class Test {
    public static void main(String[] args) {
        Son son = new Son();
        Father father = son;
        /*
        * 1、访问属性看编译类型:
        * son的编译类型是Son,因此访问count去Son里面找, son的count =20。
        * father的编译类型是Father,因此访问count去Father里面找,father的count =10。
        * */
        System.out.println(son.count);  // 20
        System.out.println(father.count); // 10
        /*
        * 2、访问方法看运行类型:
        * father运行类型是Son,因此去Son里面找M1方法。
        * */
        father.M1();  // 我是子类M1方法
        /*
        * 3、==:是比较地址值:只要不是new,那么两个地址值就一样,如果:
        * Father father = new Son();那么就是FALSE,因为new了一个新空间。
        * */
        System.out.println(son == father);  // true
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值