接口、继承、多态(this可看作当前对象,super看成父类对象)

127 阅读22分钟

一、继承

概述、使用继承的好处

可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展

(1)通过继承创建的新类称为“子类”或“派生类”。

(2)被继承的类称为“基类”、“父类”或“超类”。

(3)一个子类只能有一个父类

继承的优势可以把相同的代码定义在父类中,子类可以直接继承使用。
这样就可以提高代码的复用性:相同代码只需要在父类中写一次就可以了。
设计规范、内存运行原理
特点
继承后:成员变量、成员方法的访问特点

成员变量访问特点:就近原则。子类有找子类,子类没有找父类,父类没有就报错。如果一定要申明访问父类的成员变量可以使用:super.父类成员变量。

public class TestDemo {
    public static void main(String[] args) {
        Cat cat = new Cat();
        cat.show();
    }
}
class Animal{
    public String name = "动物名称";
}
class Cat extends Animal{
    public String name = "子类名称";
    public void show(){
        String name = "局部名称";
        System.out.println(name); // 局部名称
        System.out.println(this.name); // this代表了当前对象的引用,可以用于访问当前子类对象的成员变量。
        System.out.println(super.name); // super代表了父类对象的引用,可以用于访问父类中的成员变量。
        // System.out.println(name2); // 报错了!
    }
}

继承后-成员方法的访问特点:就近原则。子类对象优先使用子类已有的方法

继承后:方法重写

方法重写的具体要求:

1.子类重写方法的名称和形参列表必须与父类被重写方法一样。

2.子类重写方法的返回值类型申明要么与父类一样,要么比父类方法返回值类型范围更小。

3.子类重写方法的修饰符权限应该与父类被重写方法的修饰符权限相同或者更大。

4.子类重写方法申明抛出的异常应该与父类被重写方法申明抛出的异常一样或者范围更小!

方法重写的规范:

1.加上@Override注解。

2.建议“申明不变,重新实现”。

super调用父类被重写的方法。

public class ExtendsDemo02 {
    public static void main(String[] args) {
        SportMan yaoMing = new SportMan();
        yaoMing.go();
    }
}


class SportMan extends People{
    @Override
    public void run(){
        System.out.println("运动员跑的贼快~~~~~");
    }


    public void go(){
        super.run(); // 父类的
        run(); // 子类的
    }
}


