面对对象—多态

本文深入探讨了Java中的多态性概念,包括成员访问特点、多态的好处与弊端、以及转型操作。通过实例代码展示了多态如何在程序设计中提高扩展性,并通过内存图解帮助理解向上转型和向下转型的过程。文章强调了多态在方法调用时编译看左边,运行看右边的原则,并指出多态不能直接访问子类特有的成员变量和方法,但可以通过向下转型解决这一问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1.多态

1.1多态概述

1.2多态中成员访问特点

1.3多态的好处和弊端

1.4多态中的转型

                  1.5多态转型内存图解


1.多态

1.1多态概述

同一个对象,在不同时刻表现出来的不同形态

(也就是多态研究的是对象的不同形态)

举例说明:(说一个猫的例子)

我们可以说猫是猫:猫 cat = new 猫();

我们也可以说猫是动物:动物 animal = new 猫();

(这个时候猫这个对象,其实是把它认为是一只动物了)

这里猫在不同的时刻表现出来了不同的形态,这就是多态。

程序中多态的前提和体现:

  • 有继承或者实现关系(此处实现关系还没有学习)
  • 有方法重写
  • 有父类引用指向子类对象(动物 animal = new 猫();就好比这个代码左边内容就是父类的引用,右边内容就是子类的对象,因为我们研究的是对象的多态,重点要看子类对象)

程序演示:

public class Animal {
    public void eat(){
        System.out.println("动物吃东西");
    }
}
//1.有继承/实现关系
class Cat extends Animal {//2.有方法重写,直接输入父类方法名就可以快捷输入
    @Override
    public void eat() {
        super.eat();
    }
}
/*
    多态:
        同一个对象,在不同时刻表现出来的不同形态

    举例:猫
        我们可以说猫是猫:猫 cat = new 猫();
        我们也可以说猫是动物:动物 animal = new 猫();
        这里猫在不同的时刻表现出来了不同的形态:这就是多态

    多态的前提和体现:
        有继承/实现关系
        有方法重写
        有父类引用指向子类对象
 */
class AnimalDemo {
    public static void main(String[] args) {
        //3.父类引用指向子类对象,就是创建子类对象调用父类方法
        Animal animal = new Cat();
        animal.eat();
    }

}

1.2多态中成员访问特点

先到代码中演示,再来小结:

public class Animal {
    //1.在动物类里面定义了一个成员变量和方法
    public int age = 40;

    public void eat(){
        System.out.println("动物吃东西");
    }
}

//2.猫类继承动物类
class Cat extends Animal{
    //3.在猫类里面有两个成员变量
    public int age = 20;
    public int weight = 10;

    //4.重写eat方法
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    //成员方法
    public void playGame(){
        System.out.println("猫捉迷藏");
    }
}
/*
    测试类
 */
class AnimalDemo {
    public static void main(String[] args) {
        //5.有父类引用指向子类对象
        Animal a = new Cat();

        /*6.因为我们最终new的是猫,猫类里面有两个成员变量
        通过子类对象输出成员变量看看输出的是谁
         */
        System.out.println(a.age);//40
        /*9.最终执行结果是40,说明我们通过多态的形式去访问
        成员变量其实是访问父类中的成员变量。那么也就是说
        多态的形式访问成员变量,它的编译要看左边的内容,
        它的运行也要看左边。

         */

        //7.输出weight报错了
//        System.out.println(a.weight);
        /*8.虽然说我们最终new的内存中的对象是Cat,
        但是外界看到的其实是动物这个引用,所以说
        我们通过多态的形式在访问成员变量的时候它的
        编译要看左边,首先得看父类里面有没有,它里面有
        才行,所以age没保存,它里面没有weight这个成员变量
        所以a.weight报错了*/

        //10.访问成员方法
        a.eat();//猫吃鱼
        /*12.执行得到猫吃鱼,猫里面的eat方法输出的是猫吃鱼
        动物里面的eat方法输出的是动物吃东西,由此可见,这一次
        输出的是重写后的方法中的内容,所以我们说多态的形式访问
        成员方法编译看左边,运行看右边。
         */


        /*11.访问子类方法也报错了,说明方法它也是看
        左边的Animal有没有,Animal没有 所以它这里报错
         */
//        a.playGame;
    }
}

