Java中的继承与多态(黑马视频笔记)

继承

继承主要解决的问题是:共性抽取
继承关系的特点:

  1. 子类可以拥有父类的“内容”
  2. 子类还可以拥有自己专有的内容
    在继承的关系中,“子类就是一个父类”。关系:is-a

继承格式

//定义父类的格式:(一个普通的类定义)
public class 父类名称 {
	//......
}
// 定义子类的格式:
public class 子类名称 extends 父类名称 {
	//.......
}

继承中成员变量的访问特点
在父子类的继承关系中,如果成员变量重名,则创建子类对象时,有两种访问方式:

  1. 直接通过子类对象访问成员变量
    等号左边是谁,就优先用谁,没有再向上找
  2. 间接通过成员方法访问成员变量
    该方法属于谁,就优先用谁,没有则向上找
//定义父类
public class Employee {
    int num = 10;

    public void methodEmployee() {
        System.out.println(num);
    }
}

//定义子类
public class Teacher extends Employee {
    int num = 20;

    public void methodTeacher() {
        System.out.println(num);
    }
}

public class Demo01Extends {
    public static void main(String[] args) {
        Employee employee = new Employee();
        System.out.println(employee.num);   //10

        Teacher teacher = new Teacher();
        System.out.println(teacher.num);    //20

        //这个方法是子类的,优先使用子类的,没有再向上找
        teacher.methodTeacher();    //20
        //这个方法是父类中定义的
        teacher.methodEmployee();   //10
    }
}

区分子类方法中的三种变量重名
局部变量:直接写
子类的成员变量:this.成员变量
父类的成员变量:super.成员变量

public class Father {
    int num = 10;
}

public class Son extends Father {
    int num = 20;

    public void method(){
        int num = 30;
        System.out.println(num);    //30,局部变量
        System.out.println(this.num);   //20,本类的成员变量
        System.out.println(super.num);  //10,父类的成员变量
    }
}

public class Demo01ExtendsField {
    public static void main(String[] args) {
        Son son = new Son();
        son.method();   //30  20  10
    }
}

继承中成员方法的访问特点
在父子类的继承关系中,创建子类对象,访问成员方法的规则:创建的对象是谁,就优先访问谁的方法,如果没有则向上找
注意:无论是成员方法或者成员变量,若没有都是向上找父类

public class Father {
    public void methodFather() {
        System.out.println("父类方法执行");
    }
    public void method(){
        System.out.println("父类重名方法执行");
    }
}

public class Son extends Father{
    public void methodSon(){
        System.out.println("子类方法执行");
    }
    public void method(){
        System.out.println("子类重名方法执行");
    }
}

public class Demo02ExtendsField {
    public static void main(String[] args) {
        Son son = new Son();
        son.methodSon();    //子类方法执行
        son.methodFather(); //父类方法执行

        //创建的是子类对象,所以优先使用子类方法
        son.method();   //子类重名方法执行
    }
}

继承中方法的覆盖重写
重写(override):发生在继承关系中,方法的名称一样,参数列表也一样。发生在继承关系中(也称为覆盖、覆写)
重载(overload):方法的名称一样,参数列表不一样

方法的重写特点:创建的是子类对象,则优先使用子类方法

注意:

  1. 必须保证父子类之间的方法名称相同,参数列表也相同
    @Override 可以选择写在方法前面,用来检测是不是有效的正确重写(以防不小心重载而非覆写了该方法)
  2. 子类方法的返回值必须 <= 父类方法的返回值范围
    提示:java.lang.Object类是所有类的公共最高父类,所以比如 java.lang.String 就是Object 的子类
  3. 子类方法的权限必须 >= 父类方法的权限修饰符
    提示:public > protected > (default)> private(其中default 不是关键字,而是什么都不写,留空)
public class Phone {
    public void call() {
        System.out.println("打电话");
    }
    public void send() {
        System.out.println("发短信");
    }
    public void show() {
        System.out.println("显示号码");
    }
}

