Java多态

本文详细介绍了Java中的多态概念、其在方法调用和成员变量访问中的特点,以及抽象类、抽象方法、接口的定义和使用。重点探讨了继承、重写、向上转型、向下转型的概念,并展示了多态如何提高代码的灵活性和降低耦合性。

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

1. 多态

1.1 概述

多态:可以理解为一个事物的多种形态。同一方法调用可以根据实际调用对象的不同而采用多种不同的行为方式。

  • 多态的前提:

    • 要有子父类的继承(实现)关系

    • 有方法的重写

  • 何为多态:

    • 父类的引用指向子类的对象

  • 示例:

  • public class Person {
        String name;
        int age;
    
        public void eat() {
            System.out.println("人:吃饭!");
        }
    }
    
    /***********************************/
    
    public class Man extends Person {
        boolean isSmoking;
    
        public void earnMoney() {
            System.out.println("男人负责挣钱养家!");
        }
    
        @Override
        public void eat() {
            System.out.println("男人要多吃饭,才能长身体!");
        }
    }
    
    /***********************************/
    
    public class Woman extends Person {
        boolean isBeauty;
    
        public void goShopping() {
            System.out.println("女人喜欢购物!");
        }
    
        @Override
        public void eat() {
            System.out.println("女人要少吃饭,为了减肥!");
        }
    }
    /***********************************/
    
    public class PersonTest {
        public static void main(String[] args) {
            // Person person = new Man();// 父类的引用指向子类的对象
            Person person = new Woman();// 父类的引用指向子类的对象
            person.eat();
        }
    }

1.2 多态中方法的访问特点

  • 虚拟方法调用(多态情况下)

    • 正常的方法调用 Student student = new Student(); student.getInfo();

    • 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的

  • 编译看左边,运行看右边

  • 多态情况下

    • “看左边”:看的是父类的引用(父类中不具备子类特有的方法)

    • “看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

  • 编译的时候,要看【=】左边的引用所属的类型中,是否有该方法的定义,如果有,就编译成功,如果没有,就编译失败。

  • 运行的时候,要看【=】右边的对象所属的类型中,是如何实现这个方法的。最终运行的是子类重写过的方法实现。

  • public class PersonTest {
        public static void main(String[] args) {
            Person man = new Man();// 父类的引用指向子类的对象
            Person woman = new Woman();// 父类的引用指向子类的对象
            man.earnMoney();// 编译不通过
            woman.goShopping();// 编译不通过
            man.eat(); // 运行看右边
            woman.eat();// 运行看右边
        }
    }

1.3多态中成员变量访问特点

  • 编译看左边,运行看左边(多态性只适用于方法不适用于属性)

  • 编译的时候,要看【=】左边的引用的类型中,是否有该变量的定义,如果有,就编译成功,如果没有,就编译失败。

  • 运行的时候,要看【=】左边的引用所属类型中,真正如何给变量赋值的。获取到的是父类的赋值结果。

  • 示例:

  • 1.将Person age属性 修改成30;
    2.Man age属性修改成35;
    3.Woman age属性修改成25;
    public class PersonTest {
        public static void main(String[] args) {
            Person man = new Man();
            Person woman = new Woman();
            System.out.println(man.age);
            System.out.println(woman.age);
            System.out.println(man.isSmoking);//编译失败
            System.out.println(man.isBeauty);//编译失败
        }
    }

