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(); } } }

 

猜你喜欢

转载自www.cnblogs.com/JohnTeslaaa/p/10290737.html