Java面向对象多态、抽象类、接口详解

一、方法重写

1. 方法重写概述

  • 什么是方法重写
    方法重写是在继承中出现,当子类对父类所提供的方法不满意是可以对方法重写,又称为方法覆盖、方法复写。重写的方法与父类中的原方法的方法声明(方法名、参数列表、返回值)一样
  • 应用
    当子类需要父类的功能,而父类提供的功能又无法完全满足子类的需求的时候,可以重写父类的方法,这样子,即沿袭了父类的功能,又满足了子类特有的需求

2. 方法重写的注意事项

  1. 父类私有的方法不能重写
    因为父类私有的方法子类根本就无法继承,更别说重写了
  2. 子类重写父类方法时,访问权限不能更低,最好一致
  3. 父类中的静态方法,子类也必须通过静态方法进行重写

3. 举例

//定义动物类
public class Animal {
    public String name;
    public int age;

    public void eat() {
        System.out.println("吃饭");
    }
}
//定义猫类,继承自动物类,并对动物类所提供的eat方法进行了重写
public class Cat extends Animal {
    @Override
    public void eat() {
        System.out.println("猫爱吃鱼");
    }
}
//测试类
public class MyTest {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();//输出“猫爱吃鱼”
    }
}

二、多态

1. 多态的概述

  • 什么是多态
    同一个事物在不同的时刻表现出来的状态
  • 多态举例
    Cat cat = new Cat();
    Animal animal = new Cat();
    
    cat是Cat类的一员,所以有Cat cat = new Cat();,但同时cat也是Animal类的一员,所以也有Animal animal = new Cat();
  • 多态的前提
    1. 要有继承关系
      多态是建立在子类与父类之间的关系上的(必须要求)
    2. 要有方法重写
      子类有自己的独有的功能(语法不要求,但是要有)
    3. 要有父类引用指向子类的对象
      Animal animal = new Cat();

2. 多态中的成员访问特点

  • Animal类与Cat类

    public class Animal {//Animal类
        public String name = "animal";
        public int age = 100;
    
        public static void run() {
            System.out.println("跑步");
        }
    
        public Animal() {
            System.out.println("Animal类的无参构造");
        }
    
        public void eat() {
            System.out.println("吃饭");
        }
    }
    public class Cat extends Animal {//Cat类
        public String name = "cat";
        public int age = 10;
    
        public static void run() {
            System.out.println("猫跑步");
        }
    
        public Cat() {
            System.out.println("Cat类的无参构造");
        }
    
        @Override
        public void eat() {
            System.out.println("猫爱吃鱼");
        }
    }
    
  • 成员变量

    • 编译看左边,运行看左边
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              Animal animal = new Cat();//多态,用父类型去引用子类对象
              System.out.println(animal.name);
              System.out.println(animal.age);
          }
      }
      //执行结果:
      //      animal
      //      100
      
  • 构造方法

    • 在创建子类对象的时候会优先访问父类的构造方法,对父类的数据进行初始化
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              Animal animal = new Cat();
               }
      }
      //      执行结果:
      //      Animal类的无参构造
      //      Cat类的无参构造
      
  • 成员方法

    • 编译在左边,运行在右边
      编译的时候会先去父类中寻找成员方法,如果又,则不报错;在运行时优先执行子类中重载、写过后的方法,如果没有,则执行父类原来的方法
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              Animal animal = new Cat();
              animal.eat();
          }
      }
      //  猫爱吃鱼
      //  调用的子类的方法,也就是“右边”
      
  • 静态方法

    • 静态方法因为与类相关,所以算不上重写,所以还是访问父类的静态方法
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              Animal animal = new Cat();
              animal.run();
          }
      }
      //跑步
      //因为静态方法是属于类所有的,所以调用的还是父类的静态方法
      

3. 多态的利弊

  • 优点
    • 提高了代码的维护性(继承)
    • 提高了代码的扩展性(多态)
  • 缺点
    • 不能访问子类所独有的成员方法和子类的成员变量
  • 缺点的解决方案
    • 创建一个子类的对象,利用对象调用
      这样做可以,但是太麻烦,而且也占用空间(创建对象)
    • 向下转型
      将父类引用强制转换为子类引用,这样就可以调用子类所独有的成员

4. 向上转型与向下转型

  • 向上转型
    • 将子类对象的类型转换为父类的类型,即用父类的引用指向子类的对象
    • 父类的引用可以调用父类的成员变量及子类重写过的方法,不能调用子类的成员变量及子类特有的方法
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              //向上转型,将子类Cat类型的对象转为父类Animal类型
              Animal animalCat = new Cat();
              System.out.println(animalCat.name);
              System.out.println(animalCat.age);//可以用父类类型引用调用父类成员变量
              animalCat.eat();//子类重写过后的方法
          }
      }
      
  • 向下转型
    • 将向上转型为父类类型的引用再强转为原来的类型
    • 这个对象最初new出来时是哪个子类的对象,最后父类的引用只能向下转型为哪个类,不能转型为父类的其他子类
    • 子类的引用可以调用子类的所有的成员,包括从父类继承过来的成员
    • 举例
      public class MyTest {
          public static void main(String[] args) {
              //向上转型,将子类Cat类型的对象转为父类Animal类型
              Animal animalCat = new Cat();
              //向下转型,将父类类型的animalCat转换为原来的子类类型Cat
              Cat cat = (Cat) animalCat;//使用强制转换
              System.out.println(cat.name);
              System.out.println(cat.age);
              cat.eat();//使用子类引用调用子类的成员
          }
      }
      

