• java基础(4)继承和多态


    1 继承

    1.1  继承概述

    继承要解决的问题:共性抽取

    1.2 继承的格式

    class 父类 {
    ...
    }
    class 子类 extends 父类 {
    ...
    }

    1.3 继承中成员变量的特点(变量重名)

    1.3.1 子类父类成员变量重名

    • 规则1:直接通过子类访问成员变量:

    等号左边是谁,就优先用谁,没有则向上寻找

    • 规则2:间接通过成员方法访问成员变量:

    方法属于谁,就优先用谁,没有则向上找。

    //定义父类
    public class Fu {
        int num = 10;
    
        public int method(){
            return num;
        }
    }
    //定义子类
    public class Zi extends Fu{
        int num = 20;//与父类成员变量重名
    }
    //
    public class demo01 {
        public static void main(String[] args) {
            Fu fu = new Fu();
            Zi zi = new Zi();
    
            System.out.println(fu.num);//输出10,Fu fu = new Fu()等号左边是Fu,所以去Fu类中找成员变量num
            System.out.println(zi.num);//输出20,Zi zi = new Zi() 等号左边是Zi,所以在Zi类中找成员变量num
     System.out.println(fu.method()); //10,方法属于父类
    System.out.println(zi.method()); //10,方法属于父类
    }
    }

    1.3.2 三种变量重名

    子类方法的局部变量:直接写成员变量

    子类的成员变量:this.

    父类的成员变量:super.

    class Fu {
    // Fu中的成员变量。
    int num = 10;
    }
    
    class Zi extends Fu {
    // Zi中的成员变量
    int num = 20;
    public void show() {
    int num = 30; System.out.println(
    "local num=" + num); //访问父类中的num System.out.println("Fu num=" + super.num); //访问子类中的num System.out.println("Zi num=" + this.num); } } class ExtendsDemo03 { public static void main(String[] args) { // 创建子类对象 Zi z = new Zi(); // 调用子类中的show方法 z.show(); } }
    //
    10
    20
    30

    1.4 继承中成员方法的访问特点

    成员方法不重名:

    对象调用方法时,会先在子类中查找有没有对
    应的方法,若子类中存在就会执行子类中的方法,若子类中不存在就会执行父类中相应的方法。

    成员方法重名——重写(Override)

    class Fu {
    public void show() {
    System.out.println("Fu show");
    }
    }
    class Zi extends Fu {
    //子类重写了父类的show方法
    public void show() {
    System.out.println("Zi show");
    }
    }
    public class ExtendsDemo05{
    public static void main(String[] args) {
    Zi z = new Zi();
    // 子类中有show方法,只执行重写后的show方法
    z.show(); // Zi show
    }
    }

    总结:在调用子类成员变量和成员方法时,如果子类中有的,就先用自己的。没有再找父类。方法是谁的,就优先用谁的。

    重写和重载的区别:

    重写override:方法的名称一样,参数列表页一样;

    @override 注解,可以检测重写是否正确

    重载overload:方法的名称一样,参数列表不一样。

    重写权限:子类方法的权限必须大于等于父类方法的访问权限

    public>protected>(default)>private

    其中default是留空

    1.5 继承中构造方法的访问特点

    • 构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构
      造方法中默认有一个super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。

    public class Fu {
        public Fu(){
            System.out.println("Father Constrictor");
        }
    }
    
    public class Zi extends Fu {
        public Zi(){
            System.out.println("Son Constrictor");
        }
    }
    
    public class Constructor {
        public static void main(String[] args) {
            Zi zi = new Zi();//默认会调用super(),无参构造方法
        }
    }
    
    //
    Father Constrictor
    Son Constrictor
    • 子类构造可以通过super调用父类重载构造,且只能用一次super
    • 子类必须调用父类构造方法,不写则默认赠送super()无参构造,写了则用指定的super()方法

    1.6 super和this的几种用法

    1.6 this当前对象的一点总结

    //创建父类
    public
    class User { String name; private double leftMoney; public User() { } public User(String name, double leftMoney) { this.name = name; this.leftMoney = leftMoney; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getLeftMoney() { return leftMoney; } public void setLeftMoney(double leftMoney) { this.leftMoney = leftMoney;////子类对象去调用父类方法时,并没有创建父类对象。this指针指的是调用该方法的对象(子类对象),所以这里的this指的是子类对象}
      public void show(){ 
        System.out.println(
    "用户名:"+name+"余额:"+leftMoney);
    }
    }


    //创建子类
    public class Manager extends User { public Manager(){ } public Manager(String name, double money){ super(name,money); } public void setLeft(double redTotal){ double leftMoney = super.getLeftMoney(); super.setLeftMoney(leftMoney-redTotal); } }
    //创建main方法
    public class redMain { public static void main(String[] args) { Manager manager = new Manager("Manager",200); manager.show();//输出:用户名:Manager余额:200.0 manager.setLeft(10);//子类对象去调用父类方法时,并没有创建父类对象。this指针指的是调用该方法的对象(子类对象) manager.show();//输出:用户名:Manager余额:190.0
    } }

    2 接口

    2.1 概述

    • 接口是多个类的公共规范;
    • 接口是一种引用数据类型,最重要的是其中的抽象方法;接口也会被编译成.class文件,但一定要明确它并不是类,而是另外一种引用数据类型。
    • 引用数据类型:数组,类,接口。
    • 接口是方法的集合,如果说类的内部封装了成员变量、构造方法和成员方法,那么接口的内部主要就是封装了方法,包含抽象方法(JDK 7及以前),默认方法和静态方法(JDK 8),私有方法(JDK 9)。
    • 接口的使用:它不能创建对象,但是可以被实现( implements ,类似于被继承)。一个实现接口的类(可以看做
      是接口的子类),需要实现接口中所有的抽象方法,创建该类对象,就可以调用方法了,否则它必须是一个抽象
      类。

    2.2 接口定义

    //定义格式
    public
    interface 接口名称 { // 抽象方法 // 默认方法 // 静态方法 // 私有方法 }

    2.2.1 接口中的抽象方法定义和使用

    public interface MyInterfaceAbstract {
        public abstract void methodAbstract();//不能有大括号
    //    private abstract void methodAbstrac2();//错误,只能用public
        abstract void methodAbstrac3();//可以省略public
        void methodAbstrac4();//可以同时省略public abstract
    }

    接口的使用:

    • 接口不能直接new对象使用。必须有一个实现类类实现该接口
    • 类与接口的关系为实现关系,即类实现接口,该类可以称为接口的实现类,也可以称为接口的子类。实现的动作类

        似继承,格式相仿,只是关键字不同,实现使用 implements 关键字。

    • 非抽象子类实现接口:

        1. 必须重写接口中所有抽象方法。
        2. 继承了接口的默认方法,即可以直接调用,也可以重写。

    public class MyInterfaceAbstractImpl implements MyInterfaceAbstract{
        @Override
        public void methodAbstract() {
            
        }
    
        @Override
        public void methodAbstrac3() {
    
        }
    
        @Override
        public void methodAbstrac4() {
    
        }
    }

    2.2.2 接口中的默认方法定义和使用

    默认方法的作用:

    实现类会继承接口中的抽象方法。当接口升级(添加新方法)的时候,将该新方法定义为default,则已存在的实现类中不用添加代码去实现该方法(如果新方法在接口中定义为abstract,则实现类必须要实现该方法,会增加很多工作量)。

    //定义接口
    public interface MyInterfaceDefault {
        public abstract void methodAbs();
        public default void methodDeft(){
            System.out.println("接口的default方法");
        }
    }
    
    //实现类
    public class MyInterfaceDefaultImplA implements MyInterfaceDefault {
        @Override
        public void methodAbs() {
            System.out.println("实现类的抽象方法");
        }
    }
    
    //主函数
    public class demoMyDefault {
        public static void main(String[] args) {
            MyInterfaceDefaultImplA implA = new MyInterfaceDefaultImplA();
            implA.methodAbs();
            implA.methodDeft();//默认继承了接口的default方法,实现类中可以不用写,直接调用;也可以重写
        }
    }
    
    //输出
    实现类的抽象方法
    接口的default方法

    2.2.3 接口中的静态方法定义和使用

    /*
    从Java 8开始,接口当中允许定义静态方法。
    格式:
    public static 返回值类型 方法名称(参数列表) {
        方法体
    }
    提示:就是将abstract或者default换成static即可,带上方法体。
     */
    public interface MyInterfaceStatic {
    
        public static void methodStatic() {
            System.out.println("这是接口的静态方法!");
        }
    
    }
    
    /* 注意事项:不能通过接口实现类的对象来调用接口当中的静态方法。 正确用法:通过接口名称,直接调用其中的静态方法。 格式: 接口名称.静态方法名(参数); */ public class Demo03Interface { public static void main(String[] args) { // 创建了实现类对象 MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl(); // 错误写法!不能用实现类的对象来调用接口中的静态方法 // impl.methodStatic(); // 直接通过接口名称调用静态方法 MyInterfaceStatic.methodStatic(); } }

    2.2.4 接口中的私有方法定义和使用

    /*
    问题描述:
    我们需要抽取一个共有方法,用来解决两个默认方法之间重复代码的问题。
    但是这个共有方法不应该让实现类使用,应该是私有化的。
    
    解决方案:
    从Java 9开始,接口当中允许定义私有方法。
    1. 普通私有方法,解决多个默认方法之间重复代码问题
    格式:
    private 返回值类型 方法名称(参数列表) {
        方法体
    }
    
    2. 静态私有方法,解决多个静态方法之间重复代码问题
    格式:
    private static 返回值类型 方法名称(参数列表) {
        方法体
    }
     */

    //对默认方法的重复代码私有化
    public interface MyInterfacePrivateA { public default void methodDefault1() { System.out.println("默认方法1"); methodCommon(); } public default void methodDefault2() { System.out.println("默认方法2"); methodCommon(); } private void methodCommon() { System.out.println("AAA"); System.out.println("BBB"); System.out.println("CCC"); } }
    //在接口中,对静态方法的重复代码的私有化
    public interface MyInterfacePrivateB {
    
        public static void methodStatic1() {
            System.out.println("静态方法1");
            methodStaticCommon();
        }
    
        public static void methodStatic2() {
            System.out.println("静态方法2");
            methodStaticCommon();
        }
    
        private static void methodStaticCommon() {
            System.out.println("AAA");
            System.out.println("BBB");
            System.out.println("CCC");
        }
    
    }

    2.3 接口中的常量定义

    /*
    接口当中也可以定义“成员变量”,但是必须使用public static final三个关键字进行修饰。
    从效果上看,这其实就是接口的【常量】。
    格式:
    public static final 数据类型 常量名称 = 数据值;
    
    注意事项:
    1. 接口当中的常量,可以省略public static final,注意:不写也默认是public static final。
    2. 接口当中的常量,必须进行赋值。
    3. 接口中常量的名称,使用完全大写的字母,用下划线进行分隔。(推荐命名规则)
     */
    public interface MyInterfaceConst {
    
        // 这其实就是一个常量,一旦赋值,不可以修改
        public static final int NUM_OF_MY_CLASS = 12;
    
    }

    2.4 接口的多实现和多继承

    在继承体系中,一个类只能继承一个父类。而对于接口而言,一个类是可以实现多个接口的,这叫做接
    口的多实现。并且,一个类能继承一个父类,同时实现多个接口。
    
    格式:
    class 类名 [extends 父类名] implements 接口名1,接口名2,接口名3... {
    // 重写接口中抽象方法【必须】
    // 重写接口中默认方法【不重名时可选】
    }
    
    [ ]: 表示可选操作。

    2.4.1 多实现会出现以下的问题:

    /*
    使用接口的时候,需要注意:
    
    1. 接口是没有静态代码块或者构造方法的。
    2. 一个类的直接父类是唯一的,但是一个类可以同时实现多个接口。
    格式:
    public class MyInterfaceImpl implements MyInterfaceA, MyInterfaceB {
        // 覆盖重写所有抽象方法
    }
    3. 如果实现类所实现的多个接口当中,存在重复的抽象方法,那么只需要覆盖重写一次即可。
    4. 如果实现类没有覆盖重写所有接口当中的所有抽象方法,那么实现类就必须是一个抽象类。
    5. 如果实现类所实现的多个接口当中,存在重复的默认方法,那么实现类一定要对冲突的默认方法进行覆盖重写(两个默认方法都不用,自己重新覆盖重写)。
    6. 一个类如果直接父类当中的方法,和接口当中的默认方法产生了冲突,优先用父类当中的方法。
     */
    public class Demo01Interface {
    
        public static void main(String[] args) {
            Zi zi = new Zi();
            zi.method();
        }
    
    }
    //第1点
    public interface MyInterfaceA {
    
        // 错误写法!接口不能有静态代码块
    //    static {
    //
    //    }
    
        // 错误写法!接口不能有构造方法
    //    public MyInterfaceA() {
    //
    //    }
    
        public abstract void methodA();
    
        public abstract void methodAbs();
    
        public default void methodDefault() {
            System.out.println("默认方法AAA");
        }
    
    }
    public interface MyInterfaceB {
    
        // 错误写法!接口不能有静态代码块
    //    static {
    //
    //    }
    
        // 错误写法!接口不能有构造方法
    //    public MyInterfaceA() {
    //
    //    }
    
        public abstract void methodB();
    
        public abstract void methodAbs();
    
        public default void methodDefault() {
            System.out.println("默认方法BBB");
        }
    
    }
    //第2点和第3点
    public class MyInterfaceImpl /*extends Object*/ implements MyInterfaceA, MyInterfaceB {
    
        @Override
        public void methodA() {
            System.out.println("覆盖重写了A方法");
        }
    
    
        @Override
        public void methodB() {
            System.out.println("覆盖重写了B方法");
        }
    
        @Override
        public void methodAbs() {
            System.out.println("覆盖重写了AB接口都有的抽象方法");
        }
    
        @Override
        public void methodDefault() {
            System.out.println("对多个接口当中冲突的默认方法进行了覆盖重写");
        }
    }
    //第6点
    //优先级,继承优先于接口
    public class Fu {
    
        public void method() {
            System.out.println("父类方法");
        }
    }
    
    
    public class Zi extends Fu implements MyInterface {
    }

    2.4.2 多继承注意事项

    /*
    1. 类与类之间是单继承的。直接父类只有一个。
    2. 类与接口之间是多实现的。一个类可以实现多个接口。
    3. 接口与接口之间是多继承的。
    
    注意事项:
    1. 多个父接口当中的抽象方法如果重复,没关系。
    2. 多个父接口当中的默认方法如果重复,那么子接口必须进行默认方法的覆盖重写,【而且带着default关键字】。
     */
    public class Demo01Relations {
    }
    public interface MyInterfaceA {
        public abstract void mehtodA();
        public abstract void methodCom();
        public default void methodDef(){
            System.out.println("AAA");
        }
    }
    
    public interface MyInterfaceB {
        public abstract void mehtodB();
        public abstract void methodCom();
        public default void methodDef(){
            System.out.println("BBB");
        }
    }
    
    public interface MyInterface extends MyInterfaceA,MyInterfaceB{
        public abstract void mehtod();
    
        @Override
        default void methodDef() {//必须重写default方法
    
        }
    }

    3 多态 

    3.1 概述

    //多态体现的格式:左父右子
    
    父类类型 变量名 = new 子类对象;
    变量名.方法名();

    3.2 多态中成员变量的访问特点

    当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,执行的是子类重写
    后方法。

    //定义父类
    public class Fu /*extends Object*/ {
    
        int num = 10;
    
        public void showNum() {
            System.out.println(num);
        }
    
        public void method() {
            System.out.println("父类方法");
        }
    
        public void methodFu() {
            System.out.println("父类特有方法");
        }
    
    }
    
    //定义子类
    public class Zi extends Fu {
    
        int num = 20;
    
        int age = 16;
    
        @Override
        public void showNum() {
            System.out.println(num);
        }
    
        @Override
        public void method() {
            System.out.println("子类方法");
        }
    
        public void methodZi() {
            System.out.println("子类特有方法");
        }
    }
    
    
    /*
    访问成员变量的两种方式:
    
    1. 直接通过对象名称访问成员变量:看等号左边是谁,优先用谁,没有则向上找。
    2. 间接通过成员方法访问成员变量:看该方法属于谁,优先用谁,没有则向上找。
     */
    public class Demo01MultiField {
    
        public static void main(String[] args) {
            // 使用多态的写法,父类引用指向子类对象
            Fu obj = new Zi();
            System.out.println(obj.num); // 父:10 //等号左边是父类
    //        System.out.println(obj.age); // 错误写法!//父类没有该成员变量,向上找也没有
            System.out.println("=============");
    
            // 子类没有覆盖重写,就是父:10
            // 子类如果覆盖重写,就是子:20
            obj.showNum();//看该方法属于谁
        }
    
    }

    3.3 多态中成员方法的访问特点

    //父子类定义如上节
    /* 在多态的代码当中,成员方法的访问规则是: 看new的是谁,就优先用谁,没有则向上找。 口诀:编译看左边,运行看右边。 对比一下: 成员变量:编译看左边,运行还看左边。 成员方法:编译看左边,运行看右边。 */ public class Demo02MultiMethod { public static void main(String[] args) { Fu obj = new Zi(); // new的Zi obj.method(); // 父子都有,优先用子 obj.methodFu(); // 子类没有,父类有,向上找到父类 // 编译看左边,左边是Fu,Fu当中没有methodZi方法,所以编译报错。 // obj.methodZi(); // 错误写法! } }

    3.4 向上转型和向下转型

    //向上转型

    父类类型 变量名 = new 子类类型(); 如:Animal a = new Cat();
    //当父类引用指向一个子类对象
    //创建一个子类对象,把它当做父类来看待使用
    //向上转型一定是安全的
    //向下转型

    子类类型 变量名 = (子类类型) 父类变量名; 如:Cat cat =(Cat) animal;

    为什么要转型:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误。也就是说,不能调用子类拥
    有,而父类没有的方法。编译都错误,更别说运行了。所以,想要调用子
    类特有的方法,必须做向下转型。

    含义:将父类对象,还原成本来的子类对象。

    public class Test {
    public static void main(String[] args) {
    // 向上转型
    Animal a = new Cat(); //创建一只cat
    a.eat(); // 调用的是 Cat 的 eat
    // 向下转型
    Dog d = (Dog)a;//本来创建的是一只cat,所以向下不能还原成dog
    d.watchHouse(); // 调用的是 Dog 的 watchHouse 【运行报错】
    }
    }

    3.5 instanceof

    /*
    如何才能知道一个父类引用的对象,本来是什么子类?
    格式:
    对象 instanceof 类名称
    这将会得到一个boolean值结果,也就是判断前面的对象能不能当做后面类型的实例。
     */
    public class Demo02Instanceof {
    
        public static void main(String[] args) {
            Animal animal = new Dog(); // 本来是一只狗
            animal.eat(); // 狗吃SHIT
    
            // 如果希望掉用子类特有方法,需要向下转型
            // 判断一下父类引用animal本来是不是Dog
            if (animal instanceof Dog) {
                Dog dog = (Dog) animal;
                dog.watchHouse();
            }
            // 判断一下animal本来是不是Cat
            if (animal instanceof Cat) {
                Cat cat = (Cat) animal;
                cat.catchMouse();
            }
    
            giveMeAPet(new Dog());
        }
    //实际应用场景,外边传入的对象类型不定,需要自己判断一下类型 public static void giveMeAPet(Animal animal) { if (animal instanceof Dog) { Dog dog = (Dog) animal; dog.watchHouse(); } if (animal instanceof Cat) { Cat cat = (Cat) animal; cat.catchMouse(); } } }

     

  • 相关阅读:
    洛谷1525关押罪犯——二分
    洛谷P1525关押罪犯——二分做法
    poj2411铺砖——状压DP
    1 理解Linux系统的“平均负载”
    3.2-3 tac、more
    3.20 tr:替换或删除字符
    3.14-19 wc、iconv、dos2unix、diff、vimdiff、rev
    3.21-22 od、tee
    指针和引用的区别
    new与malloc区别
  • 原文地址:https://www.cnblogs.com/JohnTeslaaa/p/10290737.html
Copyright © 2020-2023  润新知