本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2024-11(2)

Java 继承

发布于2024-11-02 10:30     阅读(959)     评论(0)     点赞(3)     收藏(2)


目录

什么是继承

继承的语法

父类成员的访问

访问父类的成员变量

访问父类的成员方法

super 关键字

访问父类成员变量

访问父类成员方法

访问父类构造方法

super 和 this

初始化

访问限定符

继承方式

final 关键字

继承和组合


什么是继承

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

定义一个 Cat 类来描述猫:

  1. public class Cat {
  2. String name;
  3. int age;
  4. public void eat() {
  5. System.out.println("吃饭");
  6. }
  7. }

定义一个 Dog 类来描述狗:

  1. public class Dog {
  2. String name;
  3. int age;
  4. public void eat() {
  5. System.out.println("吃饭");
  6. }
  7. }

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

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

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

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

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

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

继承的语法

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

修饰符 class 子类 extends 父类{

        ...

}

  1. public class Animal {
  2. String name;
  3. int age;
  4. public void eat() {
  5. System.out.println("吃饭");
  6. }
  7. }

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

  1. public class Cat extends Animal{
  2. public void miaow() {
  3. System.out.println("喵喵叫");
  4. }
  5. }

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

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

类成员的访问

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

访问父类的成员变量

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

  1. public class A {
  2. int a = 10;
  3. }
  1. public class B extends A {
  2. int b = 20;
  3. public static void main(String[] args) {
  4. B b = new B();
  5. System.out.println(b.a);
  6. System.out.println(b.b);
  7. // System.out.println(b.c); // 编译失败,子类和父类中都不存在 c
  8. b.method();
  9. }
  10. }

运行结果:

 

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

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

  1. public class A {
  2. int a = 10;
  3. int b = 20;
  4. }
  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. public void method() {
  5. System.out.println("a: " + a + " b: " + b);
  6. }
  7. public static void main(String[] args) {
  8. B b = new B();
  9. b.method();
  10. }
  11. }

运行结果:

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

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

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

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

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

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

访问父类的成员方法

成员方法名不同

  1. public class A {
  2. int a = 10;
  3. int b = 20;
  4. public void methodA() {
  5. System.out.println("methodA...");
  6. }
  7. }
  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. public void methodB() {
  5. System.out.println("methodB...");
  6. }
  7. public static void main(String[] args) {
  8. B b = new B();
  9. b.methodA();
  10. b.methodB();
  11. // b.methodC(); // 编译失败,在继承体系中没有发现 methodC()
  12. }
  13. }

运行结果:

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

成员方法名相同

  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. public void methodA(int a) {
  5. System.out.println("methodA..." + a);
  6. }
  7. public static void main(String[] args) {
  8. B b = new B();
  9. b.methodA();
  10. b.methodA(10);
  11. }
  12. }

 运行结果:

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

  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. @Override
  5. public void methodA() {
  6. System.out.println("B methodA...");
  7. }
  8. public static void main(String[] args) {
  9. B b = new B();
  10. b.methodA();
  11. }
  12. }

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

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

super 关键字

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

访问父类成员变量

  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. public void methodB() {
  5. System.out.println(super.a);
  6. System.out.println(super.b);
  7. }
  8. public static void main(String[] args) {
  9. B b = new B();
  10. b.methodB();
  11. }
  12. }

访问父类成员方法

  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. public void methodB() {
  5. super.methodA();
  6. System.out.println("methodB...");
  7. }
  8. public static void main(String[] args) {
  9. B b = new B();
  10. b.methodB();
  11. }
  12. }

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

访问父类构造方法

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

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

  1. public class A {
  2. int a = 10;
  3. int b = 20;
  4. public A(int a) {
  5. System.out.println("a");
  6. }
  7. public void methodA() {
  8. System.out.println("methodA...");
  9. }
  10. }

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

为什么会报错呢?

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

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

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

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

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

  1. public B(int a) {
  2. super(a);
  3. }

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

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

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

 

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

在子类构造方法中,通过 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博客 中,我们学习了代码块,重点学习了 实例代码块 静态代码块,以及它们的执行顺序,接下来,我们就来看存在继承关系时,它们的执行顺序

  1. public class A {
  2. int a;
  3. int b;
  4. {
  5. a = 10;
  6. b = 20;
  7. System.out.println("A 构造代码块执行...");
  8. }
  9. static {
  10. System.out.println("A 静态代码块执行...");
  11. }
  12. public A(int a) {
  13. System.out.println("A 构造方法执行...");
  14. }
  15. public void methodA() {
  16. System.out.println("methodA...");
  17. }
  18. }
  1. public class B extends A {
  2. int a = 100; // 与父类中的成员同名,且类型相同
  3. char b = 'b'; // 与父类中的成员同名,但类型不同
  4. static {
  5. System.out.println("B 静态代码块执行...");
  6. }
  7. {
  8. System.out.println("B 构造代码块执行...");
  9. }
  10. public B(int a) {
  11. super(a);
  12. System.out.println("B 构造方法执行...");
  13. }
  14. public void methodA() {
  15. System.out.println("methodB...");
  16. }
  17. public static void main(String[] args) {
  18. B b1 = new B(10);
  19. System.out.println("---------------------");
  20. B b2 = new B(20);
  21. }
  22. }

运行结果: 

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

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

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

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

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

访问限定符

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

范围privatedefaultprotectedpublic
同一个包中的同一个类
同一个包中的不同类
不同包中的子类
不同包中的非子类

public:在任何类都可以访问

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

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

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

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

  1. public class A {
  2. public int a;
  3. int b;
  4. protected int c;
  5. private int d;
  6. }
  1. public class B extends A {
  2. public void method() {
  3. super.a = 10;
  4. super.b = 20;
  5. super.c = 30;
  6. super.d = 40;
  7. }
  8. }

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

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

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

  1. public class A {
  2. public int a;
  3. int b;
  4. protected int c;
  5. private int d;
  6. public int getD() {
  7. return d;
  8. }
  9. public void setD(int d) {
  10. this.d = d;
  11. }
  12. }
  1. public class B extends A {
  2. public void method() {
  3. super.a = 10;
  4. super.b = 20;
  5. super.c = 30;
  6. super.setD(40);
  7. }
  8. }

当 A 和 B 位于不同包中:

  1. public class B extends A {
  2. public void method() {
  3. super.a = 10; // 父类中 public 修饰的成员在不同包子类中可以直接访问
  4. super.b = 20; // 父类中 protected 修饰的成员在不同包子类中可以直接访问
  5. // super.c = 30; // 编译报错,父类中默认访问权限修饰的成员在不同包子类中不能直接访问
  6. // super.d = 40; // 编译报错,父类中默认访问权限修饰的成员在不同包子类中不能直接访问
  7. }
  8. }

继承方式

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

(1)单继承

(2)多层继承

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

(4)多继承(不支持)

Java 中不支持多继承

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

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

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

final 关键字

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

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

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

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

继承和组合

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

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

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

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

组合:

  1. public class Person {
  2. private Student[] students;
  3. }

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

继承: 

  1. public class Student extends Person{
  2. }

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

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

原文链接:https://blog.csdn.net/2301_76161469/article/details/142621979



所属网站分类: 技术文章 > 博客

作者:想要飞翔的天使

链接:http://www.javaheidong.com/blog/article/691574/94ca023d3e1b0cd2d6b4/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

3 0
收藏该文
已收藏

评论内容:(最多支持255个字符)