Java学习-面向对象之多态

面向对象之多态

1. 多态概述

什么是多态?

多种状态,同一对象在不同情况下表现出不同的状态或行为

Java中实现多态的步骤

  • 要有继承(或实现)关系
  • 要有方法重写
  • 父类引用指向子类对象(is a关系)

案例:

  • 代码演示
// 定义父类
public class Animal {
    // 姓名
    private String name;

    // 空参构造
    public Animal() {

    }

    // 全参构造
    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // 成员方法
    public void eat() {
        System.out.println("吃饭");
    }
}
// 是Animal类的子类
public class Dog extends Animal{
    // 需求:因为狗吃骨头,所以要优化父类的eat()方法
    @Override
    public void eat() {
        System.out.println(getName() + "吃骨头");
    }
}
/*
    动物类案例:
        已知父类Animal,成员变量为:姓名,成员方法为:eat()方法
        其有一子类Dog类,请用该案例模拟多态
 */
public class Test {
    public static void main(String[] args) {
        // 需求:演示多态
        /*
            Java中实现多态的三个步骤:
                1. 要有继承(或者实现)关系。
                2. 要有方法重写
                3. 要有父类引用指向子类对象
         */
        // 多态
        Animal an = new Dog();

        // 测试成员方法的调用
        // 结论:多态中调用成员方法时编译看左(左边的类型有没有这个成员),
        // 运行看右(运行时具体用的是右边类中的该成员)
        an.setName("哈士奇");
        an.eat();
    }
}
  • 运行结果
    在这里插入图片描述
    父类引用指向子类对象的内存图
    在这里插入图片描述

多态的效果演示

  • 需求:父类型变量作为参数时,可以接收任意子类对象
  • 分析
      A:定义方法,参数类型为父类型Animal:showAnimal(Animal animal)
      B.分别创建Dog类和Mouse类的对象
      调用showAnimal方法演示效果
  • 代码演示
// 父类:动物类
public class Animal {
    // 成员变量
    private String name;

    // 构造方法
    public Animal() {

    }

    public Animal(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    // 成员方法
    public void eat() {
        System.out.println("吃饭");
    }
}
// 子类:狗类
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println(getName() + "吃骨头");
    }
}
// 子类:老鼠类
public class Mouse extends Animal{
    @Override
    public void eat() {
        System.out.println(getName() + "吃奶酪");
    }
}
/*
    已知父类Anima,成员变量为:姓名、成员方法为:eat()方法
    它有两个子类Dog类,Mouse类,两个子类都重写了Animal类中的eat()方法
    在测试类中,定义showAnimal()方法,用来测试Dog类和Mouse类
 */
public class Test {
    public static void main(String[] args) {

        // 用来测试Dog类和Mouse类
        // 测试Dog类
        Dog d = new Dog();
        d.setName("哈士奇");
        showAnimal(d);

        // 测试Mouse类
        Mouse m = new Mouse();
        m.setName("Jerry");
        showAnimal(m);
    }
    // 需求:在该类中定义showAnimal()方法
    // 多态做法
    // 多态的使用场景:父类型可以作为形参的数据类型
    // 这样可接收其任意的子类对象
    public static void showAnimal(Animal an) {
        an.eat();
    }
    // 传统做法
   /* public static void showAnimal(Dog d) {
        d.eat();
    }

    public static void showAnimal(Mouse m) {
        m.eat();
    }*/
}
  • 运行结果
    在这里插入图片描述
  • 多态的效果内存图
    在这里插入图片描述

案例:多态关系中成员变量的使用

  • 需求:子父类中定义了同名的成员变量,如何调用?
  • 分析
      A:子父类中定义同名属性name并分别初始化值:String name;
      B:在测试类中以多态的方式创建对象并打印name属性值:Animal animal = new Dog();
      C:在测试类中以普通方式创建对象并打印name属性值:Dog dog = new Dog();
  • 结论
    成员变量不能重写
  • 代码演示
// 父类:动物类
public class Animal {
    String name = "Animal";
}
// 子类:狗类
public class Dog extends Animal{
    String name = "Dog";
}
/*
    需求:测试多态关系,成员变量的使用,

    结论:
        多态关系中,成员变量是不涉及到重写的
        简单记忆:
            多态关系中,使用成员变量,遵循“编译看左,运行看左”
            编译看左:意思是在编译期间看左边的类型有没有这个成员,没有就报错,有就不报错
            运行看左,意思是在运行期间使用的是 左边的类型中的这个成员.
 */