小结:

成员变量:编译看左边,执行看左边。

成员方法:编译看左边,执行看右边。

为什么成员变量和成员方法的访问不一样呢?

因为成员方法有重写,而成员变量没有。

1.3多态的好处和弊端

先到程序中演示再小结:

public class Animal {
    //1.动物类里面有一个eat方法
    public void eat(){
        System.out.println("动物吃东西");
    }
}
//2.猫继承动物
class Cat extends Animal{
    //3.重写eat方法
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
}
//14.Dog继承Animal
class Dog extends Animal{
    //15.继承之后重写方法

    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    //36.在狗类里面添加一个方法
    public void lookDoor(){
        System.out.println("狗看门");
    }
}
/*31.还想添加一个动物,继承Animal重写eat方法,
这一次就不需要在操作类里面再写方法了,只需要到测试类里面创建对象调用方法
 */
class Pig extends Animal{
    @Override
    public void eat() {
        System.out.println("猪吃白菜");
    }
}
/*
    动物操作类
 */
class AnimalOperator {//4.在这个类里面有一个方法,参数是猫类型的
    //11.就相当于把new Cat传递给这个形参c
//    public void useAnimal(Cat c) {//12.这里形参就相当于Cat c = new Cat();
//        c.eat();//13.这里调方法最终就是调的Cat里面的eat方法。
//    }
    //16.在操作类里面添加新方法
    //19.就相当于把new Dog传递给了这个地方
//     public void useAnimal1(Dog d){//20.相当于 Dog d = new Dog();
//        d.eat();//21.这里调用方法最终就是调用的Dog里面的eat方法
//}
    /*22.假如说我要添加很多的动物,都需要新建类,这样很麻烦,还要去动物操作类里面修改方法
    因为我要添加一个新方法,最后到测试类里面来创建这个新添加的动物的对象,然后调用方法。我们
    要添加一个动物,要建一个类,这样操作没有问题。最后测试创建这个动物类的对象也没有问题,
    但是这个动物操作类我每次进来都添加一个方法,这样太麻烦了,有没有更简单的方法呢?
    有!继续演示→23.
     */

    //23.把操作类里面的方法先注释掉,然后写一个方法,方法名也叫useAnimal
    /*24.然后思考参数写什么
    不管它是猫还是狗,它们都继承了Animal,也就是说它们都可以看成是动物,
    所以在里面直接传一个Animal
     */
    //27.创建对象的Cat c = new Cat();先把new Cat传递给了Animal a
    //29.然后看测试类里面的Dog d = new Dog();,与上面同理
    public void useAnimal(Animal a){
        /*28.这一次就相当于Animal a = new Cat();
        然后编译看左边,Animal里面有一个eat方法,然后执行看右边
        Cat里面重写了eat方法,所以控制台输出猫吃鱼
         */
        //30.Animal a = new Dog,编译看左,执行看右,所以输出狗吃骨头
        a.eat();

        //37.然后到操作类的方法里面里面调方法
//        a.lookDoor();//报错
        /*38.也就是说这个多提的形式,
        它不能访问我们具体的子类所特有的功能,所以这是多态形式访问的一个弊端
         */
    }
}
/*
    测试类
 */