1.4 多态中静态方法的访问特点

  • 编译看左边,运行看左边

  • 编译的时候,要看【=】左边的引用所属的类型中,是否有该方法的定义,如果有,就编译成功,如果没有,就编译失败

  • 运行的时候,要看【=】左边的引用所属的类型中,如何实现该方法的。最终运行的是【=】左边类型中方法的结果(方法属于类,不属于重写关系,所以不加@Override)

  • 静态解释:

    • 静态变量:存储在类的字节码中的变量,被所有对象所共享,不随着对象的变化而变化,都有相同的值,所以称为静态变量。

    • 静态方法:只会根据引用所属的父类,来决定运行的内容,运行内容,不会随着子类的变化而变化,都是引用所属的父类的方法实现,所以称为静态方法。

  • 示例:

  • 1.Person Man Woman 分别添加三个static方法 walk()
     ...
     public static void walk() {
            System.out.println("人:走路!");
        }
    ...
     public static void walk() {
            System.out.println("男人走路,威武霸气!");
        }
    ...
     public static void walk() {
            System.out.println("女人走路,窈窕多姿!");
        }
    ...
      
     public class PersonTest {
        public static void main(String[] args) {
            Person man = new Man();
            Person woman = new Woman();
            man.walk();
            woman.walk();
        }
    }

1.5 向上或向下转型

  • 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法,但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法,子类特有的属性和方法不能调用。

  • 如何才能调用子类特有的属性和方法?

  • 正常情况:使用子类的引用指向子类的对象

  • 向上转型:

    • 多态中,使用父类的引用指向子类的对象(向上转型)

    • 本质:缩小了对象本身的访问范围,减少了访问的权限(只能访问父类中定义的内容)

  • 向下转型:

    • 子类的引用指向父类的对象

    • 格式:

    • 子类类型 引用名称 = (子类类型)父类类型的引用
      Man man = (Man) person;// 向下转型 person表示父类类型的引用

    • 本质:【恢复】子类类型原本就有的访问范围

    • public class PersonTest {
          public static void main(String[] args) {
              Person person = new Man();
              Man man = (Man) person;
              System.out.println(man.isSmoking);
              man.earnMoney();
              man.eat();
          }
      }

    • 注意:使用强转时,可能出现ClassCastException异常(强制类型转换异常),强转有风险。

    • public static void main(String[] args) {
              Person person = new Man();
              Woman woman = (Woman) person;
              System.out.println(woman.isBeauty);//ClassCastException
              woman.eat();//ClassCastException
              woman.goShopping();//ClassCastException
          }
      /************************正确做法************************/
      
      public static void main(String[] args) {
              Person person = new Man();
              System.out.println(person instanceof Woman);
              if (person instanceof Woman) {
                  Woman woman = (Woman) person;
                  System.out.println(woman.isBeauty);//ClassCastException
                  woman.eat();//ClassCastException
                  woman.goShopping();//ClassCastException
              }
      
          }

  • image-20230206163808114

1.6多态的好处

  • 代码示例:

    • 有一个榨汁机,放什么水果就能榨什么水果的果汁。

    • public class Fruit {
          public void flow() {
              System.out.println("榨汁");
          }
      }
      
      class Apple extends Fruit {
          @Override
          public void flow() {
              System.out.println("榨苹果汁");
          }
      }
      
      class Orange extends Fruit {
          @Override
          public void flow() {
              System.out.println("榨橙子汁");
          }
      }
      
      class Machine {
          public static void main(String[] args) {
              Machine machine = new Machine();
              machine.juiceMachine(new Apple()); // 调用的时候,传递子类类型的对象
              machine.juiceMachine(new Orange()); // 调用的时候,传递子类类型的对象
          }
      
          public void juiceMachine(Fruit fruit) {
              // 参数父类类型的引用
              fruit.flow();
          }
      }
      //******************不使用多态的写法************************************************
      
         public void juiceMachine(Fruit fruit) {
              // 参数父类类型的引用
              fruit.flow();
          }
      
          public void juiceMachine(Orange orange) {
              orange.flow();
          }
      
          public void juiceMachine(Apple apple) {
              apple.flow();
          }

    • 提高了代码的可扩展性,不需要修改源代码

    • 多态的好处体现在方法的定义上,在方法的参数列表中,可以定义父类类型的引用,将来调用的时候,所有的子类类型的对象,都可以作为方法的实际参数。 public void juiceMachine (Fruit fruit) { ... }

    • 如果不在方法的参数列表中,使用父类的类型指向子类的对象,也能提高代码的可扩展性。因为对象的来源非常广泛,不仅仅是new出来的,(还可能是通过反射获取的,通过文件读取的,还可能是网络传递的,在写代码的编译阶段,无法知道对象具体的子类类型的)需要使用父类类型的引用,操作不知道的子类类型的对象。

    • 多态还可以降低程序之间的耦合性,比如Machine类就只依赖于Fruit 类,与其他子类无关。

