为什么需要继承
我们先看两段代码:
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 + "正在咪咪叫");
}
}
我们会发现这两段代码中会有大量的重复,那么有没有办法将这些共性提取出来呢?在面向对象思想中提出了继承的概念,专门用来进行共性的抽取,实现代码复用。
什么是继承
继承是面向对象编程中的一个重要概念。它指的是一个类(称为子类或派生类)可以继承另一个类(称为父类或基类)的特征和行为。通过继承,子类可以重用父类的代码,并且可以添加或修改自己的行为,以满足特定的需求。
例如上面的代码中,猫和狗都是动物,可以把属于动物的共性给提取出来:
上述图示中,Dog和Cat都继承了Animal类,其中Animal称为父类(基类),Dog和Cat都是子类(派生类).
我们可以发现,继承最大的特点就是:实现代码复用
继承的语法
在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继承相关的知识,码字不易,还请多多点赞呀