class People{
    public void run(){
        System.out.println("人会跑~");
    }

静态方法和私有方法是否可以被重写?不可以

子类不能继承的父类的内容

子类不能继承父类的构造器:子类有自己的构造器。

子类不能继承父类的私有成员(私有成员变量,私有成员方法)

子类是否可以继承父类的静态成员?子类是不能继承父类的静态成员的,子类只是可以访问父类的静态成员,父类的静态成员只有一份可以被子类共享访问。

继承后:子类构造器的特点

子类的全部构造器默认一定会先访问父类的无参数构造器,再执行子类自己的构造器。为什么子子类构造器会先调用父类构造器?

1.子类的全部构造器的第一行默认有一个super()调用父类的无参数构造器,写不写都存在!

2.子类继承父类,子类就得到了父类的属性和行为。当调用子类构造器初始化子类对象数据的时候,必须先调用父类构造器初始化继承自父类的属性和行为。

可以在子类构造器中通过super(...)根据参数选择调用父类的有参构造器

继承后:子类构造器访问父类有参构造器
子类继承父类构造器

二、Object类

是所有类的父类,可以使用Object类型的变量引用任何类型的对象:

bject obj = new Employee ("Harry Hacker", 35000);

当然,Object类型的变量只能用于作为各种值的通用持有者。要想对其中的内容进行具体的操作,还需要清楚对象的原始类型,并进行相应的类型转换:

Employee e = (Employee) obj;

在Java中,只有基本类型是对象,例如,数值、字符和布尔类型的值都不是对象。

Object类没有定义属性,一共有13个方法,具体的类定义结构如下图:

三、对象类型的转换

1、向上转型

把子类对象赋值给父类类型的变量

2、向下转型

把较抽象的类转换为较具体的类

四、判断父类对象是否为子类对象的实例

instanceof关键字用来判断某个变量是否为某个类型的一个关键字,测试左边的对象是否为右边的实例。返回值为boolean类型

o instanceof Character
五、方法的重写重载

1、重写

子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!

好处:子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。

重写方法不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常。例如: 父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常。

在面向对象原则里,重写意味着可以重写任何现有方法。

2、重载(Overload)

是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表

最常用的地方就是构造器的重载

一、什么是重写

在子类中创建了一个与父类中名称相同、返回值类型相同、参数列表的方法相同,只是方法体中的实现不同,以实现不同于父类的功能,这种方式被称为方法重写(override),又称为方法覆盖、方法复写。

二、为什么需要重写

原有的方法无法满足新的需求,需要对这个方法进行改良来满足新的需求。重写是出现在子类继承父类方法中。可以看出,重写就是子类继承的父类方法无法满足子类的新的功能需求,子类对父类的这个方法进行改良重写的一种方式。子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。

三、重写的条件/规则

重写只跟非静态方法(成员方法)有关,而与静态方法无关:

静态方法和非静态方法是不一样的:在静态方法中,方法的调用只和左边声明的对象类型有关,而与右边无关,是哪个类型,就调用对应的方法。

四、重写的注意事项

重写的方法可以使用 @Override 注解来标识。

构造方法不能被重写。

声明为 final 的方法不能被重写。

子类和父类在同一个包中时,子类可以重写父类除了声明为 private 和 final 方法的其他方法。

子类和父类不在同一个包中时,子类只能重写父类的声明为 public 和 protected 的非 final 方法。

如果不能继承一个方法,则不能重写这个方法。重写是在继承的基础上,如果方法无法被继承那么就无法重写

五、super关键字的使用

当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

六、多态

父类引用或者接口的引用指向了自己的子类对象。//Animal a = new Cat();

多态的入门概述

/**
    目标:多态的入门概述。


    面向对象的三大特征:封装,继承,多态。


    多态的形式:
        父类类型 对象名称 = new 子类构造器;
        接口    对象名称 = new 实现类构造器;


        父类类型的范围 > 子类类型范围的。


    多态的概念:
        同一个类型的对象,执行同一个行为,在不同的状态下会表现出不同的行为特征。


    多态的识别技巧:
        对于方法的调用:编译看左边,运行看右边。
        对于变量的调用:编译看左边,运行看左边。


    多态的使用前提:
        (1)  必须存在继承或者实现关系。
        (2)  必须存在父类类型的变量引用子类类型的对象。
        (3)  需要存在方法重写。


    小结:
        记住多态的形式,识别,概念等语法即可!
 */
public class PolymorphicDemo {
    public static void main(String[] args) {
        //  父类类型 对象名称 = new 子类构造器;
        Animal dlam = new Cat();
        dlam.run(); // 对于方法的调用:编译看左边,运行看右边。
        System.out.println(dlam.name); // 对于变量的调用:编译看左边,运行看左边。


        Animal taiDi = new Dog();
        taiDi.run(); // 对于方法的调用:编译看左边,运行看右边。
        System.out.println(taiDi.name); // 对于变量的调用:编译看左边,运行看左边。
    }
}


class Dog extends Animal{
    public String name = "🐶名称Dog";
    @Override
    public void run(){
        System.out.println("🐕跑的贼快~~~~!");
    }
}


class Cat extends Animal{
    public String name = "🐱名称Cat";
    @Override
    public void run(){
        System.out.println("🐱跑的飞快~~~~!");
    }
}


class Animal{
    public String name = "动物名称Animal";
    public void run(){
        System.out.println("动物跑!");
    }
}

利弊

package com.itheima._02多态的优劣势;


/**
    目标:多态的优劣势。


    优势:
        1.在多态形式下,右边对象可以实现组件化切换,业务功能也随之改变,
           便于扩展和维护。可以实现类与类之间的解耦。
        2.实际开发的过程中,父类类型作为方法形式参数,传递子类对象给方法,
            可以传入一切子类对象进行方法的调用,更能体现出多态的扩展性与便利。


    劣势:
        1.多态形式下,不能直接调用子类特有的功能。编译看左边!! 左边
        父类中没有子类独有的功能,所以代码在编译阶段就直接报错了!
    小结:
         记住以上语法!
 */
public class PolymorphicDemo {
    public static void main(String[] args) {
        //  父类类型 对象名称 = new 子类构造器;
        Animal dlam = new Dog();
        dlam.run(); // 对于方法的调用:编译看左边,运行看右边。
        // dlam.lookDoor(); // 报错了,多态形式下,编译看左边,左边没有独有功能


        Animal taidi = new Dog();
        go(taidi);


        Animal tom = new Cat();
        go(tom);
    }


