JAVA继承

为什么需要继承

我们先看两段代码:

public class Dog {
    public String name;
    public int age;

    public void eat(){
        System.out.println(this.name + "正在吃饭");
    }

    public void bark(){
        System.out.println(this.name + "正在汪汪叫");
    }
}
public class Cat {
    public String name;
    public int age;
    
    public void eat(){
        System.out.println(this.name + "正在吃饭");
    }
    public void mimi(){
        System.out.println(this.name + "正在咪咪叫");
    }
}

我们会发现这两段代码中会有大量的重复,那么有没有办法将这些共性提取出来呢?在面向对象思想中提出了继承的概念,专门用来进行共性的抽取,实现代码复用

什么是继承

继承是面向对象编程中的一个重要概念。它指的是一个类(称为子类派生类)可以继承另一个类(称为父类基类)的特征和行为。通过继承,子类可以重用父类的代码,并且可以添加或修改自己的行为,以满足特定的需求。

例如上面的代码中,猫和狗都是动物,可以把属于动物的共性给提取出来:
在这里插入图片描述
上述图示中,DogCat都继承了Animal类,其中Animal称为父类(基类),DogCat都是子类(派生类).
我们可以发现,继承最大的特点就是:实现代码复用

继承的语法

在JAVA中,继承需要用到extends关键字,具体如下:

修饰符 class 子类 extends 父类 {
// ... 
}

对上面的案例使用继承重新设计:

//Test.java
public class Test {
    public String name;
    public int age;

    public void eat(){
        System.out.println(this.name + "正在吃饭");
    }
}


//Dog.java
public class Dog extends Test{
    public void bark(){
        System.out.println(this.name + "正在汪汪叫");
    }
}


//Cat.java
public class Cat extends Test{
    public void mimi(){
        System.out.println(this.name + "正在咪咪叫");
    }
}

特点:
1.继承后,父类中的成员变量和成员方法都会继承到子类中
2.子类继承父类之后,一定要添加新的子类特有的成员,不然就没必要继承了

父类成员的访问

子类访问父类中的成员

1.当子类中不存在与父类相同的成员变量时:

//Base.java
public class Base {
    int a;
    int b;
}

=======================================================

//Derived.java
public class Derived extends Base{
    int c;
    public void method(){
        a = 10;//访问父类的a
        b = 20;//访问父类的b
        c = 30;//访问子类自己的c
    }
}

2.子类与父类的成员变量相同:

//Base.java
public class Base {
    int a;
    int b;
}
 
=======================================================

//Derived.java
public class Derived extends Base{
    int a;
    int b;
    int c;
    public void method(){
        a = 10;// 访问从父类继承的a,还是子类自己新增的a?
        b = 20;// 访问从父类继承的b,还是子类自己新增的a?
        c = 30;// 父类没有,肯定访问子类自己的
    }
}

此时,父类和子类中有同名的变量,优先访问的是子类的变量,原因很简单,这里的method方法中,所有的变量都默认是this.变量,如下:

public void method(){
        this.a = 10;
        this.b = 20;
        this.c = 30;
}

而this关键字是 正在被引用的子类 的对象,所以访问子类的a和b,那如果我们要在这里访问父类的变量该怎么办?
答:用super关键字:

public void method(){
        super.a = 10;
        super.b = 20;
        this.c = 30;
}

super关键字

在Java中,由于场景需要,子类可能会和父类拥有同名的成员,子类如果想要直接访问父类的成员是做不到的,Java提供了super关键字用来在子类中访问父类的成员.
我们来看一段代码:

//Base.java
public class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("父类中的methodA方法");
    }
    public void methodB(){
        System.out.println("父类中的methodB方法");
    }
}

//Derived.java
public class Derived extends Base{
    int a;
    char b;

    public void methodA(int a){
        System.out.println("子类中的methodA方法");
    }
    @Override
    public void methodB(){
        System.out.println("子类类中的methodB方法");
    }

    public void methodC(){
        a = 10;
        b = 20;
        
        methodA(1);
        methodA();
        
        methodB();
        super.methodB();
        

    }
}

可以看到
1.子类中有和父类同名且同同类型的变量a和同名且但不同类型的变量b,此时都等价于this.a和this.b,所以访问的是子类的a和b,
2.子类中有和父类同名但参数不同的方法methodA,这里构成了重载,在methodC中调用时取决于你给编译器传的参数来选择对应的方法.
3.子类中有和父类同名且参数和返回类型都相同的方法methodB,这里构成了重写,会优先访问子类的methodB方法,若要访问父类的methodB方法,需要借助super关键字.

子类构造方法

子类对象构造时,需要先调用父类的构造方法,帮助父类进行初始化,然后执行子类的构造方法.

//Base.java
public class Base {
    public Base(){
        System.out.println("Base()");
    }
}

//Derived.java
public class Derived extends Base{
	public Derived(){
		super();
		//子类的构造方法.......
	}
}

如以上代码,在子类构造方法中,通过super()来调用父类的构造方法,然后在执行子类构造方法的其他内容,这也意味着super()必须在子类构造方法中的第一行,而且super()只能出现一次;
当子类中没写构造方法时编译器会默认给你提供一个子类的无参构造方法,这个构造方法长这样:

public Derived(){
		super();
	}

注意,上面说的构造方法是无参的,如果父类中的构造方法有参,记得给super()传入相应的参数,当父类的构造方法有参时,子类中必须显式定义构造方法,并调用super()来帮助父类进行初始化,因为编译器无法默认提供有参的构造函数.

继承中代码块的执行顺序

之前说到,Java中有静态代码块和实例代码块,那么当一个类继承了一个类的时候,各种代码块以及构造方法的执行顺序是什么样的呢?
我们来看以下代码:

//Base.java
public class Base {
    public Base(){
        System.out.println("父类的构造方法");
    }
    {
        System.out.println("父类的实例代码块");
    }
    static {
        System.out.println("父类的静态代码块");
    }

}

//Derived.java
public class Derived extends Base{
    public Derived() {
        super();
        System.out.println("子类的构造方法");
    }
    static{
        System.out.println("子类的静态代码块");
    }
    {
        System.out.println("子类的实例代码块");
    }
}

//Test.java
public class Test {
    public static void main(String[] args) {
        Derived derived = new Derived();

    }
}

运行结果:
在这里插入图片描述
我们会发现执行的顺序是:
先执行父类和子类静态代码块,然后是父类的实例和构造,再是子类的实例和构造;

为什么是这样的顺序?
答:静态代码块优先执行是因为它们在类的初始化阶段之前被执行,这是Java类加载机制的一部分,用于在类的任何实例被创建之前初始化静态成员和资源。
当子类的构造方法通过super()显式地调用父类的构造方法,或者没有显式调用而默认调用父类的无参构造方法时,JVM会确保在调用父类构造方法之前,父类的所有实例变量和实例初始化块都已经被执行。这是因为父类的状态需要在子类能够访问它之前被正确地初始化。
如果不能很好的理解,自己在编译器中打断点调试一下就很清晰了.

要注意的是,静态代码块在类初始化阶段就被执行,因此在整个程序中只执行一次,因为类只用加载一次,所以当我们再次在main函数中创建子类对象时,静态代码块不会再次执行:

public class Test {
    public static void main(String[] args) {
        Derived derived = new Derived();
        System.out.println("================");
        Derived derived2 = new Derived();
    }
}

运行结果:
在这里插入图片描述

以上就是Java继承相关的知识,码字不易,还请多多点赞呀

  • 22
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值