public class Test {
    public static void main(String[] args) {
        // 通过多态的方式创建对象,然后测试成员变量的使用
        // 多态:父类引用指向子类对象
        Animal an = new Dog();
        System.out.println(an.name);

        // 通过普通方式创建对象,然测试
        Dog dog = new Dog();
        System.out.println(dog.name);
    }
}
  • 运行结果
    在这里插入图片描述
  • 多态关系中成员变量的内存图
    在这里插入图片描述

2.多态的好处和弊端

多态的好处

  • 可维护性:基于继承关系,只需要维护父类代码,提高了代码的复用性,大大降低了维护程序的工作量
  • 可扩展性:把不同的子类对象都当做父类看待,屏蔽了不同子类对象间的差异,做出通用的代码,以适应不同的需求,实现了向后兼容

多态的弊端

不能使用子类特有成员

类型转换

当需要使用子类特有功能时,需要进行类型转换

  • 向上转型(自动类型转换)
      子类型转换成父类型
      Animal animal = new Dog();
  • 向下转型(强制类型转换)
  •   父类型转换成子类型
  •   Dog dog = (Dog)animal;

注意事项

  • 只能在继承层次内进行转换(ClassCastException)
  • 将父类对象转换成子类之前,使用instanceof进行检查

案例

  • 代码演示
// 父类:动物类
public class Animal {
    public void eat() {
        System.out.println("吃饭");
    }
}
// 子类:狗类
public class Dog extends Animal{
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }

    // 狗类独有的方法,父类中是没有这个成员方法的
    public void watch() {
        System.out.println("狗会看家");
    }
}
// 子类:猫类
public class Cat extends Animal{

}
public class Test {
    public static void main(String[] args) {
        // 需求:通过多态创建对象,调用子类中的成员
        Animal an = new Dog();

        // 调用eat()方法
        an.eat();

        // 调用watch()方法,属于子类独有的方法
        // an.watch();
        // 正确的写法
        /*Dog dog = (Dog)an;
        dog.watch();*/

        // 不正常的转换
        // Cat cat = (Cat)an;

        // 优化后的方案:判断当前对象是否是Dog类的对象,如果是,再调用watch()方法
        if (an instanceof Dog) {    // 判断an是否是Dog类的对象
            //能走到这里,说明条件满足
            Dog dog = (Dog)an;
            dog.watch();
        }
    }
}
  • 运行结果
    在这里插入图片描述

3. 抽象类概述

抽象的由来

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

抽象类的概念

包含抽象方法的类。用abstract修饰

抽象方法的概念

只有方法声明,没有方法体的方法。用abstract修饰

抽象方法的由来

当需要定义一个方法,却不明确方法的具体实现时,可以将方法定义为abstract,体局实现延迟到子类

案例1:

  • 代码演示
//父类:动物类(抽象类)
public abstract class Animal {
    //抽象方法(特点:要求子类方法必须重写)
    public abstract void eat();
}
//子类:狗类
public class Dog extends Animal{
    //alt + enter:快捷键,自动帮你重写方法
    @Override
    public void eat() {
        System.out.println("狗吃骨头");
    }
}
//子类:老鼠类
public class Mouse extends Animal{
    @Override
    public void eat() {
        System.out.println("老鼠吃奶酪");
    }
}
public class Test {
    public static void main(String[] args) {
        //测试狗类
        Dog dog = new Dog();
        dog.eat();

        //测试老鼠类
        Mouse mouse = new Mouse();
        mouse.eat();
        System.out.println("---------------");

        //通过多态进行测试
        Animal an = new Dog();
        an.eat();
    }
}
  • 运行结果
    在这里插入图片描述

4. 抽象类的特点

抽象类的特点

  • 修饰符:必须用abstract关键字修饰
      修饰符 abstract class 类名{}
      修饰符 abstract 返回类型 方法名{}
  • 抽象类不能被实例化,只能创建子类对象
  • 抽象类子类的两个选择
      重写父类所有抽象方法
      定义成抽象类

抽象类成员的特点

  • 成员变量
      可以有普通的成员变量
      也可以有成员常量(final)
  • 成员方法
      可以有普通方法,也可以有抽象方法
      抽象类不一定有抽象方法,有抽象方法的类一定是抽象类(或接口)
  • 构造方法
      像普通类一样有构造方法,且可以重载