5. 多态的内存图解

多态的内存图

三、抽象类

1. 抽象类概述

  • 为什么要有抽象类
    • 在前面,我们对动物类Animal进行了实例化,也就是创建了对象,这其实是错的,对象应该是具体的某一事物,而“animal”对象一点也不具体,所以不能对Animal类进行实例化。同时,在Animal类中,有eat和sleep方法,而且给出了具体的实现,这也是不对的。
      不同的动物有不同的食物种类,也有不同的睡觉时间和睡觉方式,应该由具体的子类去实现这个具体的功能,而不是由父类给出具体的实现细节,父类只需要给出定义即可,不用给出具体的实现。
  • 抽象方法
    • 只有方法声明,而没有具体的方法体的方法,用abstract关键字修饰
    • 例如
      • public abstract void eat();
    • 父类中如果有抽象方法,那么子类中一定要对父类中的抽象方法进行重写
  • 抽象类
    • 用abstract关键字修饰的类,被修饰的类不能实例化
    • 例如
      public abstract class Animal {
          public String name = "animal";
          public int age = 100;
          public abstract void eat();
      }
      
    • 抽象类中不一定有抽象方法,但是有抽象方法的类一定是抽象类
    • 抽象类中构造方法的作用
      • 虽然抽象类不用实例化,但是在抽象类的子类实例时,要访问父类(也就是抽象类)的数据,这个时候就需要构造方法来对父类数据初始化
    • 可以用多态的形式,由具体的子类来实例抽象类
    • 抽象类的子类
      • 抽象类,不用重写抽象方法
      • 具体的类,要重写所有父类的抽象方法

2. 抽象类的成员特点

  • 成员变量
    • 可以是变量,也可以是常量
  • 构造方法
    • 有,当子类访问父类数据时,用来对父类数据进行初始化
  • 成员方法
    • 抽象方法,要求子类必须重写,自己实现
    • 非抽象方法,子类继承父类的实现,提高代码复用性

3. 抽象类练习

  • 案例演示1
    具体事物:小学生,大学生
    共性:姓名,年龄,学习,睡觉

  • 代码

    public abstract class Student {//抽象类
        public static String name;
        public static int age;
    
        public abstract void study();//抽象方法
    
        public abstract void sleep();//抽象方法
    }
    
    public class PrimarySchoolStudent extends Student {//小学生子类
        @Override
        public void study() {//方法重写
            System.out.println("小学生上课听讲");
        }
    
        @Override
        public void sleep() {//方法重写
            System.out.println("小学生睡得早");
        }
    
        public void watchAnimation() {//子类独有的方法
            System.out.println("小学生看动画片");
        }
    }
    
    public class UniversityStudent extends Student {//大学生子类
        @Override
        public void study() {//方法重写
            System.out.println("大学生上台自己讲");
        }
    
        @Override
        public void sleep() {//方法重写
            System.out.println("大学生睡得迟");
        }
    
        public void playCellPhone() {//子类独有的方法
            System.out.println("大学生玩手机");
        }
    }
    
    public class MyTest {//测试类
        public static void main(String[] args) {
            PrimarySchoolStudent smallStudent = new PrimarySchoolStudent();
            smallStudent.study();
            smallStudent.sleep();
            smallStudent.watchAnimation();
    
            UniversityStudent bigStudent = new UniversityStudent();
            bigStudent.study();
            bigStudent.sleep();
            bigStudent.playCellPhone();
        }
    }
    

4. abstract关键字与哪些关键字冲突

  • private
    • private修饰的成员是本类私有的,在类外不能访问,而抽象方法与抽象类是建立在继承的基础上的,所以冲突
  • final
    • final修饰的类不能被继承,修饰的方法不能被重写,修饰的成员变量是常量,所以冲突
  • static
    • static修饰的成员可以直接用类名调用,而抽象方法只有声明而没有方法体,使用类名调用也无意义,所以冲突

四、接口

1. 接口概述

  • 为什么要有接口
    • 当一个类想要拥有它原来所不具有的功能时,可以将这个功能的声明写在一个接口里面,让这个类实现这个接口就好了。
    • 比如动物园中老虎可以钻火圈,老虎类中本来不具有这个功能,如果将钻火圈这个功能定义在老虎类中就显得不合适,这个时候就可以将钻火圈这个功能定义在一个接口中,让有需要的老虎实现这个接口就可以了
  • 接口的含义
    • 接口就是给类提供的一些额外功能,体现了扩展性