2.抽象类

2.1抽象类

  • 为什么需要抽象?

  • 水果案例不合理的地方:

  • machine.juiceMachine(new Fruit());
    // 解决方式,将Fruit类抽

  • 抽象:抽取像的、相同的、相似的内容出来

  • 可以定义抽象方法的类,就是抽象类,抽象类不能直接实例化对象

  • 定义格式:

  • abstract class 类名 {
        
        }

2.2抽象方法

  • 抽象方法:只有方法声明,而没有方法实现的方法,就是抽象方法 在各个子类中,对于某个方法都有自己不同的实现,所以实现各不相同,无法抽取,只能抽取方法的声明上来,在父类中,方法就只有方法声明没有方法体。就是抽象方法。

  • 定义格式:

    • 没有方法实现,连大括号都没有,直接在方法声明后面加上一个分号,表示方法定义结束

    • 为了将方法标记为抽象方法,需要在方法前面加上一个abstract关键字

  • 代码示例:

  • public abstract class Animal {
        public abstract void eat();
    }
    
    class Dog extends Animal {
        @Override
        public void eat() {
            System.out.println("狗啃骨头");
        }
    }
    
    class Cat extends Animal {
        @Override
        public void eat() {
            System.out.println("猫吃鱼");
        }
    }

2.3抽象类的特点

  • 抽象类和抽象方法都需要使用abstract关键字修饰

  • 抽象类:abstract class {}
    抽象方法:public abstract void test();
  • 抽象类和抽象方法的关系:

    • 抽象方法所在的类必须是抽象类

    • 抽象类中未必一定都定义抽象方法,抽象类中可以存在非抽象方法 ===> (可以继承给子类)

  • 抽象类的实例化(抽象类如何创建对象)

    • 抽象类不能直接实例化

    • 定义抽象类的子类,由子类创建对象,调用方法

  • 抽象类子类前途:

    • 在子类中,将父类所有的抽象方法全部重写(实现),子类就成了一个普通类,就可以创建对象

    • 在子类中,没有将父类中所有的抽象方法全部实现,子类就还是一个抽象类,还需要使用abstract关键字修饰子类。

2.4抽象类成员特点

  • 成员变量:可以定义变量,也可以定义常量,但是不能被抽象(成员变量是有默认值的,设置抽象没有意义)

  • 成员方法:

    • 既可以是抽象方法:强制子类重写

    • 也可以是非抽象方法:用于给子类继承,提高代码的复用性

  • 构造方法:

    • 是否有构造方法,不取决于是否可以创建对象,而是取决于是否可以定义成员变量。如果可以定义成员变量,那么就需要初始化成员变量,就是构造方法来完成的。

  • 代码实例:

  • /**
     * 抽象类成员特点
     */
    
    public class AbstractDemo {
        public static void main(String[] args) {
            Son son = new Son(1);
            son.test1();
            son.test2();
        }
    }
    
    abstract class Father {
        int a = 10;
        final int b = 20;
    
        public Father(int a) {
            this.a = a;
        }
    
        public abstract void test1();
    
        public void test2() {
            System.out.println("定义非抽象方法是否有意义?");
        }
    
    }
    
    class Son extends Father {
    
        public Son(int a) {
            super(a);
        }
    
        @Override
        public void test1() {
            // 实现
            super.test2();
        }
    }

3.接口