    // 开发一个游戏 所有动物都可以进来比赛
    public static void go(Animal d){
        System.out.println("开始。。");
        d.run();
        System.out.println("结束。。");
    }
}


class Dog extends Animal{
    @Override
    public void run(){
        System.out.println("🐕跑的贼快~~~~!");
    }


    // 独有功能
    public void lookDoor(){
        System.out.println("🐶看门");
    }
}


class Cat extends Animal{
    @Override
    public void run(){
        System.out.println("🐱跑的飞快~~~~!");
    }


    // 独有功能
    public void catchMouse(){
        System.out.println("🐱抓🐀");
    }
}


class Animal{
    public void run(){
        System.out.println("动物跑!");
    }
}

七、抽象类与接口

1、抽象类

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

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

由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类。

在Java语言中使用abstract class来定义抽象类

抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

抽象类总结规定

1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。最好将类中的域(成员变量)标记为private,而方法标记为public

2. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

3. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。

4. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。

5. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

2、接口

1)是一个抽象类型,由全局常量和公共的抽象方法所组成,以interface来声明

2)一个类通过继承接口的方式,从而来继承接口的抽象方法

3)一个接口只有方法的特征没有方法的实现,因此这些方法可以在不同的地方被不同的类实现,而这些实现可以具有不同的行为(功能)

4)是解决Java无法使用多继承的一种手段,但是接口在实际中更多的作用是制定标准的。或者我们可以直接把接口理解为100%的抽象类,既接口中的方法必须全部是抽象方法。(JDK1.8之前可以这样理解)

5)接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

6)接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象

3、接口与类的相似和区别

相似

一个接口可以有多个方法。

接口文件保存在 .java 结尾的文件中,文件名使用接口名。

接口的字节码文件保存在 .class 结尾的文件中。

接口相应的字节码文件必须在与包名称相匹配的目录结构中。

区别

接口不能用于实例化对象。

接口没有构造方法。

接口中所有的方法必须是抽象方法。

接口不能包含成员变量,除了 static 和 final 变量。

接口不是被类继承了,而是要被类实现。

接口支持多继承。

4、接口的特点

就像一个类一样,一个接口也能拥有方法和属性,但是在接口中声明的方法默认是抽象的(即只有方法标识符,而没有方法体)。
1.接口被用来描述一种抽象。接口是隐式抽象的,当声明一个接口的时候,不必使用abstract关键字。
2.接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是public abstract,其它修饰符都会报错)。
3.接口中可含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
4.接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。
5.接口中的方法都是公有的。

5、为什么要用接口

1.接口被用来描述一种抽象。
2.因为Java不像C++一样支持多继承,所以Java可以通过实现接口来弥补这个局限。
3.接口也被用来实现解耦。
4.接口被用来实现抽象,而抽象类也被用来实现抽象,为什么一定要用接口呢?接口和抽象类之间又有什么区别呢?原因是抽象类内部可能包含非final的变量,但是在接口中存在的变量一定是finalpublicstatic的。

6、抽象类和接口的区别

1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
2. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
3. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
4. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。
注:JDK 1.8 以后,接口里可以有静态方法和方法体了。

7、接口的语法实现

在接口中的所有方法都必须只声明方法标识,而不要去声明具体的方法体,因为具体的方法体的实现是由继承该接口的类来去实现的,因此,接口并不用管具体的实现。接口中的属性默认为Public Static Final.一个类实现这个接口必须实现这个接口中定义的所有的抽象方法。

声明

[可见度] interface 接口名称 [extends 其他的接口名] {
        // 声明变量
        // 抽象方法
}

一个简单的接口就像这样:拥有全局变量和抽象方法

inerface in1{
  final int a=10;
  void display(;)
}

实现(要把接口规定的抽象方法都实现了)

class Test implements in1{
  public void display(){
    System.out.prinntln("hello");
  }
}

注意事项:

1)一个类只能继承一个类,但可以实现多个接口

2)一个接口能继承另外一个接口(extends)

3)接口允许多继承。在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口:

public interface Hockey extends Sports, Event

接口的概述和定义

