1 多态的基础语法
1.1 向上转型和向下转型的概念
向上转型:子 ---> 父(upcasting)
又被成为自动类型转换:Animal a = new Cat(); 需要添加强制类型转换符。
向下转型: 父 ---> 子(downcasting)
又被成为强制类型转换:Cat c = (Cat)a; 需要添加强制类型转换符。
什么时候需要向下转型?
需要调用或者执行子类对象中特有的方法。
必须进行向下转型,才可以调用。
向下转型有风险吗?
容易出现ClassCastException(类型转换异常)
怎么避免这个风险?
instanceof运算符,可以在程序运行阶段动态的判断某个引用指向的对象是否为某一种类型。
养成好习惯,向下转型之前一定要使用instanceof运算符进行判断。
不管是向上转型还是向下转型,首先他们之间必须有继承,这样编译器就不会报错。
1.2 什么是多态
多种形态,多种状态,编译和运行有两个不同的状态。
编译期叫做静态绑定。
运行期叫做动态绑定。
Animal a = new Cat();
// 编译的时候编译器发现a的类型是Animal,所以编译器回去Animal类中找move()方法
// 找到了,绑定,编译通过。但是运行的时候和底层堆内存当中的实际对象有关。
// 真正执行的到时候会自动调用“堆内存中真是对象”的相关对象。
多态的典型代码:父类型的引用指向子类型的对象。(java中允许这样写代码!!!)
1.3 什么时候必须向下转型?
调用子类对象上特有的方法时。
代码1:父类(动物类)
// 动物类:父类 public class Animal{ // 移动的方法 public void move(){ System.out.println("动物在移动!!!"); } }
代码2:猫类(子类)
// 猫类,子类 public class Cat extends Animal{ // 对move方法进行重写 public void move(){ System.out.println("猫儿在走猫步!!!"); } // 猫除了move之外,应该有自己特有的行为。例如抓老鼠 // 这个行为是子类型对象特有的方法。 public void catchMouse(){ System.out.println("猫正在抓老鼠!!!!!"); } }
代码3:鸟类(子类)
// 鸟类、子类 public class Bird extends Animal{ // 对move方法进行重写 public void move(){ System.out.println("鸟儿在飞翔!!!"); } public void sing(){ System.out.println("鸟儿在歌唱"); } }
代码4:多态的测试案例1
/* 多态的基础语法: 1 学习多态基础语法之前,我们需要普及两个概念: 第一个:向上转型 子 ---> 父(自动类型转换) 第二个:向下转型 父 ---> 子(强制类型转换,需要加强制类型转换符) 注意: java中允许向上转型,也允许向下转型。 *****(五颗星)无论是向上转型,还是向下转型, 两种类型之间必须有继承关系,没有继承关系编译报错。 以后再工作过程中,和别人聊天的时候,要专业一点,说向上转型和向下转型,不要说自动类型转换, 也不要说强制类型转换,因为自动类型转换和强制类型转换使用在基本数据类型方面的,在引用类型转换这里只有向上和向下转型。 2 多态指的是: 父类型引用指向子类型对象。 包括编译阶段和运行阶段。 编译阶段:绑定父类的方法 运行阶段:动态绑定子类型对象的方法。 多种形态。 3 java中只有“类名.”或者“引用.”才能去“.” 类名. 引用. 万变不离其宗。 4 什么时候必须要使用向下转型? 不要随便做强制类型转换。 当你需要访问的是子类对象中“特有”的方法。此时必须使用向下转型。 */ public class Test01{ public static void main(String[] args){ Animal a1 = new Animal(); a1.move();//动物在移动!!! Cat a2 = new Cat(); a2.move();// 猫儿在走猫步!!! Bird a3 = new Bird(); a3.move();// 鸟儿在飞翔!!! // 代码可以这样写吗? /* 1 Animal和Cat之间有继承关系吗?有的。 2 Animal是父类,Cat是子类。 3 Cat is a Animal,这句话能不能说通?能。 4 经过测试得知java中支持这样的一个语法: 父类型的引用允许指向子类的对象。 Animal a4 = new Cat(); a4就是父类型的引用。 new Cat()是一个子类型的对象。 允许a2这个父类型引用指向子类型的对象。 */ Animal a4 = new Cat(); Animal a5 = new Bird(); // 没有继承关系的两个类型之间存在转型吗? // 错误: 不兼容的类型: Dog无法转换为Animal // Animal a6 = new Dog(); /* 什么是多态? 多种形态,多种状态。 分析:a2.move(); java 程序分为编译阶段和运行阶段。 先来分析编译阶段: 对于编译器来说,编译器只知道a2的类型是Animal,所以编译器在检查语法的时候,会去Animal.class字节码文件中找move方法, 找到了,绑定了move()方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。) 再来分析运行阶段: 运行阶段的时候,实际上在对内存中创建的java对象的是Cat对象,所有move的时候,真正参与move的对象是一只猫。 所以运行阶段会动态执行Cat对象的move()方法。 这个过程属于运行阶段绑定。(运行阶段绑定属于动态绑定。) 多态表示多种形态: 编译的时候一种形态。 运行的时候是另一种形态。 */ //调用a2 的 move()方法 a4.move(); //调用a3 的 move()方法 a5.move(); // ------------------------------------------------------------------------------------- Animal a6 = new Cat();// 底层对象是一只猫 // 分析这个程序能否编译和运行。 // 分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。 // 只有编译通过的代码才能运行,没有编译,根本轮不到运行。 // 错误: 找不到符号 // why???因为编译器只知道a6的类型是Animal,去Animal.class文件中找catchMounse()方法 // 结果没有找到,所以静态绑定失败,编译报错。无法运行。(语法不合法) //a6.catchMouse(); // 假设代码写到这里,我非要调用catchMouse()方法怎么办? // 这个时候就必须使用“向下转型”了。(强制类型转换) // 以下这行代码为啥没报错???? // 因为a6是Animal类型。 转成Cat,Animal和Cat之间存在继承关系。所以没报错。 // 子类特有的方法才需要强制类型转换。 Cat x = (Cat) a6; x.catchMouse(); // 向下转型有风险吗? Animal a7 = new Bird();// 表面上a7是一个Animal,运行的时候实际上是一只鸟儿。 /* 分析以下程序,编译的时候和运行的时候有什么区别? 编译器检测到a7这个引用是Animal类型,而Animal和Cat之间存在继承关系,所以可以向下转型。 编译没问题。 运行阶段,堆内存实际创建的对象是:Bird对象。 在实际运行过程中,拿着Bird对象转换成Cat对象就不行了。以你为Bird和Cat之间没有继承关系。 运行时出现异常,这个异常和空指针异常一样非常重要,也非常经典: java.lang.ClassCastException(类型转换异常) java.lang.NullPointerException:空指针异常,这个也非常重要。 */ // Cat y = (Cat)a7; // y.catchMouse(); // 怎么避免ClassCastException异常的发生???? /* 新的内容,运算符: instanceof(运行阶段动态判断) 第一:instanceof可以再运行阶段动态判断引用指向的对象的类型。 第二:instanceof的语法: (引用 instanceof类型) 第三:instanceof运算符的运算结果只能是 true/false 第四:c是一个引用,c变量保存了内存地址执行了堆中的对象。 假设(c instanceof Cat)为true表示: c引用指向的堆内存中的java对象是一个Cat。 假设(c instanceof Cat)为false表示: c引用指向的堆内存中的java对象不是一个Cat。 程序员要养成一个好习惯: 任何时候,任何地点,对类型进行向下转型时,一定要使用 instanceof 运算符进行判断。(java规范中要求的) 这样可以很好地避免:java.lang.NullPointerException */ System.out.println(a7 instanceof Cat); if (a7 instanceof Cat){// 如果a7是一只猫 Cat y = (Cat)a7;// 再进行强制类型判断 y.catchMouse(); } } }
代码5:多态的测试案例2(instanceof的用法及作用)
/* 这个代码的疑问? 肉眼可以观察底层到底是new Bird() 还是new Cat()!! 我们为什么还要进行instanceof的判断呢!!! 原因是: 你以后可能肉眼看不到。 */ public class Test02{ public static void main(String[] args){ Animal a = new Bird(); Animal b = new Cat(); if(a instanceof Bird){ Bird c = (Bird)a; c.sing(); }else if(a instanceof Cat){ Cat d = (Cat)a; d.catchMouse(); } if(b instanceof Cat){ Cat d = (Cat)b; d.catchMouse(); }else if(b instanceof Bird){ Bird c = (Bird)b; c.sing(); } } }
代码5:多态的测试案例3(instanceof的用法及作用)
public class Test03{ public static void main(String[] args){ // main方法是程序员A负责编写 AnimalTest at = new AnimalTest(); at.test(new Cat()); at.test(new Bird()); } }
public class AnimalTest{ // test方法是程序员B负责编写。 // 这个test()方法的参数是一个Animal。 public void test(Animal a){ // 你写的这个方法别人会去调用。 // 别人调用的时候可能给你test()方法传过来一个Bird // 当然也可能传过来一个Cat // 对于我来说,我不知道你调用的时候给我传过来一个啥。
// 想下转型的目的就是为了调用子类中特有的方法,为了保险起见,你写instanceof的时候会规避掉你不清楚对方会给你传过一个什么子类,但是当子类中有一百个特定的方法的话,那你要写一百个instance的判断语句,没办法。
if(a instanceof Cat){ Cat c = (Cat)a; c.catchMouse(); }else if(a instanceof Bird){ Bird d = (Bird)a; d.sing(); } } }