案例2:

//父类:动物类(抽象类)
public abstract class Animal {
    //构造方法
    public Animal() {}

    public Animal(String name) {
        this.name = name;
    }
    //成员变量,其值可变
    String name = "哈士奇";
    //成员常量,其值不能发生改变
    final int AGE = 10;

    //吃
    public abstract void eat();
    //睡
    public abstract void sleep();
    //叫
    public void call() {
        System.out.println("动物会叫");
    }
}
//子类:狗类
public abstract class Dog extends Animal{
}
//子类:猫类
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫吃鱼");
    }

    @Override
    public void sleep() {
        System.out.println("猫躺着睡");
    }
}
/*
    总结:
        抽象类中的成员比普通类多一种:抽象方法
        其它和普通类一样
 */
public class Test {
    public static void main(String[] args) {
        //抽象类不能new(抽象类不能实例化)
        //Animal an = new Animal();     这样写是错误的

        //初始化抽象类
        Animal an = new Cat();
        System.out.println("-----------------------");

        //抽象类的成员特点
        an.name = "汤姆";
        System.out.println(an.name);

        //an.AGE = 50;  代码会报错,原因是常量的值不能发生改变
        System.out.println(an.AGE);
    }
}
  • 运行结果
    在这里插入图片描述

案例3:

  • 代码演示
//父类:员工类
public abstract class Employee {
    //成员变量
    //姓名
    private String name;
    //工资
    private double salary;
    //工号
    private String id;

    //构造方法

    public Employee() {
    }

    public Employee(String name, double salary, String id) {
        this.name = name;
        this.salary = salary;
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getSalary() {
        return salary;
    }

    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    //成员方法
    //工作
    public abstract void work();
}
//子类:经理类
public class Manager extends Employee{
    public Manager() {

    }

    public Manager(String name, double salary, String id, int bonus) {
        super(name, salary, id);
        this.bonus = bonus;
    }

    //奖金
    private int bonus;

    public int getBonus() {
        return bonus;
    }

    public void setBonus(int bonus) {
        this.bonus = bonus;
    }

    @Override
    public void work() {
        System.out.println("经理喝着茶看着程序员敲代码");
    }
}
//子类:程序员类
public class Coder extends Employee{
    //小细节,在实际开发中,子类一般都有两个构造方法
    //子类的空参构造访问父类的空参构造
    //子类的全彩构造访问父类的全参构造

    public Coder() {
        super();
    }

    public Coder(String name, double salary, String id) {
        super(name, salary, id);
    }

    @Override
    public void work() {
        System.out.println("程序员要敲代码");
    }
}
public class Test {
    public static void main(String[] args) {
        //测试员工类
        Employee em = new Coder();
        em.work();

        //测试经理类
        Employee em2 = new Manager();
        em2.work();
        System.out.println("--------------");

        //扩展内容,快速实例化对象
        //需求:创建一个姓名叫张三,工资为50000,工号为:研发部007的程序员
       /* Coder c = new Coder();
        c.setName("张三");
        c.setSalary(500000);
        c.setId("研发部007");*/
        Coder c = new Coder("张三", 50000, "研发部007");
        System.out.println("姓名" + c.getName());
        System.out.println("工资" + c.getSalary());
        System.out.println("工号" + c.getId());

        //需求:创建一个名字叫:李四,工资为:400000,工号为:研发部01,奖金为100000
        Manager m = new Manager("李四", 400000, "研发部01",100000);
        System.out.println("姓名" + m.getName());
        System.out.println("工资" + m.getSalary());
        System.out.println("工号" + m.getId());
        System.out.println("奖金" + m.getBonus());
    }
}
  • 运行结果
    在这里插入图片描述

5. final关键字

final的概念

最终的、最后的

final的作用

用于修饰类、方法和变量

  • 修饰类:该类不能被继承
        String,System
  • 修饰方法:该方法不能被重写
         不能与abstract共存
  • 修饰变量:最终变量,即常量,只能赋值一次
         不建议修饰引用类型数据,因为仍然可以通过引用修改对象的内部数据,意义不大

案例

