Java 继承

楠枬 2024-10-11 10:05:02 阅读 91

目录

什么是继承

继承的语法

父类成员的访问

访问父类的成员变量

访问父类的成员方法

super 关键字

访问父类成员变量

访问父类成员方法

访问父类构造方法

super 和 this

初始化

访问限定符

继承方式

final 关键字

继承和组合


什么是继承

在 Java 中,使用类来对现实中的实体进行描述,例如

定义一个 Cat 类来描述猫:

<code>public class Cat {

String name;

int age;

public void eat() {

System.out.println("吃饭");

}

}

定义一个 Dog 类来描述狗:

public class Dog {

String name;

int age;

public void eat() {

System.out.println("吃饭");

}

}

而上述代码中的 name、age 以及 eat 方法,都是重复的

此时,就可以对这些共性进行抽取

而在面向对象思想中提出了 继承 的概念,专门用来进行共性抽取,实现代码复用

继承(inhertance):是面向对象编程的特征,它允许在保持原有类的基础上进行扩展,增加新的功能,这样产生的新类,称之为派生类。通过继承,能够实现共性的抽取,从而实现代码的复用

我们将上述 Cat 和 Dog 的共性进行抽取,使用继承的思想来达到复用效果:

如上图所示,Dog 和 Cat 都继承了 Animal 类,其中,Animal 称为 父类(或 超类、基类),Dog 和 Cat 称之为 Animal 的 子类(或 派生类),继承之后,子类可以复用父类中的成员,子类在实现时只需关心自己新增的成员即可

继承的语法

在 Java 中若要表示类之间的继承关系,需要使用 extends 关键字:

修饰符 class 子类 extends 父类{

        ...

}

<code>public class Animal {

String name;

int age;

public void eat() {

System.out.println("吃饭");

}

}

Cat 类继承 Animal,还可以新增需要的成员变量或方法

public class Cat extends Animal{

public void miaow() {

System.out.println("喵喵叫");

}

}

子类会将父类中的成员变量或成员方法继承到子类中

子类在继承父类之后,可以添加自己特有的成员

父类成员的访问

子类将父类中的方法和变量继承之后,那么,子类中能否直接访问父类中继承下来的成员呢?

访问父类的成员变量

当子类和父类不存在同名对象时

public class A {

int a = 10;

}

public class B extends A {

int b = 20;

public static void main(String[] args) {

B b = new B();

System.out.println(b.a);

System.out.println(b.b);

// System.out.println(b.c); // 编译失败,子类和父类中都不存在 c

b.method();

}

}

运行结果:

 

子类自己有就访问自己的成员变量,若没有,就访问父类的成员变量 

当子类和父类存在同名对象时

<code>public class A {

int a = 10;

int b = 20;

}

public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

public void method() {

System.out.println("a: " + a + " b: " + b);

}

public static void main(String[] args) {

B b = new B();

b.method();

}

}

运行结果:

存在同名变量时,优先访问子类的成员变量 

 从上述两个示例中,可以看出:

若访问的成员变量子类中有,则访问子类自己的成员变量

若访问的成员变量子类中没有,则访问父类中继承下来的,若父类中也没有,则会编译报错

若访问的成员变量,父类子类中都有,则优先访问自己的成员变量

自己有就优先自己的,若没有再向父类中找

访问父类的成员方法

成员方法名不同

<code>public class A {

int a = 10;

int b = 20;

public void methodA() {

System.out.println("methodA...");

}

}

public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

public void methodB() {

System.out.println("methodB...");

}

public static void main(String[] args) {

B b = new B();

b.methodA();

b.methodB();

// b.methodC(); // 编译失败,在继承体系中没有发现 methodC()

}

}

运行结果:

当成员方法没有同名时,在子类方法中或通过子类对象访问方法时,若自己有,访问自己的,若自己没有,则在父类中找,若父类中也没有,则报错

成员方法名相同

<code>public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

public void methodA(int a) {

System.out.println("methodA..." + a);

}