class AnimalDemo {
    public static void main(String[] args) {
        //6.在测试类里创建动物操作类的对象,调用方法
        AnimalOperator a = new AnimalOperator();

        //8.注意调用方法的参数是猫类型的,所以要先创建猫类型的变量
        Cat c = new Cat();//9.此处这个c就相当于new Cat
        //7.对象有了之后调用方法
        a.useAnimal(c);//10.然后把c传递给这个方法的参数

        //17.创建动物操作类对象调用方法,这里直接使用同一个对象就好了
        Dog d = new Dog();
        a.useAnimal(d);//18.这个d就是new Dog

        //25.然后执行得到的结果还是一样的 猫吃鱼和狗吃骨头,怎么理解呢?

        //32.创建猪的对象
        Pig p = new Pig();
        //33.然后把p传递给这个方法的参数
        a.useAnimal(p);//34.执行得到猪吃白菜

        /*35.所以以后要添加新的动物类只许要添加一个类,
        然后在这添加对象,然后调方法就可以了,操作类里的内容就不需要再改了
        看到这里就可以明白多态的好处:提高了程序的拓展性,它的具体体现就是
        定义方法的时候,使用父类型做参数,将来在使用的时候,使用的是具体的
        子类型,参与操作。
        接着再来说→36.
         */
    }
}

小结:

多态的好处:提高了程序的扩展性。

     具体体现:定义方法的时候,使用父类型作为参数,将来在使用的时候,使用具体的子类型参与操作。

多态的弊端:不饿能使用子类的特有功能。

1.4多态中的转型

多态的转型分类:

  • 向上转型:

从子到父

父类引用指向子类对象

  • 向下转型:

从父到子

父类引用转为子类对象

如何理解向上转型和向下转型呢?

直接程序中演示:

public class Animal {
    //1.动物类里面有一个eat方法
    public void eat(){
        System.out.println("动物吃东西");
    }
}
//2.Cat继承Animal
class Cat extends Animal{
    //3.改写eat方法
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    //4.特有方法
    public void playGame(){
        System.out.println("猫捉迷藏");
    }
}
/*
    向上转型:
        从子到父
        父类引用指向子类对象

    向下转型:
        从父到子
        父类引用转为子类对象
 */
class AnimalDemo {
    public static void main(String[] args) {
        //5.在测试类里面创建一个多态方式的对象
        //6.new Cat创建的是子类对象,Animal a是父类引用
        Animal a = new Cat();//7.父类引用指向子类对象,这个动作称为向上转型
        //8.也就是把子类对象赋值给父类引用,这个叫向上转型

        //9.调方法
        a.eat();//猫吃鱼
        /*10.再来解释一下这个操作,
        编译看左边Animal a,执行看右边new Cat();
        先看左边Animal里面有eat这个方法,所以编译通过
        右边Cat重写了eat方法,所以在控制台输出的是猫吃鱼
         */

        /*11.接下来Cat里面有一个特有方法,现在我想通过a去访问这个特有方法
        就会报错,因为编译看左边Animal里面没有这个方法,所以这里报错
         */
//        a.playGame();
        //12.而现在我就想调用这个方法应该怎么办呢?

        //13.很简单,我们创建Cat类的对象就好了
//        Cat c = new Cat();
        //14.然后调用方法
//        c.eat();//猫吃鱼
//        c.playGame();//猫捉迷藏
        /*15.这一次我们就可以调用猫的特有方法了,
        但是 我们这里创建的是猫的对象,它当然可以使用猫的特有方法了
        我现在是在内存中有两个猫的对象了,Animal a = new Cat();有一个
        Cat c = new Cat();有一个,为了一个特有的方法创建两个对象,没有必要啊
        所以我们要把第二个创造对象给改一下
         */

        //16.我现在还想去用这个特有方法可以怎么办呢?

        //17.这里就要用到向下转型
        //18.这里就是把父类对象a强转为Cat对象,然后赋值给Cat对象c
        Cat c = (Cat)a;
        //19.然后通过c来调用特有方法
        c.eat();//猫吃鱼
        c.playGame();//猫捉迷藏
        //20.这样就没有问题,这样就是通过向下转型解决了多态的弊端(不能访问子类的特有功能)。
    }
}

1.5多态转型内存图解

这里有一个代码块:

有一个测试类 类名:AnimalDemo

在类里面有一个main方法,然后咋在main方法里面

首先main方法会加载到栈内存里面。

进去之后执行代码