public class newPhone extends Phone {
    @Override
    public void show(){
        super.show();   //把父类的show()方法拿来复用
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

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

  1. 子类构造方法当中,有一个默认隐含的 “super();” 调用,所以先调用父类构造方法,后执行的子类构造方法
  2. 子类构造可以通过super关键字来调用父类重载构造
  3. super的父类构造调用,必须是子类构造方法的第一个语句。不能一个子类构造调用多次super构造
  4. 子类必须调用父类构造方法,不写则自动调用super(),写了则用指定的super调用

super关键字的三种用法

  1. 在子类的成员方法中,访问父类的成员变量
  2. 在子类的成员方法中,访问父类的成员方法
  3. 在子类的构造方法中,访问父类的构造方法
public class Father {
    int num =10;
    public void method() {
        System.out.println("父类方法");
    }
}

public class Son extends Father {
    int num = 20;
    public Son(){
        super();    //访问父类构造器,其实如果不写程序也会默认调用
    }
    public void methodSon(){
        System.out.println(super.num);  //父类中的num
    }
    public void method(){
        super.method(); //父类的method()方法
        System.out.println("子类方法");
    }
}

this关键字的三种用法
super关键字用来访问父类内容,而this关键字用来访问本类内容

  1. 在本类的成员方法中,访问本类的成员变量
  2. 在本类的成员方法中,访问本类的另一个成员方法
  3. 在本类的构造方法中,访问本类的另一个构造方法(注意:this(…)调用也必须是构造方法的第一个语句,且是唯一一个)
    注意:super和this两种构造调用,不能同时使用
public class Father {
   int num = 30;
}

public class Son extends Father {
   int num = 10;

   public Son(){
       this(123);  //构造方法的重载调用。本类的无参构造,调用本类的有参构造
       //this(1,2);    //错误写法,必须是第一个语句,只能调用一个构造方法
   }
   public Son(int n){

   }
   public Son(int n, int m){

   }

   public void showNum(){
       int num = 20;
       System.out.println(num);    //局部变量,20
       System.out.println(this.num);   //成员变量,10
       System.out.println(super.num);  //父类成员变量,30
   }

   public void methodA(){
       System.out.println("AAA");
   }

   public void methodB(){
       this.methodA(); //调用本类methodA()方法
       System.out.println("BBB");
   }
}

下面是一个继承关系中super与this的内存图:在这里插入图片描述
Java中继承的特点

  1. Java是单继承的。即一个类的直接父类只能有唯一一个
  2. Java可以多级继承(Java中所有类都继承自java.lang.Object类)
  3. 一个子类的直接父类是唯一的,但一个父类可以拥有多个子类。

抽象类

  • 抽象类概念:如果一个类中没有包含足够的信息来描绘一个具体的对象,即如果父类当中的方法不确实如何进行方法体{}的具体实现,这样的类就是抽象类。

  • 抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  • 由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。

  • 在Java中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口。

抽象方法和抽象类的格式

抽象方法:加上abstract关键字,然后去掉{},直接分号结束
抽象类:抽象方法所在的类,必须是抽象类,(在class之前直接加上abstract即可)

抽象方法和抽象类的使用

  1. 抽象类不能实例化对象
  2. 所以用一个子类继承抽象父类类,才能被使用。
  3. 子类必须覆盖重写抽象父类当中所有的抽象方法
    覆盖重写(实现):子类去掉父类的abstract关键字,然后补上方法体{}
  4. 创建子类对象进行使用
//抽象类
public abstract class Animal {
    //抽象方法
    public abstract void eat();
    
    public void normalMethod() {  }
}

public class Cat extends Animal {
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
}

public class Demo01Abstract {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();	//猫吃鱼
    }
}

注意:

  1. 抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象
  2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
    (子类的构造方法中,有默认的super(),需要访问父类构造方法)
  3. 抽象类中不一定包含抽象方法,但有抽象方法的类必须是抽象类
    (没有抽象方法的抽象类也不能直接创建对象,在一些特殊场景下有用途,比如设计模式中的适配器模式)
  4. 抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类
//2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的
public abstract class Animal {
    public abstract void eat();
    public Animal(){
        System.out.println("抽象父类构造方法执行");
    }
}

public class Cat extends Animal {
    public Cat(){
        //super();  //程序自动执行
        System.out.println("子类构造方法执行");
    }
    @Override
    public void eat(){
        System.out.println("吃鱼");
    }
}

public class Demo01Abstract {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.eat();
    }
}
//Output:
//抽象父类构造方法执行
//子类构造方法执行
//吃鱼
//4. 抽象类的子类,必须重写抽象父类中**所有的**抽象方法,除非该子类也是抽象类
public abstract  class Animal {
    public abstract void eat();
    public abstract void sleep();
}