public static void main(String[] args) {

B b = new B();

b.methodA();

b.methodA(10);

}

}

 运行结果:

当子类方法名和父类方法名相同,但其参数列表不同时,就构成了重载,根据调用方法时传递的参数选择合适的方法访问,若不存在该方法,则报错

<code>public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

@Override

public void methodA() {

System.out.println("B methodA...");

}

public static void main(String[] args) {

B b = new B();

b.methodA();

}

}

当子类方法名和父类方法名相同,且其参数列表也相同时,就构成了重写,此时,会访问子类的方法

那么,当子类中存在和父类相同的成员时,如何在子类中访问父类同名成员呢?

super 关键字

当子类和父类中存在相同名称的成员时,若要在子类方法中访问父类同名成员时,是不能直接访问的。Java 提供了 super 关键字,super 的主要作用:在子类方法中访问父类成员

访问父类成员变量

<code>public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

public void methodB() {

System.out.println(super.a);

System.out.println(super.b);

}

public static void main(String[] args) {

B b = new B();

b.methodB();

}

}

访问父类成员方法

public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

public void methodB() {

super.methodA();

System.out.println("methodB...");

}

public static void main(String[] args) {

B b = new B();

b.methodB();

}

}

需要注意的是:只能在非静态方法中使用 super

访问父类构造方法

在子类对象构造时,会先调用父类构造方法,然后再执行子类构造方法

为 A 添加带一个参数的构造方法

<code>public class A {

int a = 10;

int b = 20;

public A(int a) {

System.out.println("a");

}

public void methodA() {

System.out.println("methodA...");

}

}

 编译报错:A 中没有可用的默认构造函数

为什么会报错呢?

这是因为在 A 中没有定义的构造方法时,编译器提供了默认的无参构造方法

子类 B 中也没有定义构造方法,编译器也提供了默认的无参构造方法,且在 B 的构造方法中第一行默认有隐含的 super() 调用父类的无参构造方法

添加了带有一个参数的构造方法后,编译器也就不再提供无参的构造方法,此时也就会报错

我们可以在 A 中添加无参构造方法

或是在 B 的无参构造方法中调用 A 带有一个参数的构造方法

<code> public B(int a) {

super(a);

}

当父类中有多个构造方法时,就需要在子类构造方法中选择一个合适的父类构造方法调用,否则编译失败

为什么要先调用父类的构造方法呢?

子类对象中的成员是由两部分组成的:父类继承下来的成员子类新增的成员

 

 因此,在构造子类对象时,就需要先调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增的成员初始化完整

在子类构造方法中,通过 super(...) 调用父类构造,且 super(...) 必须是子类构造函数中的第一条语句

因此,super(...) 只能在子类构造方法中出现一次,且必须在第一行

而若需要在构造方法中使用 this 关键字调用类中的其他构造方法,也必须在第一行使用,也就是说,super(...) 和 this(...) 不能同时出现

super 和 this

相同点

(1)都是 Java 中的关键字

(2)都可以在成员方法中用来访问成员变量和调用其他成员方法,都可以作为构造方法的第一条语句

(3)只能在类的非静态方法中使用,用来访问非静态成员方法和变量

(4)在构造方法中调用时,必须是构造方法的第一条语句,并且不能同时存在

不同点

(1)this 表示当前对象(实例方法的对象)的引用,而 super 表示子类对象重父类继承下来部分成员的引用

(2)在非静态成员方法中,this 用来访问本类的方法和属性,super 用来访问父类继承下来的方法和属性

(3)在构造方法中,this(...) 用于调用本类构造方法,super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现

(4)构造方法中一定会存在 super(...) 的调用,但 this(...) 若没有写则没有

初始化

在前面的文章 Java 代码块-CSDN博客 中,我们学习了代码块,重点学习了 实例代码块 静态代码块,以及它们的执行顺序,接下来,我们就来看存在继承关系时,它们的执行顺序

<code>public class A {

int a;

int b;

{

a = 10;

b = 20;

System.out.println("A 构造代码块执行...");

}

static {

System.out.println("A 静态代码块执行...");

}

public A(int a) {

System.out.println("A 构造方法执行...");

}

public void methodA() {

System.out.println("methodA...");

}

}