3.1接口的概述

  • 广义:一切定义规则的都是接口

  • 狭义:java中用于定义方法命名的规则就是接口

  • Java接口中,全都是方法的声明,必须都是抽象方法

  • 好处:一旦将命名规则定义出来,【方法的调用】和【方法的实现】就分离开了,可以提升开发效率,降低代码耦合性

  • 接口的特点

    • 接口的定义:使用interface关键字,编译也是生成一个【.class】文件

    • interface 接口名称 {
               方法声明的定义;
      }

    • 类可以实现接口:使用implements关键字

    • class 类名称 implements 接口名称 { 
              对接口中方法的实现;
      }

    • 代码示例:

    • /**
       * @author Petrel
       */
      public class HumanDemo {
      }
      
      interface Speakable {
          public abstract void speak();
      }
      
      abstract class Human {
          abstract void eat();
      }
      
      class Chinese extends Human implements Speakable {
          @Override
          void eat() {
              System.out.println("吃米饭");
          }
      
          @Override
          public void speak() {
              System.out.println("说汉语");
          }
      }
      
      
      class English extends Human implements Speakable {
      
          @Override
          void eat() {
              System.out.println("吃汉堡");
          }
      
          @Override
          public void speak() {
              System.out.println("说英语");
          }
      
      }
      
      class Primitive extends Human {
          @Override
          void eat() {
              System.out.println("会吃");
          }
      }

    • 实现:接口中只有方法名称的定义,在类中把接口方法的真正完成逻辑写出来

    • 接口的实例化:不能直接实例化

      • 定义实现类,实现接口,类创建对象,对象调用方法

    • 接口的实现类前途:

      • 实现类如果是一个抽象类,那么该类没有实现接口中的所有抽象方法

      • 实现类如果是一个普通类,那么该类实现了接口中的所有抽象方法

3.2接口中成员的特点

  • 成员变量:

    • 只能是常量,不能是变量,默认加上public static final,建议手动加上,因为常量是存在方法区的常量池中,没在对象中

  • 成员方法:

    • 只能是抽象方法,不能是非抽象方法,默认加上public abstract,建议手动加上,不加public abstract 的方法也是抽象方法

  • 构造方法:

    • 没有构造方法。接口中无法定义成员变量,所以不需要使用构造方法给成员变量初始化赋值。虽然接口有自己的实现类,但是对于实现类而言,不去访问接口中的构造方法,而是访问实现类的父类的构造方法。(父类是亲爹(Object)、接口是干爹,找亲爹的构造方法,而不是干爹的)

  • 代码实例:

  • /**
     * 接口中成员的特点
     */
    public interface InterfaceDemo {
        public static final int AGE = 10;
    
        public abstract void studyMath();
    
        public void studyEnglish();
    
        void studyChinese();
    
    }
    
    class A {
        public A() {
            System.out.println("父类的构造执行了");
        }
    }
    
    class B extends A implements InterfaceDemo {
        @Override
        public void studyMath() {
    
        }
    
        @Override
        public void studyEnglish() {
    
        }
    
        @Override
        public void studyChinese() {
    
        }
    }

3.3类与类、类与接口、接口与接口之间的关系

  • 类与类

    • 继承的关系,使用extends

    • 可以单继承、不可以多继承、可以多层继承

  • 类与接口:

    • 实现关系,使用implements

    • 可以单实现、也可以多实现

    • 多实现的格式

    • class 实现类类名 implements 接口1, 接口2, 接口3.......{ 
               重写所有接口中的所有抽象方法 
       }

    • 接口与接口:

    • 继承关系,使用extends

    • 多继承的格式:

    • interface 接口名 extends 父接口1, 父接口2.....{
                相当于继承了所有父接口的所有抽象方法
      }

    • 类和接口的区别(设计区别):

      • 抽象类:定义物体本身具有的固有属性和行为 (战斗机, 直升机, 民航飞机) (is - a)

      • 接口:定义物体通过学习、训练而扩展出来的行为 (鸟,子弹,热气球,风筝 ) (has -a)

    • 代码实例 :USB案例

    • public class UsbDemo {
      
          public static void main(String[] args) {
              Computer c = new Computer();
              c.start();
              c.useUSB(new KeyBorad());
              c.useUSB(new Mouse());
              c.close();
          }
      }
      
      interface USB {
          public abstract void use();
      }
      
      class Computer {//电脑是接口的使用者,不直接依赖具体的外接设备
      
          public void start() {
              System.out.println("电脑开机");
          }
      
          public void close() {
              System.out.println("电脑关机");
          }
      
          public void useUSB(USB usb) {//USB usb = new KeyBorad();//接口类引用,指向实现类对象
              usb.use();
          }
      }
      
      class KeyBorad implements USB {//键盘是USB实现者,不依赖具体的电脑
      
          @Override
          public void use() {
              System.out.println("读取键盘的输入");
          }
      }
      
      class Mouse implements USB {
          @Override
          public void use() {
              System.out.println("读取鼠标操作");
          }
      }