  • 代码演示
//员工类
public class Employee /*extends Person*/{
    //成员变量
    String name;
    int age;
    public final void show() {
        System.out.println("这个是绝密文件");
    }
}
//程序员类
public class Coder extends Employee{
    /*@Override
    public void show() {
        System.out.println("这个是垃圾文件");
    }*/
}
/*
    final关键字:
        final这个单词是”最终“的意思,在Java中是一个关键字,可以用来修饰类,成员变量,成员方法
        修饰的类:不能被继承,但是恶意继承其他的类
        修饰的方法:不能被重写
        修饰的变量:是一个常量,值只能设置一次
 */
public class Test {
    public static void main(String[] args) {
        Employee em = new Coder();
        em.show();
        System.out.println("---------------");

        //final修饰的变量:基本类型的变量,是值不能改变
        final int NUM = 20;
        //NUM = 30; 代码错误,常量值只能设置y9ici
        System.out.println(NUM);
        System.out.println("------------------");
        //final修饰的变量:引用类型的变量,是地址值不能改变,但是属性值可以发生变化
        final Employee em2 = new Employee();
        //em2 = new Employee(); 代码报错,原因是只要new就会开辟新空间
        em2.name = "张三";
        em2.age = 23;
        System.out.println("name属性值:" + em2.name);
        System.out.println("age属性值" + em2.age);
        System.out.println("------------");

        em2.name = "李四";
        em2.age = 25;
        System.out.println("name属性值:" + em2.name);
        System.out.println("age属性值" + em2.age);
    }
}
  • 运行结果
    在这里插入图片描述

6. static关键字

static的概念

静态的

static的作用

用于修饰类的成员:
    成员变量:类变量
    成员方法:类方法

调用方式

    类名,成员变量名;
    类名,成员方法名(参数);

static修饰成员变量

  • 特点:
        被本类所有对象共享
  • 需求:定义研发部成员类,让每位成员进行自我介绍
  • 分析
        A:研发部成员统称为开发者,定义类Developer
        B.每位开发者所属部门相同,所以属性departmentName用static修饰:public static String     departmentName = “研发部”;
        C:Developer类的普通属性和行为:name,work,selfIntroduction();
        D:在测试类中创建对象并使用
        E:修改部门名称为“开发部”,测试效果
  • 注意事项
    随意修改静态变量的值时有风险的,为了降低风险,可以同时用final关键字修饰,即共有静态常量(注意命名的变化);
  • 代码演示
public class Developer {
    //成员变量
    //姓名
    String name;
    //工作内容
    String work;
    //部门名(公共的静态常量)
    public final static String DEPARTMENT_NAME = "研发部";      //应该用static修饰
    //成员方法
    //自我介绍
    public void selfIntroduction() {
        System.out.println("我是" + DEPARTMENT_NAME + "的" + name + ",我的工作内容是" + work);
    }
}
public class Test {
    public static void main(String[] args) {
        //需求:创建两个员工,然后测试
        Developer d1 = new Developer();
        d1.name = "张三";
        d1.work = "写代码";
        d1.selfIntroduction();

        Developer d2 = new Developer();
        d2.name = "李四";
        d2.work = "鼓励师";
        d2.selfIntroduction();
        System.out.println("--------------");
        //随着公司的发展,部门名字要进行调整,改为:开发部
        //Developer.departmentName = "开发部";

        d1.selfIntroduction();
        d2.selfIntroduction();
    }
}
  • 运行结果
    在这里插入图片描述

static修饰成员方法

  • 静态方法:
        静态方法中没有对象this,所以不能访问非静态成员
  • 静态方法的使用场景
        只需要访问静态成员
        不需要访问对象状态,所需参数都由参数列表显示提供
  • 需求:定义静态方法,反转数组中的元素
  • 分析:
        A:先明确定义方法的三要素:
        方法名:reverse(反转)
        参数列表:int[] arr
        返回值类型:void
        B:遍历数组,交换数组索引为i和length-1-i的元素:arr[i] <=> arr[arr.length-1-i]
        C:当索引i >= (length - 1 - i)时,停止交换元素
        D:在测试类中创建对象并使用
  • 代码演示
public class ReverseArray {
    int num1 = 10;
    static int num2 = 20;

    //静态方法中没有对象this,所以不能访问非静态成员
    public static void show() {
        System.out.println(num2);
    }