public class B extends A {

int a = 100; // 与父类中的成员同名,且类型相同

char b = 'b'; // 与父类中的成员同名,但类型不同

static {

System.out.println("B 静态代码块执行...");

}

{

System.out.println("B 构造代码块执行...");

}

public B(int a) {

super(a);

System.out.println("B 构造方法执行...");

}

public void methodA() {

System.out.println("methodB...");

}

public static void main(String[] args) {

B b1 = new B(10);

System.out.println("---------------------");

B b2 = new B(20);

}

}

运行结果: 

 通过运行结果,可以看到:

静态代码块先执行,并且只执行一次,在类加载阶段执行

父类静态代码块优先于子类静态代码块执行,且最早执行

当有对象创建时,才会执行实例代码块

父类实例代码块和父类构造方法先执行,然后再执行子类的实例代码块和子类的构造方法

访问限定符

为了实现封装性,Java 引入了访问限定符,主要限定:类或类中成员能否在类外或其他包中被访问

范围 private default protected public
同一个包中的同一个类
同一个包中的不同类
不同包中的子类
不同包中的非子类

public:在任何类都可以访问

protected:在同一个包中的类或不同包中的子类可以访问

default:同一个包中的类可以访问

private:只有该类内部可以访问

当 A 和 B 位于同一个包中时:

<code>public class A {

public int a;

int b;

protected int c;

private int d;

}

public class B extends A {

public void method() {

super.a = 10;

super.b = 20;

super.c = 30;

super.d = 40;

}

}

由于变量 d 是 A 私有的,即只能在 A 类中访问,因此,子类中不能直接访问

但是,父类中的 private 成员变量虽然不能在子类中直接访问,但是也继承到子类中了

子类可以通过父类提供的方法来进行修改

<code>public class A {

public int a;

int b;

protected int c;

private int d;

public int getD() {

return d;

}

public void setD(int d) {

this.d = d;

}

}

public class B extends A {

public void method() {

super.a = 10;

super.b = 20;

super.c = 30;

super.setD(40);

}

}

当 A 和 B 位于不同包中:

public class B extends A {

public void method() {

super.a = 10; // 父类中 public 修饰的成员在不同包子类中可以直接访问

super.b = 20; // 父类中 protected 修饰的成员在不同包子类中可以直接访问

// super.c = 30; // 编译报错,父类中默认访问权限修饰的成员在不同包子类中不能直接访问

// super.d = 40; // 编译报错,父类中默认访问权限修饰的成员在不同包子类中不能直接访问

}

}

继承方式

在 Java 中,支持的继承方式有:

(1)单继承

(2)多层继承

(3)不同类继承同一个类

(4)多继承(不支持)

Java 中不支持多继承

类是对现实事物的抽象,而当情况比较复杂时,涉及到的类也会比较多,类之间的关系也会比较复杂

但即使如此,我们并不希望类之间的继承层次太复杂,一般不希望出现超过三层的继承关系

此时,若想从语法上限制继承,就可以使用 final 关键字

final 关键字

final 可以用来修饰变量成员方法以及

当修饰变量时,表示该变量为常量(不可变)

当修饰方法时,表示该方法不能被重写

当修饰时,表示该类不能被继承

继承和组合

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

但组合并没有涉及到特殊的语法,仅仅是将一个类的实例作为另一个类的字段

继承表示的对象之间是 is a 的关系,如:猫 是 动物

组合表示的对象之间是 has a 的关系,如:电脑 有 键盘

组合:

<code>public class Person {

private Student[] students;

}

此时 Student 属于 Person 的一部分,  在 Person 中,可以复用 Student 中的属性和方法

继承: 

public class Student extends Person{

}

Student 是 Person 的子类,继承了父类的成员,能够复用父类中的属性和方法

组合和继承都可以实现代码复用,是使用组合还是继承,需要根据具体的情况来进行选择



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。