public abstract class Dog extends Animal {
    @Override
    public void eat(){
        System.out.println("狗吃骨头");
    }
}

public class DogGolden extends Dog {
    @Override
    public void sleep(){
        System.out.println("呼呼呼...");
    }
}

public class Demo02Main {
    public static void main(String[] args) {
        DogGolden dog = new DogGolden();
        dog.eat();  //狗吃骨头
        dog.sleep();    //呼呼呼...
    }
}

接口

接口就是多个类的公共规范,是一种引用数据类型
接口最重要的内容就是其中的抽象方法

接口的定义
public interface 接口名称 { //接口内容 }
(备注:使用关键字 interface 后,编译生成的字节码文件依然为 .java → .class)

接口中可以包含的内容有

  1. 常量
  2. 抽象方法
  3. 默认方法(Java 8)
  4. 静态方法(Java 8)
  5. 私有方法(Java 9)

接口的抽象方法

在任何版本的Java中,接口都可以定义抽象方法

注意: 接口中的抽象方法,修饰符必须是两个固定关键字:public,abstract(这两个修饰符可以选择性省略),方法的三要素可以随意定义

接口使用步骤:

  1. 接口不能直接使用,必须有一个“实现类”来“实现”该接口
    public class 实现类名称 implements 接口名称 { //... }
  2. 接口的实现类必须覆盖重写(实现)接口中所有的抽象方法(去掉 abstract 关键字,加上方法体大括号{})
  3. 创建实现类的对象,进行使用
    注意:如果实现类并没有覆盖重写接口中所有的抽象方法,那么这个实现类自身必须是抽象类
public interface MyInterfaceAbstract {

    public abstract void methodAbs1();   //这是一个抽象方法

