面向对象三大特征——多态

2023-12-16 13:36:31

目录

1. 多态

1.1 概述

1.2 多态中方法的访问特点

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

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

1.5 向上或向下转型

1.6多态的好处

2.抽象类

2.1抽象类

2.2抽象方法

2.3抽象类的特点

2.4抽象类成员特点

3.接口

3.1接口的概述

3.2接口中成员的特点

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


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();
 ?  }
}
 
  • 员工案例练习

/**
 * @author Petrel
 */
public class AbstractExercise {
 ? ?/*
 ? ?*  员工类练习
 ? ? ? 测试工程师:属性(姓名、工号、工资) ? ?  ,行为(工作:软件测试,展示自己信息)
 ? ? ? 程序员类: 属性(姓名、工号、工资、奖金) ,行为(工作:软件开发,展示自己信息)
 ? ? ? 项目经理类:属性(姓名、工号、工资、奖金) ,行为(工作:控制进度,展示自己信息)
?
 ? ?* */
 ? ?public static void main(String[] args) {
 ? ? ? ?Employee tester = new Tester("张三", "001", 8000);
 ? ? ? ?tester.showInfo();
 ? ? ? ?tester.work();
 ? ? ? ?Employee coder = new Coder("李四", "002", 10000, 5000);
 ? ? ? ?coder.showInfo();
 ? ? ? ?coder.work();
 ? ? ? ?Employee manager = new Manager("王五", "003", 12000, 8000);
 ? ? ? ?manager.showInfo();
 ? ? ? ?manager.work();
 ?  }
}
?
abstract class Employee {
 ? ?private String name;//工资
 ? ?private String id;//工号
 ? ?private double salary;//工资
?
 ? ?public Employee(String name, String id, double salary) {
 ? ? ? ?this.name = name;
 ? ? ? ?this.id = id;
 ? ? ? ?this.salary = salary;
 ?  }
?
 ? ?public Employee() {
 ?  }
?
 ? ?public String getName() {
 ? ? ? ?return name;
 ?  }
?
 ? ?public void setName(String name) {
 ? ? ? ?this.name = name;
 ?  }
?
 ? ?public String getId() {
 ? ? ? ?return id;
 ?  }
?
 ? ?public void setId(String id) {
 ? ? ? ?this.id = id;
 ?  }
?
 ? ?public double getSalary() {
 ? ? ? ?return salary;
 ?  }
?
 ? ?public void setSalary(double salary) {
 ? ? ? ?this.salary = salary;
 ?  }
?
 ? ?abstract void showInfo();
?
 ? ?abstract void work();
}
?
abstract class Emp extends Employee {
 ? ?private double bonus;
?
 ? ?public Emp(String name, String id, double salary, double bonus) {
 ? ? ? ?super(name, id, salary);
 ? ? ? ?this.bonus = bonus;
 ?  }
?
 ? ?public double getBonus() {
 ? ? ? ?return bonus;
 ?  }
?
 ? ?public void setBonus(double bonus) {
 ? ? ? ?this.bonus = bonus;
 ?  }
?
 ? ?@Override
 ? ?void showInfo() {
 ? ? ? ?System.out.println(super.getName() + "..." + super.getId() + "..." + super.getSalary() + "..." + bonus);
 ?  }
}
?
class Coder extends Emp {
 ? ?public Coder(String name, String id, double salary, double bonus) {
 ? ? ? ?super(name, id, salary, bonus);
 ?  }
?
 ? ?@Override
 ? ?void work() {
 ? ? ? ?System.out.println("软件开发");
 ?  }
}
?
class Manager extends Emp {
 ? ?public Manager(String name, String id, double salary, double bonus) {
 ? ? ? ?super(name, id, salary, bonus);
 ?  }
?
 ? ?@Override
 ? ?void work() {
 ? ? ? ?System.out.println("控制进度");
 ?  }
}
?
class Tester extends Employee {
 ? ?public Tester(String name, String id, double salary) {
 ? ? ? ?super(name, id, salary);
 ?  }
?
 ? ?public Tester() {
 ?  }
?
 ? ?@Override
 ? ?void showInfo() {
 ? ? ? ?System.out.println(super.getName() + "..." + super.getId() + "..." + super.getSalary());
 ?  }
?
 ? ?@Override
 ? ?void work() {
 ? ? ? ?System.out.println("软件测试");
 ?  }
}

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("读取鼠标操作");
 ?  }
}

文章来源:https://blog.csdn.net/2301_77351901/article/details/135031249
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。