### Java 多态的概念 Java 中的多态是指同一个接口或类可以有多种不同的实现方式。它允许程序在运行时决定调用哪个方法,从而提高代码的灵活性和可扩展性。多态的核心机制依赖于继承、重写以及动态绑定。 #### 动态绑定 当子类覆盖父类的方法时,在运行期间会根据对象的实际类型来决定执行哪一个版本的方法[^1]。这是多态的关键特性之一。 ### 实现多态的方式 Java多态可以通过以下两种主要形式实现: 1. **方法重写(Override)** 2. **接口实现** 以下是具体示例说明如何利用 `instanceof` 运算符避免潜在异常并展示多态的应用场景。 --- ### 示例代码:Java 多态的具体应用 下面是一个完整的例子,展示了如何通过多态性和 `instanceof` 来处理不同类型的对象实例。 ```java // 定义一个基类 Animal class Animal { void makeSound() { System.out.println("Some generic sound"); } } // 子类 Dog 继承自 Animal 并重写了 makeSound 方法 class Dog extends Animal { @Override void makeSound() { System.out.println("Bark"); } // 额外的功能只属于狗 void fetchStick() { System.out.println("Fetching stick..."); } } // 子类 Cat 继承自 Animal 并重写了 makeSound 方法 class Cat extends Animal { @Override void makeSound() { System.out.println("Meow"); } // 额外的功能只属于猫 void climbTree() { System.out.println("Climbing tree..."); } } public class PolymorphismExample { public static void main(String[] args) { // 创建多个动物对象并通过向上转型存储它们 Animal myDog = new Dog(); Animal myCat = new Cat(); // 调用各自的 makeSound 方法 myDog.makeSound(); // 输出 Bark myCat.makeSound(); // 输出 Meow // 如果需要访问特定子类功能,则需使用 instanceof 和强制转换 if (myDog instanceof Dog) { ((Dog) myDog).fetchStick(); // 正确调用了 Dog 类中的特有方法 } if (myCat instanceof Cat) { ((Cat) myCat).climbTree(); // 正确调用了 Cat 类中的特有方法 } Object cValue = 42; // 假设我们有一个未知类型的变量 // 判断其实际类型并打印相应消息 System.out.println("The type of value is " + (cValue instanceof Double ? "Double" : (cValue instanceof Integer ? "Integer" : "Unknown"))); // 输出 Integer [^2] } } ``` 上述代码片段中: - 我们定义了一个通用的 `Animal` 类作为超类。 - 然后创建两个派生类 `Dog` 和 `Cat`,分别实现了自己的行为逻辑。 - 在主函数里演示了即使将这些对象赋给更广泛的父类引用 (`Animal`),仍然能够正确表现出各自的行为特征——这就是所谓的“编译看声明类型, 执行找真实类型”。 另外还加入了关于 `instanceof` 关键字使用的部分,用于确认某个对象的确切类别以便安全地进行向下造型操作而不会引发 ClassCastException 错误。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Gao_xu_sheng

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

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

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

打赏作者

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

抵扣说明:

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

余额充值