/**
     目标:接口的概述和定义等。(以理解和记住语法为主)


     什么是接口?
          接口是更加彻底的抽象,接口中全部是抽象方法和常量,没有其他成分。(JDK 1.8之前)


     接口有啥用?
          接口体现的是规范思想,实现接口的类必须重写完接口中全部的抽象方法。
          规范 == 约束。
          接口称为被实现,实现接口的类称为实现类。


     定义接口的格式:
          修饰符 interface 接口名称{


          }
          interface:定义接口的关键字。


     接口中的成分研究(JDK 1.8之前):
          1.抽象方法
               a.接口中的抽象方法默认会加上public abstract修饰,所以可以省略不写。
          2.常量
            常量:是指有public static final修饰的成员变量,有且仅能被赋值一次,值不能改变。
            常量的名称规范上要求全部大写,多个单词下划线连接。
            常量修饰的public static final 可以省略不写,默认会加上。
     小结:
          定义接口使用的关键字:interface
          接口中的成分在JDK 1.8之前只能有:常量和抽象方法。
          在接口中常量的修饰符:public static final 可以省略不写,默认会加上。
          在接口中抽象方法的修饰符:public abstract 可以省略不写,默认会加上。


 */
public interface InterfaceDemo {
     // 2.常量
     // 只有一份,在执行的过程中其值必须有,但是不能改变!
     // 常量是public static final修饰
     // 常量的名称建议字母全部大写,多个单词用“_”连接
     // 在接口中常量可以省略public static final不写,默认会加上该三个修饰符!
     //public static final String SCHOOL_NAME = "黑马";
     String SCHOOL_NAME = "黑马";




     // 1.抽象方法
     // public abstract void run();
     // 接口中的抽象方法默认会加上public abstract修饰,所以可以省略不写。
     void run();
     void work();
}

接口的基本实现

/**
    目标:接口的基本实现。


    接口是用来被类实现的。


    引入:
        类与类是继承关系:一个类只能直接继承一个父类。
        类与接口是实现关系:一个类可以实现多个接口。
        实现接口的类称为“实现类”。


        子类   继承   父类
        实现类 实现   接口


    实现类实现接口的格式:
        修饰符 class 实现类名称 implements 接口1,接口2,接口3,....{


        }
        implements:实现的含义。
        接口是可以被多实现的:一个类可以实现多个接口。


    小结:
        接口是用类被实现的,实现接口的类称为实现类。
        实现接口的关键字是:implements。
        接口是可以被类多实现的。
        注意:一个类实现接口必须重写完接口中全部抽象方法,否则这个类必须定义成抽象类!!
 */
public class InterfaceDemo {
    public static void main(String[] args) {
        PingPongMan zjk = new PingPongMan("张继科");
        zjk.run();
        zjk.win();
    }
}


//abstract class BasketBall implements SportMan{
//}


// 实现类 实现 SportMan接口
// 一个类实现接口必须重写完接口中全部抽象方法,否则这个类必须定义成抽象类!!
class PingPongMan implements SportMan{
    private String name;
    public PingPongMan(String name){
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(name+"必须天天运动。正在🏃训练~~~‍");
    }


    @Override
    public void win() {
        System.out.println(name+"参加比赛中~~~‍");
    }
}


// 定义一个接口:表示运动员的规范
interface SportMan{
    void run(); // 跑步
    void win(); // 比赛得奖
}

接口的多实现

/**
     目标:接口的多实现介绍。


     实现类实现接口的格式:
         修饰符 class 实现类名称 implements 接口1,接口2,接口3,....{


         }


     类与类是单继承。
     类与接口是多实现。


    小结:
        一个类可以实现多个接口。
        一个类如果实现了多个接口,必须重写完全部接口中的全部抽象方法
        否则这个类必须定义抽象类。
 */
public class InterfaceDemo {
    public static void main(String[] args) {
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.win();
        zjk.rule();
    }
}


class PingPongMan implements SportMan , Law{


    @Override
    public void rule() {


    }


    @Override
    public void run() {


    }


    @Override
    public void win() {


    }
}


interface Law{
    void rule();
    void run();
}


interface SportMan{
    void run();
    void win();
}

接口与接口的多继承

/**
    目标:接口与接口的多继承。


    引入:
        类与类是单继承关系:一个类只能继承一个直接父类。
        类与接口是多实现关系:一个类可以实现多个接口。
        接口与接口是多继承关系:一个接口可以继承多个接口。
 */
public class InterfaceDemo {
}


