详解Java之继承与多态

目录

继承

派生类和基类各部分执行顺序

protected

访问权限总结

final关键字

组合

多态

向上转型

向下转型

动态绑定

静态绑定

方法重载

方法重写

super关键字

super和this的对比

在构造方法中调用重写方法


继承

继承是为了解决多个类具有一些相同的属性和方法而造成的代码冗余问题,将这些具有相同属性和方法分离出来设置为一个类,然后让有这些属性和方法的类来继承它。

被继承的类成为父类,基类或超类,而继承的类成为子类或派生类。

语法规则

class 子类 extends 父类 {
}
使用 extends 指定父类.
Java 中一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
子类会继承父类的所有 public 的字段和方法.
对于父类的 private 的字段和方法, 子类中是无法访问的.
子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用

super的使用

访问父类的成员属性:super.data

访问父类的成员方法:super.func()

派生类和基类各部分执行顺序

举例

java 复制代码
class Animal{
    public String name;

    public Animal(String name) {
        this.name = name;
        System.out.println("Animal的构造方法");
    }

    {
        System.out.println("Animal的实例代码块");
    }

    static{
        System.out.println("Animal的静态代码块");
    }
}

class Dog extends Animal{
    public Dog(String name){
        super(name);
        System.out.println("Dog的构造方法");
    }

    {
        System.out.println("Dog的实例代码块");
    }

    static{
        System.out.println("Dog的静态代码块");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog=new Dog("黑子");
    }
}

输出结果:

protected

对于类的调用者来说, protected 修饰的字段和方法是不能访问的;

对于类的 子类 和 同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的。

访问权限总结

Java 中对于字段和方法共有四种访问权限

private: 类内部能访问, 类外部不能访问
默认(也叫包访问权限): 类内部能访问, 同一个包中的类可以访问, 其他类不能访问.
protected: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.
public : 类内部和类的调用者都能访问

final关键字

当final修饰一个变量或者字段的时候,表示不可被修改(常量)。

final关键字也能修饰类,此时表示被修饰的类不能被继承。

我们平时使用的String字符串类,就是用final修饰的,不能被继承。

此时的array变量保存的是地址,指向一段内存,因为array被final修饰,因此不能修改的是array的指向,但是array保存的地址指向的空间的内容可以被修改!!!

组合

和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果.

例如表示一个学校:

java 复制代码
public class Student { 
 ...
}
public class Teacher { 
 ...
}
public class School {
    public Student[] students;
    public Teacher[] teachers;
}

组合表示has-a语义;

继承表示is-a语义。

多态
向上转型

向上转型发生的时机:

直接赋值;方法传参;方法返回。

向下转型

举例

java 复制代码
class Animal {
    protected String name;
    public Animal(String name) {
        this.name = name;
    }
    public void eat(String food) {
        System.out.println("我是一只小动物");
        System.out.println(this.name + "正在吃" + food);
    }
}
// Bird.java 
class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }

    public void eat(String food) {
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }

    public void fly() {
        System.out.println(this.name + "正在飞");
    }
}

如果我们将Bird向上转型为Animal,可以调用eat方法,但是不能调用fly方法了,会编译出错

编译过程中, animal 的类型是 Animal, 此时编译器只知道这个类中有一个 eat 方法, 没有 fly 方法.

虽然 animal 实际引用的是一个 Bird 对象, 但是编译器是以 animal 的类型来查看有哪些方法的.

对于 Animal animal = new Bird("小鸟") 这样的代码,编译器检查有哪些方法存在, 看的是 Animal 这个类型执行时究竟执行父类的方法还是子类的方法, 看的是 Bird 这个类型。

解决方法:向下转型

输出结果:

向下转型是不靠谱的,比如

Animal animal = new Cat(" 小猫 ");
Bird bird = (Bird)animal;
bird.fly();

因为Cat类中没有fly方法,因此这样会执行出错,抛异常。

动态绑定

举例

java 复制代码
class Animal{
    String name;

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

    public void eat(String food){
        System.out.println("我是动物");
        System.out.println(this.name+"正在吃"+food);
    }
}

class Bird extends Animal{
    public Bird(String name){
        super(name);
    }

    public void eat(String food){
        System.out.println("我是小鸟");
        System.out.println(this.name+"正在吃"+food);
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal=new Animal("动物");
        animal.eat("小麦");
        Animal animal1=new Bird("小鸟");
        animal1.eat("谷物");
    }
}

运行结果:

我们惊奇的发现,虽然animal和animal1都是Animal类型的引用,但是animal1的eat方法调用结果与animal的eat方法调用结果不同,即animal指向Animal类型的实例,animal.eat()调用了父类的实例,animal1指向Bird类型的实例,animal1.eat()调用了子类的实例。

因此,在Java中,调用某个类的方法,究竟执行了哪段代码(是父类方法的代码还是子类方法的代码),要看这个引用指向的是父类对象还是子类对象,这个过程是程序运行时决定的(而不是编译期间),因此称之为 动态绑定。

静态绑定

静态绑定发生在编译时期,也就是说,在编译时就已经确定了要调用的方法。这通常适用于那些不需要在运行时根据对象的实际类型来确定方法的情况。静态绑定主要发生在以下几种情况:

private方法: 因为private方法是不可见的,所以它们不能被继承,调用它们的引用在编译时就已知,因此是静态绑定的。
final方法: final方法不能被子类覆盖,所以编译器在编译时就可以确定要调用的方法。
static方法: 静态方法属于类,不属于任何对象实例,且可以通过类名直接调用。因此,编译器在编译时就能确定调用哪个方法。
**构造方法:**虽然构造方法看起来像是方法,但实际上它们是在对象创建时调用的特殊方法。因为构造方法不是通过对象实例调用的,所以也是静态绑定的。

方法重载

重载是指在同一个类中,允许多个同名方法同时存在,但要求它们的参数列表不同(参数的个数、类型或顺序至少有一项不同)。重载与方法的返回类型无关,即两个方法可以有相同的名称和不同的返回类型,但如果它们的参数列表相同,则不能构成重载。重载是编译时多态的体现。

举例

java 复制代码
class Test {  
    void show(int a) {  
        System.out.println("整数:" + a);  
    }  
  
    void show(String s) {  
        System.out.println("字符串:" + s);  
    }  
  
    // 下面这个方法是合法的重载,因为参数列表不同(参数类型不同)  
    void show(double a) {  
        System.out.println("双精度数:" + a);  
    }  
  
    // 如果下面这个方法被添加到类中,它将导致编译错误,因为与第一个方法参数列表相同  
    // void show(int a) { ... }  
}
方法重写

针对刚才的 eat 方法来说:

子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 覆写/重写/覆盖(Override).

关于重写的注意事项

  1. 重写和重载完全不一样,不要混淆.

  2. 普通方法可以重写, static 修饰的静态方法不能重写.

  3. 重写中子类的方法的访问权限不能低于父类的方法访问权限.

  4. 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外).

5.被private修饰的方法不能进行重写。

6.被final修饰的方法不能进行重写,此时这个方法叫做密封方法。

7.访问修饰限定符权限大小:private < 默认权限 < protected < public。

8.方法的返回值可以不同,但是必须是父子关系,或者称为协变。

9.构造方法不能进行重写。

举例

java 复制代码
class Animal {  
    void eat() {  
        System.out.println("动物吃东西");  
    }  
}  
  
class Dog extends Animal {  
    // Dog类重写了Animal类的eat方法  
    @Override  
    void eat() {  
        System.out.println("狗吃骨头");  
    }  
}  
  
// 使用  
Animal myDog = new Dog();  
myDog.eat(); // 输出:狗吃骨头(尽管引用类型是Animal,但实际对象类型是Dog,体现了多态性)
super关键字

前面的代码中由于使用了重写机制, 调用到的是子类的方法. 如果需要在子类内部调用父类方法怎么办? 可以使用super 关键字.

super 表示获取到父类实例的引用. 涉及到两种常见用法:

  1. 使用了 super 来调用父类的构造器
java 复制代码
public Bird(String name) {
    super(name);
}
  1. 使用 super 来调用父类的普通方法
java 复制代码
public class Bird extends Animal {
    public Bird(String name) {
        super(name);
    }
    @Override
    public void eat(String food) {
        // 修改代码, 让子调用父类的接口. 
        super.eat(food);
        System.out.println("我是一只小鸟");
        System.out.println(this.name + "正在吃" + food);
    }
}

在这个代码中 , 如果在子类的 eat 方法中直接调用 eat ( 不加 super), 那么此时就认为是调用子类自己的 eat ( 也就是递归了). 而加上 super 关键字 , 才是调用父类的方法.

super和this的对比
在构造方法中调用重写方法
java 复制代码
class B {
    public B() {
        // do nothing 
        func();
    }
    public void func() {
        System.out.println("B.func()");
    }
}
class D extends B {
    private int num = 1;
    @Override
    public void func() {
        System.out.println("D.func() " + num);
    }
}
public class Test {
    public static void main(String[] args) {
        D d = new D();
    }
}

输出结果:

解释:

构造 D 对象的同时, 会调用 B 的构造方法.

B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func。

此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0。

相关推荐
抠脚小弟1 分钟前
java学习-idea编辑器基础使用设置
java·学习·intellij-idea
2401_857297917 分钟前
招联金融内推-2025校招
java·前端·算法·金融·求职招聘
2401_857297917 分钟前
2025秋招内推|招联金融
java·前端·算法·金融·求职招聘
"Return"8 分钟前
小红书2024秋招后端开发(Java工程师、C++工程师等)
java·开发语言·c++
秋夫人14 分钟前
asm技术简介
java
LearnTech_12328 分钟前
【学习笔记】手写 Tomcat 七
java·开发语言·笔记·tomcat·手写tomcat
极客先躯42 分钟前
高级java每日一道面试题-2024年9月29日-数据库篇-索引怎么定义,分哪几种?
java·数据结构·数据库·分类·索引·全文索引·聚集索引
爱吃香菜¹44 分钟前
【数据结构】对象的比较
java·开发语言·数据结构
哈哈皮皮虾的皮1 小时前
树的数据结构有哪些应用场景?
java·数据结构·安全
yicj1 小时前
SpringBoot3 Swagger笔记整理
java·springboot·swagger