    //abstract void methodAbs2();   //这也是抽象方法
    //public void methodAbs3();   //这也是抽象方法
    //void methodAbs4();   //这也是抽象方法
}

//实现类
public class MyInterfaceAbstractImpl implements MyInterfaceAbstract {
    @Override
    public void methodAbs1() {
        System.out.println("这是一个方法");
    }
}

public class Demo01Interface {
    public static void main(String[] args) {
        //MyInterfaceAbstract inter = new MyInterfaceAbstract(); //错误!不能直接new接口对象使用

        MyInterfaceAbstractImpl impl = new MyInterfaceAbstractImpl();
        impl.methodAbs1();
    }
}

接口的默认方法
从 Java 8 开始,接口中允许定义默认方法
格式:
public default 返回值类型 方法名称 (参数列表) { //方法体 }
默认方法必须为public方法 (public可省略,default不可省略)

备注:接口当中的默认方法,可以解决接口升级的问题

注意:

  1. 接口的默认方法,可以通过接口实现类对象,直接调用
  2. 接口的默认方法,也可以被接口实现类进行覆盖重写
  3. 接口中的默认方法会被实现类继承
public interface MyInterfaceDefault {

    //抽象方法
    public abstract void methodAbs();

    //新添加了一个抽象方法
    //public abstract void methodAbs2();  //此时所有接口的实现类都必须覆盖重写此抽象方法,不合理

    //新添加的方法,改为默认方法
    public default void methodDefault() {
        System.out.println("这是新添加的默认方法");
    }
}

public class MyInterfaceDefaultA implements MyInterfaceDefault {
    @Override
    public void methodAbs() {
        System.out.println("实现了抽象方法,AAA");
    }
}

public class MyInterfaceDefaultB implements MyInterfaceDefault {
    @Override
    public void methodAbs() {
        System.out.println("实现了抽象方法,BBB");
    }
    @Override
    public void methodDefault() {
        System.out.println("实现类B覆盖重写了接口的默认方法");
    }
}

public class Demo02Interface {
    public static void main(String[] args) {
        //创建实现类对象
        MyInterfaceDefaultA a = new MyInterfaceDefaultA();
        //调用抽象方法,实际运行的是右侧实现类
        a.methodAbs();  //OutPut:实现了抽象方法,AAA
        //调用默认方法,如果实现类d当中没有,会向上找接口的
        a.methodDefault();  //OutPut:这是新添加的默认方法

        MyInterfaceDefaultB b = new MyInterfaceDefaultB();
        b.methodAbs();  //OutPut:实现了抽象方法,BBB
        b.methodDefault();  //OutPut:实现类B覆盖重写了接口的默认方法
    }
}

接口的静态方法
从 Java 8 开始,接口中允许定义静态方法
格式:
public static 返回值类型 方法名称 (参数列表) { //方法体 }( public 可省略)

注意:

  1. 不能通过接口实现类的对象来调用接口中的静态方法(一个类可以实现多个接口,多个接口中静态方法可能产生冲突)
  2. 通过接口名称,直接调用其中的静态方法:接口名称.静态方法名()
public interface MyInterfaceStatic {
    public static void methodStatic(){
        System.out.println("这是接口的静态方法");
    }
}

/*public class MyInterfaceStaticImpl implements MyInterfaceStatic  {
}*/

public class Demo03Interface {
    public static void main(String[] args) {
        //MyInterfaceStaticImpl impl = new MyInterfaceStaticImpl();
        //错误写法
        //impl.methodStatic();
        
        MyInterfaceStatic.methodStatic();
    }
}

接口的私有方法

(如接口中多个方法具有大量重复内容,我们需要抽取一个共有方法,来解决两个方法之间重复代码的问题。但这个共有方法,不能被实现类使用,应该为私有化的)

从 Java 9 开始,接口中允许定义私有方法:

  1. 普通私有方法(解决多个默认方法之间重复代码问题)
    private 返回值类型 方法名称 (参数列表) { //方法体 }
  2. 静态私有方法(解决多个静态方法之间重复代码问题)
    private static 返回值类型 方法名称 (参数列表) { //方法体 }

private 方法只有接口自己才能调用,不能被实现类或他人使用

public class MyInterfacePrivateB {
   public static void methodStatic1(){
       System.out.println("静态方法1");
       methodStaticCommon();
   }

   public static void methodStatic2(){
       System.out.println("静态方法2");
       methodStaticCommon();
   }

   //只能被methodStatic1()和methodStatic2()访问
   private static void methodStaticCommon(){
       System.out.println("AAA");
       System.out.println("BBB");
       System.out.println("CCC");
   }
}

public class Demo04Interface {
   public static void main(String[] args) {
       MyInterfacePrivateB.methodStatic1();
       MyInterfacePrivateB.methodStatic2();
       //MyInterfacePrivateB.methodStaticCommon(); //  错误写法
   }
}

接口的常量
接口中可以定义“成员变量”,但是必须使用 public static final 三个关键字进行修饰。从效果上看,其实就是接口的“常量”

格式:
public static final 数据类型 常量名称 = 数据值;

注意:

  1. 接口当中的常量,可以省略 public static final,但性质不变
  2. 接口当中的常量,必须进行赋值
  3. 接口中常量的名称,使用完全大写的字母,并用下划线进行分隔(推荐命名规则)
public interface MyInterfaceConst {
    //其实就是常量,赋值后不可修改
    public static final int NUM_OF_MY_CLASS = 10;
}

public class Demo05Interface {
    public static void main(String[] args) {
        System.out.println(MyInterfaceConst.NUM_OF_MY_CLASS);
    }
}

多接口实现

  1. 接口没有静态代码块或构造方法
  2. 一个类的直接父类是唯一的,但一个类可以同时实现多个接口
    public class 实现类名称 implements 接口1, 接口2 { //覆盖重写抽象方法 }
  3. 若实现类没有覆盖重写所有接口中的所有抽象方法,则实现类必须是抽象类
  4. 若实现类所实现的多个接口中,存在重复的抽象方法,则只需要覆盖重写一次即可
  5. 若实现类实现的多个接口中,存在重复的默认方法,则实现类一定要对冲突的默认方法进行覆盖重写
  6. 一个类如果直接父类当中的方法,和接口当中的默认方法产生冲突,会优先使用父类中的方法
/* 上述 1-5 */
public interface MyInterfaceA {
    /*    错误写法,接口没有静态代码块
    static {

    }*/

/*    错误写法,接口没有构造方法
    public MyInterface(){

    }*/
    public abstract void methodA();

    public abstract void methodAbs();

    public default void methodDefault(){
        System.out.println("AAA");
    }
}

public interface MyInterfaceB {
    public abstract void methodB();

    public abstract void methodAbs();

    public default void methodDefault(){
        System.out.println("BBB ");
    }
}

public class MyInterfaceImpl 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 Father {
    public void method(){
        System.out.println("父类方法");
    }
}

public interface MyInterface {
    public default void method(){
        System.out.println("接口的默认方法");
    }
}

public class Son extends Father implements MyInterface {
    public static void main(String[] args) {
        Son son = new Son();
        son.method();   //父类方法
    }
}

接口之间的多继承

  1. 类与类之间是单继承的
  2. 类与接口之间是多实现
  3. 接口与接口时间是多继承

注意:

  1. 多个父接口当中的抽象方法重复,无影响
  2. 多个父接口当中的默认方法重复,则子接口必须进行默认方法的覆盖重写,必须加上default关键字
public interface MyInterfaceA {
    public abstract void methodA();

    public abstract void methodCommon();

    public default void methodDefault() {
        System.out.println("AAA");
    }
}

public interface MyInterfaceB {
    public abstract void methodB();

    public abstract void methodCommon();

    public default void methodDefault() {
        System.out.println("BBB");
    }
}

//这个子接口中有五个方法:
//methodA(),来源于接口A
// methodB(),来源于接口B
// methodCommon(),同时来源于接口A和接口B
// method(),来源于接口本身
//methodDefault(),同时来源于接口A和接口B,且为静态方法有冲突,必须覆盖重写
public interface MyInterface extends MyInterfaceA, MyInterfaceB {

    public abstract void method();

    @Override
    public default void methodDefault() {
        System.out.println("默认方法覆盖重写");
    }
}

多态

一个对象拥有多种形态,这就是对象的多态性
extends 继承或者 implements 实现,是多态性的前提

代码当中体现多态性:父类引用指向子类对象
格式:父类名称 对象名 = new 子类名称();接口名称 对象名 = new 实现类名称();
即:右侧子类对象被当做父类进行使用

public class Father {
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFaher(){
        System.out.println("父类特有方法");
    }
}

public class Son extends Father {
    @Override
    public void method(){
        System.out.println("子类名称");
    }
}

public class Demo01Multi {
    public static void main(String[] args) {
        //左侧父类的引用,指向右侧子类的对象
        Father obj = new Son();
        obj.method();   //Output:子类名称
        obj.methodFaher();  //Output:父类特有方法
    }
}

多态中成员变量的访问特点
访问成员变量的两种方法:

  1. 直接通过对象名称访问成员变量:等号左边是谁,优先用谁,没有则向上找
  2. 间接通过成员方法访问:看该方法属于谁,优先用谁,没有则向上找
    (子类覆盖重写,则访问子类方法;子类没有覆盖重写,则访问父类方法)
    编译看左边,运行也看左边
public class Father {
    int num = 10;
    
    public void showNum1(){
        System.out.println(num);
    }
    public void showNum2(){
        System.out.println(num);
    }
}

public class Son extends Father {
    int num = 20;
    int age = 18;
    
    @Override
    public void showNum2(){
        System.out.println(num);
    }
}

public class Demo02MultiField {
    public static void main(String[] args) {
        Father obj = new Son();
        System.out.println(obj.num);    //Output:10
        //System.out.println(obj.age);  //错误写法,不能访问到obj.num

        obj.showNum1();  //Output:10,子类没有覆盖重写,访问父类的方法
        obj.showNum2();  //Output:20,子类覆盖重写,则访问子类的方法
    }
}

多态中成员方法的访问特点
多态中成员方法的访问规则:new的是谁,就优先访问谁的方法,没有则向上找
编译看左边,运行看右边

public class Father {
    public void method(){
        System.out.println("父类方法");
    }
    public void methodFather(){
        System.out.println("父类特有方法");
    }
}

public class Son extends Father {
    @Override
    public void method(){
        System.out.println("子类方法");
    }
    public void methodSon(){
        System.out.println("子类特有方法");
    }
}

public class Demo03Multi {
    public static void main(String[] args) {
        Father obj = new Son();
        //编译看左边,父类中有method()方法,所以编译通过。运行看右边,子类中有method()方法,所以优先访问子类method()方法
        obj.method();   //Output:子类方法
        //编译看左边,父类中有methodFather()方法,所以编译通过。运行看右边,子类中没有methodFather()方法,则向上找,父类有则访问父类methodFather()方法
        obj.methodFather(); //Output:父类特有方法

        //编译看左边,父类中没有methodSon()方法,所以编译报错
        //obj.methodSon();  //错误写法
    }
}

使用多态的好处
在这里插入图片描述
若现在只需要调用 work() 方法,而对其他功能(比如具体哪个对的work方法)不关心

//若不使用多态
Teacher one = new Teacher();
one.work();	//讲课
Assistant two = new Assistant();
two.work();	//辅导

//若使用多态
Employee one = new Employee();
one.work();	//讲课
Employee two = new Employee();
two.work();	//辅导

所以使用多态的好处是:无论new的是哪个子类对象,等号左边的引用方法都不会改变

对象的向上转型
向下转型即为多态
父类名称 对象名 = new 子类名称();
创建子类对象,把它当做父类看待使用
向上转型一定是安全的。 (从小范围转换为大范围)

public abstract class Animal {
    public abstract void eat();
}

public class Cat extends Animal {
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
    //子类特有方法
    public void catchMouse(){
        System.out.println("猫捉老鼠");
    }
}

public class Demo04Main {
    public static void main(String[] args) {
        Animal animal = new Cat();	//创建一只猫,把它当做动物看待
        animal.eat();
        //animal.catchMouse();  //错误写法
    }
}

弊端:对象一旦向上转型为父类,就无法调用子类原本特有的内容
解决方案:对象的向下转型进行“还原”

对象的向下转型
子类名称 对象名 = (子类名称) 父类对象
将父类对象还原为本来的子类对象(“还原”是指:本来创建的就为该子类对象)

public abstract class Animal {
    public abstract void eat();
}

public class Cat extends Animal {
    @Override
    public void eat(){
        System.out.println("猫吃鱼");
    }
    //子类特有方法
    public void catchMouse(){
        System.out.println("猫捉老鼠哦");
    }
}

public class Dog extends Animal {
    @Override
    public void eat(){
        System.out.println("狗吃骨头");
    }
    //子类特有方法
    public void watchHouse(){
        System.out.println("狗看家");
    }
}

public class Demo04Main {
    public static void main(String[] args) {
        Animal animal = new Cat();

        //向下转型
        ((Cat) animal).catchMouse();    //Output:猫捉老鼠哦

        //向下转型
        Cat cat = (Cat)animal;
        cat.catchMouse();   //Output:猫捉老鼠哦

        //错误的向下转型,animal本身为Cat对象
        //Dog dog = (Dog)animal;    //编译不会报错,但运行会出现类转换异常java.lang.ClassCastException
    }
}

为防止向下类型转换错误,确保父类引用的对象本身为什么对象,采用 instanceof 关键字

对象名 instanceof 类型:得到一个boolean值,判断前面的对象能否当做后面类型的实例

public class Demo04Instanceof {

    public static void main(String[] args) {
        giveMeAPet(new Dog());
    }

    //当一个方法不知道其参数是谁的对象时,通过instanceof关键字进行判断
    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();
        }
    }
}

附:附一个利用多态和接口实现的实例,即实现笔记本通过USB接口,使用鼠标和键盘设备,代码如下:

//笔记本电脑类,使用USB接口
public class Laptop {
    public void powerOn(){
        System.out.println("笔记本电脑开机");
    }
    public void powerOff(){
        System.out.println("笔记本电脑关机");
    }
    //使用USB设备的方法,使用接口作为方法的参数
    public void useDevice(USB usb) {
        usb.open(); //打开设备
        if(usb instanceof Mouse) {
            Mouse mouse = (Mouse) usb;	//向下转型
            mouse.click();
        } else if(usb instanceof Laptop) {
            KeyBoard keyBoard = new KeyBoard();	//向下转型
            keyBoard.input();
        }
        usb.close();    //关闭设备
    }
}

//USB接口
public interface USB {
    //至于打开什么设备,怎么打开,根据设备不同是不同的,所以要使用抽象类
    public abstract void open();    //打开设备
    public abstract void close();   //关闭设备
}

//鼠标类,鼠标本身就是一种USB设备
public class Mouse implements USB {
    @Override
    public void open(){
        System.out.println("打开鼠标");
    }
    @Override
    public void close() {
        System.out.println("关闭鼠标");
    }
    public void click(){
        System.out.println("鼠标点击");
    }
}

//键盘类,键盘本身是一种USB设备
public class KeyBoard implements USB{
    @Override
    public void open(){
        System.out.println("打开键盘");
    }
    @Override
    public void close() {
        System.out.println("关闭键盘");
    }
    public void input(){
        System.out.println("键盘输入");
    }
}

public class DemoMain {
    public static void main(String[] args) {
        //创建笔记本
        Laptop laptop = new Laptop();
        laptop.powerOn();

        //创建鼠标,采用多态进行向上转型,把鼠标当做一种usb
        USB usbMouse = new Mouse();
        //使用鼠标设备
        laptop.useDevice(usbMouse);  //方法参数是USB类型,传递参数也是USB

        //创建键盘
        KeyBoard keyBoard = new KeyBoard();
        //使用键盘设备
        laptop.useDevice(keyBoard); //正确写法,方法参数是USB类型,传递参数是实现类对象,也是发生了向上转型
        //laptop.useDevice(new KeyBoard());   //正确写法,使用子类对象,匿名对象

        laptop.powerOff();
    }
}

//Output:
//笔记本电脑开机
//打开鼠标
//鼠标点击
//关闭鼠标
//打开键盘
//关闭键盘
//笔记本电脑关机

final 关键字

final 关键字表示最终的,不可改变的

final 关键字常见的四种用法:

  1. 修饰一个类
  2. 修饰一个方法
  3. 修饰一个局部变量
  4. 修饰一个成员变量

当 final 关键字修饰类时:
public final class 类名称() { }:指这个类不能拥有任何子类
注意:一个final 类,其中所有的成员方法都不能进行覆盖重写

当 finla 关键字修饰一个方法时:
修饰符 final 返回值类型 方法名称(参数列表) { }:指这个方法是最终方法,不能被覆盖重写(可以有子类,但子类不能对该 final 方法进行覆盖重写)

注意:对于类和方法来说,abstract 和 final 不能同时使用,相互矛盾

当 finla 关键字修饰一个局部变量时:
指该变量一旦被赋值,不可改变
对于基本类型来说,指变量当中的数据不可改变
对于引用类型来说,指变量当中的地址值不可改变

final int num1 = 10;
//num = 10;	//错误写法,不可被再次赋值

final int num2;
num2 = 200;	//正确写法,只能有唯一一次赋值
public class Student {
    public String name;

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

    public String getName() {
        return name;
    }

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

public class Demo03Main {
    public static void main(String[] args) {
        final Student stu1 = new Student("张三");
        System.out.println(stu1.getName());
        //stu1 = new Student("李四"); //错误写法,final的引用类型变量,地址值不可改变
        stu1.setName("李四"); //正确写法,引用类型地址值指向的内容可以随意改变
        System.out.println(stu1.getName());
    }
}

当 finla 关键字修饰一个成员变量时:
表示这个成员变量不可变,注意:

  1. 由于成员变量具有默认值,一旦用了 final 后不提供默认值,必须手动赋值
  2. final 成员变量,要么采用直接赋值,要么通过构造方法赋值
  3. 必须保证类当中所有重载的构造方法,都最终会对 final 的成员变量进行赋值
public class Person {
    //赋值方法一:
    //private final String name = "张三";

    private final String name;

    public Person(){
        name = "张三";
    }

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

    public String getName() {
        return name;
    }
    //不可再使用setter()方法
/*    public void setName(String name) {
        this.name = name;
    }*/
}

四种权限修饰符

四种权限修饰符:public > protected > (default) > private
default 不是关键字,指不写权限修饰符时的默认权限 (包访问权限)

权限publicprotected(default)private
同一个类
同一个包×
不同包子类××
不同包非子类×××

内部类

如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类
(如:身体与心脏的关系,汽车与发动机的关系)

分类:

  1. 成员内部类
  2. 局部内部类(包含匿名内部类)

1. 成员内部类

格式:

修饰符 class 类名称 {
	修饰符 class 内部类名称 {
		//...
	}
}

注意:内部类使用外部类可以随意访问,外部类使用内部类必须通过内部类对象

使用成员内部类的两种方式:

  1. 间接使用
    在外部类的方法当中使用内部类,main 只调用外部类的方法
  2. 直接使用
    外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
//外部类
public class Body {

    //成员内部类
    public class Heart {
        //内部类方法
        public void beat(){
            System.out.println("心脏跳动");
            System.out.println("我叫" + name);    //正确写法
        }
    }
    private String name;    //外部类成员变量
    //外部类方法
    public void methodBody(){
        System.out.println("外部类的方法");
        //Heart heart = new Heart();
        //heart.beat();
        new Heart().beat();	//可直接使用匿名内部类的方法
    }
}

public class Demo01Main {
    public static void main(String[] args) {
        Body body = new Body(); //外部类对象
        //1. 通过外部类对象调用外部类方法,里面间接使用内部类Heart
        body.methodBody();
        //2. 直接使用
        Body.Heart heart = new Body().new Heart();
        heart.beat();
    }
}

内部类的同名变量访问
若要在内部类方法中访问外部类成员变量,则格式为:外部类名称.this.变量名

public class Outer {
    int num = 10;   //外部类成员变量

    public class Inner {
        int num = 20;   //内部类成员变量

        public void methodInner(){
            int num = 30;   //内部类方法的局部变量
            System.out.println(num);    //30,局部变量,就近原则
            System.out.println(this.num);   //20,内部类的成员变量
            System.out.println(Outer.this.num);   //10,外部类的成员变量
        }
    }
}

public class Demo02Main {
    public static void main(String[] args) {
        Outer.Inner obj = new Outer().new Inner();
        obj.methodInner();
    }
}

2. 局部内部类
一个类定义在方法内部,则就是局部内部类。(只有当前所属方法才能使用该类)

格式:

修饰符 class 外部类名称 {
	修饰符 返回值类型 外部类方法名称 (参数列表) {
		class 局部内部类名称 {
			//...
		}
	}
}

注意:权限修饰符的使用规则:

  • 外部类:public / (default)
  • 成员内部类:public / protected / (default) / private
  • 局部内部类:什么都不能写
public class Outer {
    public void methodOuter(){
        class Inner{    //局部内部类
            int num = 10;
            public void methodInner(){
                System.out.println(num);    //10
            }
        }
        Inner inner = new Inner();
    }
}

public class Demo03Main {
    public static void main(String[] args) {
        Outer obj = new Outer();
        obj.methodOuter();
    }
}

注意:局部内部类如果要访问所在方法的局部变量,则这个局部变量必须是 有效final
(在Java 8 以前的版本,这种情况下所在方法的局部变量必须要加 final 修饰符,Java 8+ 开始,只要局部变量事实不变,可以省略 final 关键字)

原因:new 对象发生在堆内存中。局部变量是跟着方法走的,发生在栈内存中。方法运行结束后,立刻出栈,局部变量也会立刻消失,但 new 出来的对象会在堆当中持续存在,直到垃圾回收消失

public class Outer {
    public void methodOuter(){
        int num = 10;   //所在方法的局部变量
        //num = 20; //错误写法,不能重新赋值,改变局部变量的方法
        class Inner{
            public void methodInner(){
                System.out.println(num);
            }
        }
    }
}

3. 匿名内部类

如果接口的实现类,或者父类的子类,只需要使用唯一的一次,则这种情况下可以省略该类的定义,而使用匿名内部类

格式:

接口名称 对象名 = new 接口名称() {
	//覆盖重写所有抽象方法
};

注意:

  1. 匿名内部类,在创建对象时只能使用唯一一次。若希望多次创建对象且类的内容相同,则必须使用单独定义的实现类
  2. 匿名对象,在调用方法时只能调用唯一一次。若希望同一个对象,调用多次方法,则必须给对象取对象名
  3. 匿名内部类省略了实现类/子类名称,但匿名对象时省略了对象名称(注意两者不同)
public interface MyInterface {
    void method();  //抽象方法
}

/*public class MyInterfaceImpl implements MyInterface {
    @Override
    public void method() {
        System.out.println("实现类覆盖重写了方法");
    }
}*/

public class Demo05Main {
    public static void main(String[] args) {
/*      //写法一:
        MyInterfaceImpl impl = new MyInterfaceImpl();
        impl.method();
        //写法二:多态
        MyInterface obj = new MyInterfaceImpl();
        obj.method();   */

        //匿名内部类,但不是匿名对象,对象名叫objA
        MyInterface objA = new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类实现了方法");
            }
        };
        objA.method();
        
        //匿名内部类,而且省略了对象名称,也是匿名对象
        new MyInterface() {
            @Override
            public void method() {
                System.out.println("匿名内部类实现了方法");
            }
        }.method();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值