2. 接口的特点

  • 用关键字interface定义
    • interface 接口名{}
  • 类实现接口用implements关键字
    • class 类名 implements 接口名{}
    • 可以多实现
    • 这个类可以是抽象类,也可以是非抽象类,非抽象类要重写接口中的所有抽象方法
  • 接口继承接口用extends关键字
    • interface 子接口名 extends 父接口名{}
    • 可以多继承
  • 接口实例化
    • 接口不能直接实例化
    • 但可以使用多态的形式,类似于抽象类
  • 接口的成员特点
    • 成员变量
      • 只能是静态常量
      • 默认修饰符是public static final,建议显式给出
    • 成员方法
      • 只能是抽象方法
      • 默认修饰符:public abstract,建议显式给出
    • 构造方法
      • 没有,接口不需要实例化,成员变量都是常量,不需要初始化,方法也不需要初始化,所以不需要构造方法

3. 类与类、类与接口、接口与接口的关系

  • 类与类
    • 继承,只能单继承,但可以多层继承
  • 类与接口
    • 实现,可以多实现
  • 接口与接口
    • 继承,可以多继承

4. 抽象类与接口的区别

  • 成员区别
    • 成员变量
      • 抽象类不做要求,可以是变量,也可以是常量
      • 接口只能是静态常量
    • 成员方法
      • 抽象类不做要求,可以是抽象方法,也可以是非抽象方法
      • 接口只能是抽象方法
    • 构造方法
      • 抽象类有,用于子类非抽象类实例时父类数据初始化
      • 接口没有,不需要
  • 设计理念
    • 抽象类中定义的是该继承体系的共性功能,是“is a”关系
    • 接口中定义的是该继承体系的扩展功能,是“like a”关系

5. 接口举例

  • 题目

    培训机构的老师有基础班的,也有就业班的。
    共性:
            属性:姓名,年龄
    
            功能:讲课。
            
    现在又要针对日语这种小语种单独开班,
    需要部分基础班老师和部分就业班老师会说日语。
    请用所学知识把上面的内容用代码体现。
    
  • 思路

    1. 先创建一个老师类
    2. 由老师类继承而来基础班与就业班的老师
    3. 创建讲日语接口
    4. 给基础班和就业班需要会讲日语的老师实现讲日语接口
    5. 测试
    
  • 代码

    public abstract class Teacher {//教师类
        protected String name;
        protected int age;
        public abstract void lecture();//抽象方法讲课
    }
    
    
    public class BasicClassTeacher extends Teacher {//基础班教师类
        @Override
        public void lecture() {
            System.out.println("基础班老师讲基础多一点");
        }
        public BasicClassTeacher() {
        }
        public BasicClassTeacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    
    public class EmployClassTeacher extends Teacher {//就业班教师类
        @Override
        public void lecture() {
            System.out.println("就业班老师讲就业方面的多一点");
        }
        public EmployClassTeacher() {
        }
        public EmployClassTeacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    
    public interface SpeakJapanese {//讲日语接口
        public abstract void speakJapaneae();//定义抽象的讲日语方法
    }
    
    
    public class SpeJapBCTeacher extends BasicClassTeacher implements SpeakJapanese {//会讲日语的基础班教师类
        @Override
        public void speakJapaneae() {
            System.out.println("基础班会讲日语的老师");
        }
        public SpeJapBCTeacher() {
        }
        public SpeJapBCTeacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    
    public class SpeJapECTeacher extends EmployClassTeacher implements SpeakJapanese {//会讲日语的就业班教师类
        @Override
        public void speakJapaneae() {
            System.out.println("就业班会讲日语的老师");
        }
        public SpeJapECTeacher() {
        }
        public SpeJapECTeacher(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
    
    public class MyTest {//测试类
        public static void main(String[] args) {
            BasicClassTeacher bCTeacher = new BasicClassTeacher("张三", 20);//基础班
            System.out.println(bCTeacher.name);
            System.out.println(bCTeacher.age);
            bCTeacher.lecture();
    
            System.out.println("--------------分割线--------------");
    
            EmployClassTeacher eCTeacher = new EmployClassTeacher("李四", 23);//就业班
            System.out.println(eCTeacher.name);
            System.out.println(eCTeacher.age);
            eCTeacher.lecture();
    
            System.out.println("--------------分割线--------------");
    
            SpeJapBCTeacher sJBCTeacher = new SpeJapBCTeacher("王五", 26);//会讲日语的基础班老师
            System.out.println(sJBCTeacher.name);
            System.out.println(sJBCTeacher.age);
            sJBCTeacher.lecture();
            sJBCTeacher.speakJapaneae();
    
            System.out.println("--------------分割线--------------");
    
            SpeJapECTeacher sJECTeacher = new SpeJapECTeacher("赵六", 29);//会讲日语的就业班老师
            System.out.println(sJECTeacher.name);
            System.out.println(sJECTeacher.age);
            sJECTeacher.lecture();
            sJECTeacher.speakJapaneae();
        }
    }
    

新人上路,如有不足,请多多指教!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值