一、多态的基础语法
1、学习多态基础语法之前,我们需要普及两个概念:
第一个:向上转型
子 ---> 父 (自动类型转换)
第二个:向下转型
父 ---> 子 (强制类型转换)
注意:
java中允许向上转型 也允许向下转型
(重点):无论向上转型,还是向下转型,两种类型之间必须有继承关系,没有继承关系编译器报错
什么是多态?
多种形态,多种状态
案例1:
父类:
// 父类
public class Animal
{
public void move(){
System.out.println("动物会移动~");
}
}
子类:
public class Bird extends Animal
{
// 方法重写
public void move(){
System.out.println("鸟儿在天上飞~");
}
}
子类:
// 子类
public class Cat extends Animal
{
// 方法重写
public void move(){
System.out.println("小猫走猫步~");
}
}
定义一个Dog类:
public class Dog
{
public void move(){
System.out.println("小狗...");
}
}
程序测试:
public class OOTest01
{
public static void main(String[] args){
Animal a =new Animal();
a.move(); // 动物会移动~
Cat c =new Cat();
c.move(); // 小猫走猫步~
Bird b =new Bird();
b.move(); // 鸟儿在天上飞~
// a1就是父类型的引用
// new Cat() 是一个子类型的对象
// 允许a1这个父类型引用指向子类型的对象
Animal a1 =new Cat(); // 相当于把new Cat()这个对象的内存地址赋给了父类的引用a1
a1.move(); // 小猫走猫步~
Animal a2 =new Bird(); // [子--->父] 向上转型
// 调用a2.move()方法
a2.move(); // 鸟儿在天上飞~
// 实际上a2调用的move()方法是new对象的move()方法 不过a2.move()之前
// 编译器知道a2是Animal类型,先去Animal类中找move()方法进行绑定,没有的话报错
/*
分析:a2.move();
java程序分为编译阶段和运行阶段
先来分析编译阶段:
对于编译器来说,编译器只知道a2的类型是Animal,
所以编译器在检查语法的时候,会去Animal.class;
字节码文件中找move()方法,找到了,绑定上move ()
方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
再来分析运行阶段:
运行阶段的时候,实际上在堆内存中创建的java对象是
Cat对象,所以move的时候,真正参与move的对象是一只猫, :
所以运行阶段会动态执行Cat对象的move()方法。这个过程
属于运行阶段绑定。( 运行阶段绑定属于动态绑定。)
*/
/*
当子类Dog没有继承父类时,两者类型之间不存在转换
错误: 不兼容的类型: Dog无法转换为Animal
Animal a3 =new Dog();
a3.move();
*/
}
}
运行结果:
知识点1:
Animal a2 =new Bird(); // [子--->父]
a2.move(); // 鸟儿在天上飞~
// 调用a2.move()方法
分析:a2.move();
java程序分为编译阶段和运行阶段
先来分析编译阶段:
对于编译器来说,编译器只知道a2的类型是Animal,
所以编译器在检查语法的时候,会去Animal.class;
字节码文件中找move()方法,找到了,绑定上move ()
方法,编译通过,静态绑定成功。(编译阶段属于静态绑定。)
再来分析运行阶段:
运行阶段的时候,实际上在堆内存中创建的java对象是
Cat对象,所以move的时候,真正参与move的对象是一只猫, :
所以运行阶段会动态执行Cat对象的move()方法。这个过程
属于运行阶段绑定。( 运行阶段绑定属于动态绑定。)
当为子类Cat定义一个独有的抓老鼠方法时:
// 父类
public class Animal
{
public void move(){
System.out.println("动物会移动~");
}
}
// 子类
public class Cat extends Animal
{
// 方法重写
public void move(){
System.out.println("小猫走猫步~");
}
// 为猫写一个独有的抓老鼠的方法
public void catchMouse(){
System.out.println("猫会抓老鼠~");
}
}
代码测试:
public class OOTest01
{
public static void main(String[] args){
Animal a =new Animal();
a.move(); // 动物会移动~
//分析这个程序能否编译和运行呢?
//分析程序一定要分析编译阶段的静态绑定和运行阶段的动态绑定。
//只有编译通过的代码才能运行。没有编译,根本轮不到运行。
//错误:找不到符号
//为什么?:因为在c.catchMouse();时编译器只知道c的类型是Animal,
//去Animal.class文件中找catchMouse()方法
//结果没有找到,所以静态绑定失败,编译报错。无法运行。 (语法不合法)
Animal c =new Cat();
c.catchMouse(); // 编译时报错
}
}
运行结果:
问题1:假设代码写到了这里,我非要调用catchMouse()方法怎么办?
就必须使用"向下转型"了。(强制类型转换) 【不重新开辟一个Cat对象 然后调用 catchMouse()方法的原因是 : 节省内存空间】
问题2:什么是向下转型???【运用方法如下所示】
当子类当中相比父类有独特的方法并且需要访问的时候
以下这行代码为啥没报错????
因为a5是Animal类型,转成Cat, Animal和Cat之间存在继承关系。所以没报错。
public class OOTest01
{
public static void main(String[] args){
Animal a =new Animal();
a.move(); // 动物会移动~
// 假设非要调用catchMouse()方法 就用到了"向下转型"
// 以下这行代码为啥没报错????
// 因为c是Animal类型,转成Cat, Animal和Cat之间存在继承关系。所以没报错。
Animal c =new Cat();
Cat x =(Cat)c; // 相当于Cat x =new Cat();
x.catchMouse();// 左边能找到catchMouse()方法进行编译绑定 右面Cat对象运行访问方法
}
}
问题3:那么"向下转型(强制转换类型)"有什么风险呢?
如下所示(编译通过 运行出现错误):
public class OOTest01
{
public static void main(String[] args){
Animal a =new Animal();
a.move(); // 动物会移动~
// 向下转型
Animal c =new Cat();
Cat x =(Cat)c; // 相当于Cat x =new Cat();
x.catchMouse();// 左边能找到catchMouse()方法进行编译绑定 右面Cat对象运行访问方法
/*
分析以下程序,编译报错还是运行报错? ? ?
编译器检测到b这个引用是Animal类型,当(Cat)b的时候
Animal和Cat之间存在继承关系,所以可以向下转型。
编译没毛病。
运行阶段,堆内存实际创建的对象是: Bird对象。
在实际运行过程中,拿着Bird对象转换成Cat对象
就不行了。因为Bird和Cat之间没有继承关系。
运行是出现异常,这个异常和空指针异常一样非常重要,也非常经典:
java . lang . ClassCastException:类型转换异常。
java. lang . NullPointerException:空指针异常。这个也非常重要。
*/
Animal b =new Bird();
Cat y =(Cat)b;
y.catchMouse();
}
}
运行结果:
问题4: 怎么避免ClassCastException异常的发生? ? ?
新的内容,运算符:
instanceof
第一:instanceof可以在运行阶段动态判断"引用"指向的对象的类型。
第二: instanceof的语法:
(引用 instanceof 类型)
第三: instanceof运算符的运算结果只能是: true/false
第四:
假设(c instanceof Cat) 为true表示:
c引用指向的堆内存中的java对象是一个cat
假设(c instanceof Cat) 为false表示:
c引用指向的堆内存中的java对象是一个cat
代码演示如下:【以后的向下转型一定要用instanceof判断】
public class OOTest01
{
public static void main(String[] args){
Animal a =new Animal();
a.move(); // 动物会移动~
// 向下转型
Animal c =new Cat();
Cat x =(Cat)c; // 相当于Cat x =new Cat();
x.catchMouse();// 左边能找到catchMouse()方法进行编译绑定 右面Cat对象运行访问方法
/*
if (c instanceof Cat)// true
{
Cat x =(Cat)c;
x.catchMouse(); // 猫会抓老鼠~
}
*/
// 解决ClassCastException异常的发生
Animal b =new Bird();
if (b instanceof Cat)// 如果b是一只Cat (false)
{
Cat y =(Cat)b;// 再进行强制类型转换
y.catchMouse();
}
}
}
思考5:为什么要instanceof判断
子类:
public class Bird extends Animal
{
// 方法重写
public void move(){
System.out.println("鸟儿在天上飞~");
}
// 定义一个唱歌方法
public void sing(){
System.out.println("鸟儿在唱歌~");
}
}
子类 :
// 子类
public class Cat extends Animal
{
// 方法重写
public void move(){
System.out.println("小猫走猫步~");
}
// 为猫写一个独有的抓老鼠的方法
public void catchMouse(){
System.out.println("猫会抓老鼠~");
}
}
代码测试:
public class OOTest02
{
public static void main(String[] args){
Animal x =new Cat();
Animal y =new Bird();
// 获取子类当中独有的各自方法【向下转型】
if (x instanceof Cat)
{
Cat c =(Cat)x;
c.catchMouse();
}
if (y instanceof Cat)
{
Cat c =(Cat)y;
c.catchMouse();
}else if (y instanceof Bird)
{
Bird b =(Bird)y;
b.sing();
}
}
}
代码测试结果:
这个代码的疑问?
肉眼可以观察到底层到底是newBird()还是newCat()!!
我们为什么还要进行instanceof的判断呢! ! ! !
原因是:
你以后可能肉眼看不到。|
接下来写一个用户A与用户B之间的交互:
用户A可以当作用户端
用户A只需要调用别人(用户B)写好的类中的方法即可获得自己想要的数据
用户A:
public class PersonTest
{
public static void main(String[] args){
AnimalTest a =new AnimalTest();
a.test(new Dog());
}
}
用户B:
public class AnimalTest
{
/*
public static void main(String[] args){
Animal a =new Dog(); new Dog();相当于PeopleA传过来的局部变量
a.move();
}
*/
// test 这个方法是PeopleB负责编写
// 这个test()方法的参数是一个Animal[父类]
// 这里不知道Animal a 会接收到用户PeopleA传过来的是什么 有可能是new Cat()、new Dog()、new Bird()...
// 假如当用户A传入new Dog() 时候 test()形参相当于 Animal a =new Dog();
// 我们需要用instanceof来判断一下 因为我们不知道用户A会传什么动物进来,强转的话有可能出现报错异常
public void test(Animal a){ // 这里相当于拿到 Animal a =new Dog();
if (a instanceof Dog)
{
Dog d =(Dog)a;
d.eat();
}
// 因为我们不知道用户A到底会传什么动物进来所以我们应该把所有动物类都进行一下判断
else if (a instanceof Cat)
{
Cat c =(Cat)a;
c.catchMouse();
}
}
}
父类:
public class Animal
{
public void move(){
System.out.println("动物会移动");
}
}
子类:
public class Dog extends Animal
{
public void eat(){
System.out.println("狗会吃");
}
}
子类:
public class Cat extends Animal
{
public void catchMouse(){
System.out.println("猫会抓老鼠");
}
}
测试结果:
【重点】多态在开发中的作用
多态练习题
编写程序模拟"主人"喂养"宠物"的场景:
提示1:
主人类:Master.
宠物类: Pet
宠物类子类: Dog- Cat、 YingWu
提示2:
主人应该有喂养的方法: feed ()
宠物应该有吃的方法: eat()
只要主人喂宠物,宠物就吃。【!!!】
要求:主人类中只提供一个喂养方法feed(),要求达到可以喂养各种类型的宠物。
编写测试程序:
创建主人对象
创建各种宠物对象
调用主人的喂养方法feed(),喂养不同的宠物,观察执行结果。
通过该案例,理解多态在开发中的作用。
重要提示: feed方法是否需要一个参数,参数选什么类型! ! !
当没有运用多态时的程序代码如下:
猫类:
public class Cat
{
// 吃的方法[实例方法]
public void eat(){
System.out.println("小猫咪喜欢吃鱼,吃的好香");
}
}
狗类:
public class Dog
{
// 吃的方法
public void eat(){
System.out.println("狗狗喜欢啃骨头,吃的好香");
}
}
主人类:
public class Master
{
// 喂狗狗吃的方法[实例方法]
public void feed(Dog d){ //当主人喂的时候这里相当于Dog d =new Dog();
d.eat(); // 调用Dog对象吃的方法
}
// 喂猫吃的方法[实例方法]
public void feed(Cat c){ // 只要主人喂 动物就吃
c.eat();
}
}
测试程序:
// 测试程序
public class OOTest01
{
public static void main(String[] args){
// 创建主人对象[实例方法需要先new对象]
Master jun =new Master();
// 创建狗狗对象[同理 实例方法需要先new对象]
Dog zangAo =new Dog();
// 主人喂
jun.feed(zangAo);
// 创建猫对象
Cat xiaoBai =new Cat();
// 主人喂
jun.feed(new Cat());
}
}
当运用多态时的代码如下所示:
父类 Pet:
// 父类
public class Pet
{
// 吃的方法
public void eat(){
}
}
子类 Dog:
public class Dog extends Pet
{
// 吃的方法
public void eat(){
System.out.println("狗狗喜欢啃骨头,吃的好香");
}
}
子类 Cat:
public class Cat extends Pet
{
// 吃的方法[实例方法]
public void eat(){
System.out.println("小猫咪喜欢吃鱼,吃的好香");
}
}
新添加一个子类 Pig:
public class Pig extends Pet
{
public void eat(){
System.out.println("猪喜欢吃饲料,吃的好香");
}
}
主人类 Master:
public class Master
{
/*
// 喂狗狗吃的方法[实例方法]
public void feed(Dog d){
d.eat();
}
// 喂猫吃的方法
public void feed(Cat c){
c.eat();
}
*/
// 假设主人又想喂养其他的动物了
// 那么由于新的需求产生,导致我们"不得不"修改Master这个类的代码
// 那么当我们在修改的同时可能会把以前我们本身比较稳定的代码变的不稳定[稳定性变差]
// 问题: 我们能不能让Master这个类的代码不再修改了
// 那么我们就运用到了:多态机制
public void feed(Pet pet){ // 如果主人喂的猫: 那么会成:Pet pet =new Cat(); 则成了多态【向上转型】
// 编译的时候:编译器发现pet是Pet类,会去Pet类中找eat()方法,结果找到了,静态绑定eat()方法 编译通过
// 运行的时候:底层实际的对象是什么,就自动调用该实际对象对应的eat()方法上
pet.eat();
}
}
测试程序:
// 测试程序
public class OOTest01
{
public static void main(String[] args){
// 创建主人对象[实例方法需要先new对象]
Master jun =new Master();
// 创建狗狗对象
Dog zangAo =new Dog();
// 主人喂
jun.feed(zangAo);
// 创建猫对象
Cat xiaoBai =new Cat();
// 主人喂
jun.feed(xiaoBai);
// 当新添加一个动物类时[不再动Master类中的数据]
Pig zhu =new Pig();
// 主人喂
jun.feed(zhu);
}
}
运行结果:
总结多态:
多态在开发中的应用:
多态在开发中的作用是:
降低程序的耦合度,提高程序的扩展力public class Master(){
public void feed(Dog d){
}
public void feed(Cat c){
}
}
以上代码中表示:Master和Dog以及Cat的关系都和紧密(耦合度高),导致扩展力差public class Master(){
public void feed(Pet pet){ //Pet pet = new Dog();
pet.eat();}
}
以上的代码表示:Master和Dog以及Cat的关系就脱离了,Master关注的是Pet类(然后Dog和Cat继承了Pet[向上转型])
这样耦合度就降低了注意: 有了重写才会有多态,因为如果子类没有重写父类的方法,如下[
如果没有重写那么 绑定的时候会绑定成功 但是运行的时候在Cat类中找不到move方法
]
如:
public class Animal(){
public void move(){}
}
public class Cat(){
public void Move(){
}
}
这里提到了一个软件开发原则:
七大原则最基本的原则:OCP(对扩展力开放,对修改关闭)
目的是:降低程序的耦合度,提高程序的扩展力