class PingPongMan implements SportMan{


    @Override
    public void eat() {


    }


    @Override
    public void rule() {


    }


    @Override
    public void run() {


    }


    @Override
    public void goAbroad() {


    }
}


interface Food{
    void eat();
}


interface Law{
    void rule();
}


// 接口与接口的多继承!
interface SportMan extends Law , Food {
    void run();
    void goAbroad();
}

JDK8之后的接口新增方法

/**
    目标:JDK 1.8开始之后接口新增的三种方法(理解语法,属于Java自己的技术)


    引入:
        JDK 1.8之前接口中只能是抽象方法,常量。
        JDK 1.8开始之后接口不再纯洁了。
        JDK 1.8开始之后接口新增了如下三种方法。


    a.默认方法(就是之前写的普通实例方法)
        -- 必须用default修饰,默认会public修饰
        -- 必须用接口的实现类的对象来调用。


    b.静态方法
        -- 默认会public修饰
        -- 注意:接口的静态方法必须用接口的类名本身来调用。


    c.私有方法(就是私有的实例方法): JDK 1.9才开始有的。
        -- 只能在本类中被其他的默认方法或者私有方法访问。
 */
public class InterfaceDemo {
    public static void main(String[] args) {
        // 1.默认方法调用:必须用接口的实现类对象调用。
        PingPongMan zjk = new PingPongMan();
        zjk.run();
        zjk.work();


        // 2.接口的静态方法必须用接口的类名本身来调用。
        InterfaceJDK8.inAddr();
    }
}
class PingPongMan implements InterfaceJDK8{
    @Override
    public void work() {
        System.out.println("工作中。。。");
    }
}


interface InterfaceJDK8{
    // 之前的抽象方法!!
    void work();


    // a.默认方法(就是之前写的普通实例方法)
    // 必须用接口的实现类的对象来调用。
    default void run(){
        go();
        System.out.println("开始跑步🏃‍~~~~");
    }


    // b.静态方法
    // 注意:接口的静态方法必须用接口的类名本身来调用
    static void inAddr(){
        System.out.println("我们在吉山区~~~~");
    }


    // c.私有方法(就是私有的实例方法): JDK 1.9才开始有的。
    //  -- 只能在本接口中被其他的默认方法或者私有方法访问。
    private void go(){
        System.out.println("开始。。");
    }
}

实现多个接口的使用注意实现

/**
    拓展:实现多个接口的使用注意实现。(非常偏的语法,理解和了解即可)


    1.如果实现了多个接口,多个接口中存在同名的静态方法并不会冲突,
        原因是只能通过各自接口名访问静态方法。


    2.当一个类,既继承一个父类,又实现若干个接口时,(重点)
        父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。


    3.当一个类实现多个接口时,多个接口中存在同名的默认方法。
        实现类必须重写这个方法。


    4.接口中,没有构造器,不能创建对象。(重点)
        接口是更彻底的抽象,连构造器都没有,自然不能创建对象!!


 */
public class InterfaceDemo {
    public static void main(String[] args) {
        //C.test(); // 报错 ,接口的静态方法,只能有接口名称调用!
        A.test();
        B.test();


        Cat cat = new Cat();
        cat.run();


        // A2 a = new A2(); // 接口是更彻底的抽象,连构造器都没有,自然不能创建对象!!
    }
}


/**
 3.当一个类实现多个接口时,多个接口中存在同名的默认方法。
 实现类必须重写这个方法。
 */
interface A2{
    default void test(){}
}
interface B2{
    default void test(){}
}
class C2 implements A2 , B2{
    @Override
    public void test() {
    }
}










/**
 2.当一个类,既继承一个父类,又实现若干个接口时,(重点)
 父类中的成员方法与接口中的默认方法重名,子类就近选择执行父类的成员方法。
 */
interface A1{
    default void run(){
        System.out.println("输出干爹的run()方法~~~~");
    }
}
class Animal{
    public void run(){
        System.out.println("输出亲爹Animal的run()方法~~~~");
    }
}
class Cat   extends Animal  implements A1  {
}




/**
 1.如果实现了多个接口,多个接口中存在同名的静态方法并不会冲突,
    原因是只能通过各自接口名访问静态方法。
 */
interface A{
    static void test(){
        System.out.println("A");
    }
}
interface B{
    static void test(){
        System.out.println("B");
    }
}
class C implements A , B{
}