Animal a = new Cat();

使用多态式:父类引用指向子类对象创建了Animal父类对象a。也就是这里会把子类对象赋值给父类引用(向上转型)。

此时Animal a 就会出现在main方法中

new Cat();

首先要看一下Cat这个类它继承了Animal里面有两个方法一个重写Animal的eat方法一个Cat特有方法playGame。

然后再来看一下Animal这个方法里面有一个eat方法。

然后new Cat就会在堆内存就会开辟一个空间(假设地址值是001)

这里重点要记得Cat是继承了Animal

那么这个时候把这个对内存的地址给了Animal a

这是多态的形式,也叫向上转型:

执行此多态代码编译

的左边Animal a,执行看右边new Cat();

首先会进入到Animal类里面看看有没有eat方法,如果有就编译通过,没有就报错。然后执行的时候去到Cat类里面看看有没有重写方法Cat,有就执行此改写的方法,没有会执行父类Animal的eat方法。

发现Cat里面有eat方法然后eat方法就会加载到栈内存

然后在控制台输出猫吃鱼。

eat方法执行完毕就会从栈内存消失

然后往下执行Cat c = (Cat) a;

Cat c这个动作就会在main方法中出现。

然后再来看Cat a就会通过001指向堆内存这块内容

然后堆内存中这个001就是一个Cat,这个转换是可以进行的

然后把这个地址值001也给了这个c

(这样操作就不会在内存空间创建一个新的对象),这就是父类引用转换为子类对象(向下转型)。

然后通过c调用eat和playGame方法

c.eat();

c.playGame();

这个c代表的是一只猫然后我们就要调Cat里面的eat方法和playGame方法

然后执行完毕继续向下执行

a = new Dog();

这个Dog也继承了Animal,Dog类里面也有重写的Animal中的eat方法。

这个时候在堆内存就会再new一个空间出现new Dog(地址值是002)因为Dog继承了Animal所以它也是可以赋值给a的也是多态的形式(向上转型)。

这个时候就把Animal a的地址值改为了002

接着往下执行a.eat();

编译看左边,执行看右边,

然后通过父类对象a访问002调用Dog类中的eat方法,同理编译看右,执行看左。首先啊看父类对象a是Animal类的对象,所以就会进入到Animal类里看看有没有eat方法,如果有,就会编译通过,没有就报错。

然后执行右边 new Dog();会进入到Dog类里看看有没有写eat方法,如果有就执行,如果没有就执行Animal里的eat方法。

发现Dog类中有eat方法,这个时候Dog中的eat方法就会加载到栈内存中。

然后在控制台输出狗吃骨头

然后往下执行,方法执行完毕从栈内存消失。

接着往下执行

Cat cc = (Cat)a;

Cat cc就会出现在main方法中

(Cat) a;我们来看一下这一次的a通过002指向堆内存的002的空间地址

代表的是Dog而我们的Cat是只猫,虽然说我们的Dog和Cat都继承着Animal

但是它们相互之间是不等价的,所以它们相互之间是不能进行转换的

所以这个地方就会报错:

如果强制执行就会报错在控制台输出:

ClassCastException

到这报错便不会往下执行了。

到代码中演示一下:

public class Animal {
    public void eat(){
        System.out.println("动物吃东西");
    }
}
class Cat extends Animal{
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }
    public void playGame(){
        System.out.println("猫捉迷藏");
    }
}
class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
    public void lookDoor(){
        System.out.println("狗看门");
    }
}
class AnimalDemo {
    public static void main(String[] args) {
        //向上转型
        Animal a = new Cat();
        a.eat();//猫吃鱼

        //向下转型
        Cat c = (Cat) a;
        c.eat();//猫吃鱼
        c.playGame();//猫捉老鼠

        //向上转型
        a = new Dog();
        a.eat();//狗吃骨头

        //向下转型,类型转换异常
        Cat cc = (Cat) a;//报错:ClassCastException
        cc.eat();
        cc.playGame();
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贰陆.256

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值