    //需求:定义静态方法,反转数组中的元素
    public static void reverse(int[] arr) {
        /*
            这里只需要完成:交换元素的动作就可以了
            假设数组中的元素值为:int[] arr = {11, 22, 33, 44, 55},
            明确谁和谁进行交换
            11和55交换 22和44交换 ...
            arr[i] 和 arr[arr.length - 1 - i]交换
            明确交换次数:数组的长度/2
         */
        for (int i = 0; i < arr.length / 2; i++) {
            //arr[i] 和 arr[arr.length - 1 - i]交换
            int temp = arr[i];
            arr[i] = arr[arr.length - 1 - i];
            arr[arr.length - 1 - i] = temp;
        }
    }
}
public class Test {
    public static void main(String[] args) {
        //测试show()方法
        ReverseArray.show();

        //小需求:交换变量
        int a = 10;
        int b = 20;
        int temp = a;   //temp = 10;
        a = b;      //a = 20;
        b = temp;       //b = 10;
        System.out.println("a:" + a);
        System.out.println("b:" + b);
        System.out.println("--------------------");
        int[] arr = {1, 6, 4, 8, 2};
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
        System.out.println("---------------------");

        //调用方法,反转数组
        ReverseArray.reverse(arr);
        for (int i = 0; i < arr.length; i++) {
            System.out.println(arr[i]);
        }
    }
}
  • 运行结果
    在这里插入图片描述

7. 接口概述

接口的概念

接口技术用于描述类具有什么功能,但并不给出具体实现,类要遵从接口描述的统一规则进行定义,所以,接口是对外提供的一组规则、标准。

  • 代码演示
//接口:表示抽烟的功能
public interface Smoking {
    //成员方法
    public abstract void smoke();
}
public class Teacher implements Smoking{
    @Override
    public void smoke() {
        System.out.println("抽烟有害健康");
    }
}
public class Test {
    public static void main(String[] args) {
        //多态
        Smoking sm = new Teacher();
        sm.smoke();
    }
}
  • 运行结果
    在这里插入图片描述

接口的定义

  • 定义接口使用关键字interface
          interface 接口名{}
  • 类和接口是实现关系,用implements表示
          class 类名 implements 接口名

接口创建对象的特点

  • 接口不能实例化
          通过多态的方式实例化子类对象
  • 接口的子类(实现类)
          可以是抽象类,也可以是普通类

接口继承关系的特点

  • 接口与接口之间的关系
          继承关系,可以多继承,格式:
          接口 extends 接口1,接口2,接口3…
  • 继承和实现的区别
          继承体现的是“is a”的关系,父类中定义共性内容
          实现体现的是“like a”的关系,接口中定义扩展功能
  • 代码演示
//接口:USB接口
public interface USB {
    public abstract void open();

    public abstract void close();
}
public interface A {
}
public interface B {
}
public interface C extends A,B,USB{
}
//子类:键盘类
public abstract class KeyBoard implements USB{
}
//子类:鼠标类
public class Mouse implements USB,A,B{
    @Override
    public void open() {
        System.out.println("连接鼠标");
    }

    @Override
    public void close() {
        System.out.println("断开鼠标连接");
    }
}
public class Test {
    public static void main(String[] args) {
        //测试鼠标类
        USB usb = new Mouse();
        usb.open();
        usb.close();
    }
}
  • 运行结果
    在这里插入图片描述

8. 接口成员的特点

接口成员方法的特点

  • 成员方法
        JDK7以前,共有的、抽象方法:
        public abstract 返回值类型 方法名();
        JDK8以后,可以有默认方法和静态方法:
        public default 返回值类型 方法名(){}
        static 返回值类型 方法名(){}
        JDK9以后,可以有私有方法
        private 返回值类型 方法名(){}
  • 构造方法
        接口不能够实例化,也没有需要初始化的成员,所以接口没有构造方法
  • 代码演示
public interface USB {
    //成员常量
    public static final int NUM = 10;

    //成员方法
    //jdk7及其以前的写法
    public abstract void open();
    public abstract void close();

    //jdk8多了两种写法
    public static void method1(){
        System.out.println("我是jdk8的新特性");
    }
    public default void method2(){
        System.out.println("我是jdk8的新特性");
    }

    //jdk9多了一种写法
    /*private void method3(){
        System.out.println("我是jdk9的新特性");
    }*/

    //构造方法
//    public USB(){}
}
public class Test {
    public static void main(String[] args) {
        //测试接口中的成员变量
        //USB.num = 20;     这样写代码会报错,常量的值不能修改
        System.out.println(USB.NUM);
    }
}
  • 运行结果
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值