06-Java面向对象继承与thissuper

📅 2026/7/12 5:57:43
06-Java面向对象继承与thissuper
Java面向对象进阶继承、this与super关键字详解一、引言为什么需要继承在前面几篇文章中我们已经掌握了类、对象、构造器和封装。本章将带你走进面向对象的第二个核心特征——继承。继承让代码可以传承和扩展就像现实中父亲把家产传给儿子儿子不仅拥有父辈的财富还可以创造新的财富。掌握继承后你将能编写出复用性更强、结构更清晰的代码。本文还会深入讲解两个重要关键字this引用当前对象和super引用父类成员它们是写好继承代码的基石。二、继承的概念与意义2.1 什么是继承生活中的继承无处不在继承财产、继承基因、继承才艺。Java中的继承指的是子类继承父类子类可以拥有父类的成员变量和成员方法并在此基础上进行扩展。打个比方父亲积累了100万家产儿子继承后不需要从零开始直接拥有这100万还能继续赚钱变成200万。Java中的继承正是这个道理——子类复用父类的成果并在此基础上扩展。2.2 继承的三大意义代码复用子类直接复用父类已有的代码避免重复编写相同的逻辑代码扩展子类可以增加父类没有的功能或对父类方法进行重写改写表达is-a关系描述事物分类的层级关系小贴士is-a关系是判断是否该用继承的核心标准。如果说学生是一个人成立那么Student继承Person就合理如果说汽车是一个引擎这不成立那么就不该用继承。三、继承的语法3.1 使用extends关键字Java中使用extends关键字声明继承关系【修饰符】class子类名extends父类名{// 子类自己的成员}术语说明子类SubClass也叫派生类父类SuperClass也叫超类、基类3.2 一个简单的例子// 父类人类publicclassPerson{publicStringname;publicintage;publicvoideat(){System.out.println(吃饭);}}// 子类学生类继承自PersonpublicclassStudentextendsPerson{publicintscore;// Student自己新增的属性publicvoidstudy(){// Student自己新增的方法System.out.println(学习);}}// 测试类publicclassTest{publicstaticvoidmain(String[]args){StudentsnewStudent();s.name张三;// 继承自Person的属性s.age18;// 继承自Person的属性s.score95;// Student自己的属性s.eat();// 继承自Person的方法s.study();// Student自己的方法}}可以看到Student对象可以同时使用父类Person的成员和自己的成员这就是继承的威力。四、继承的特点4.1 单继承Java中类与类之间只允许单继承一个子类只能有一个直接父类一个亲生父亲。但一个父类可以有多个子类。为什么Java不支持多继承主要是为了避免菱形继承问题如果两个父类有同名方法子类调用时会产生歧义。Java用接口interface来弥补这一限制。4.2 多层继承虽然不能多继承但可以多层继承。例如Grandparent - Parent - Child形成一条继承链。子类不仅能继承直接父类的成员还能继承间接父类爷爷类的成员。4.3 所有类的根父类ObjectJava中所有类都直接或间接继承java.lang.Object类。即使你没写extends编译器也会默认让你的类继承Object。Object中定义的方法如toString、equals、getClass、hashCode会被所有类继承所以任何对象都能调用这些方法。五、继承时哪些成员可以被继承5.1 可以被继承的成员成员变量无论是否私有都会被继承到子类中成员方法非私有的方法会被继承子类可以直接调用5.2 不能被继承的成员构造器构造器不能被继承。每个类都需要自己定义构造器。注意构造器不能继承是重要规则很多初学者会误以为子类能继承父类的构造器这是错误的。子类对象创建时是通过super()调用父类构造器来完成父类部分的初始化而不是继承了父类的构造器。5.3 private成员的可见性问题虽然private成员变量也会被继承存在于子类对象内存中但子类不能直接访问。需要通过父类提供的public get/set方法间接访问。publicclassPerson{privatechargender;// 私有属性publicchargetGender(){returngender;}publicvoidsetGender(chargender){this.gendergender;}}publicclassStudentextendsPerson{publicvoidtest(){// gender 男; // 编译报错不能直接访问父类private成员setGender(男);// 正确通过public方法间接访问System.out.println(getGender());}}小贴士private体现的是封装思想。子类虽然拥有private成员但只能通过父类暴露的接口去操作它保证了数据的安全性。六、方法的重写Override6.1 为什么需要重写子类可以直接使用父类非私有方法。但有时父类方法对子类来说不合适子类可以重写Override该方法提供自己的实现。例如父类Person有eat()方法打印吃饭但Student类觉得这太笼统想打印吃学生餐这时就可以重写eat()方法。6.2 重写的规则重写方法必须遵守以下规则方法名必须与父类方法完全相同形参列表必须完全相同个数、类型、顺序返回值类型基本数据类型和void必须相同引用数据类型可以相同或返回父类返回类型的子类协变返回类型权限修饰符子类的权限必须大于或等于父类只能升级不能降级缺省 protected public其他修饰符不能是static方法、不能是final方法、不能是private方法6.3 Override注解的作用Override是给编译器看的注解作用有二提高可读性让人一眼看出这是一个重写方法编译器检查让编译器严格检查是否符合重写规则如果方法签名写错了会报错publicclassStudentextendsPerson{Override// 建议保留编译器会帮你检查重写是否正确publicvoideat(){System.out.println(吃学生餐);}}小贴士即使不写Override只要符合重写规则也能正确重写。但强烈建议保留可以避免因方法名拼写错误、参数不一致而导致的假重写问题。七、重写 vs 重载的区别这是Java初学者最容易混淆的两个概念一定要分清对比项重载Overload重写Override方法位置同一个类中 或 父子类父子类方法名必须相同必须相同形参列表必须不同必须相同返回值不看基本类型必须相同引用类型可降级权限修饰不看只能升级不能降级其他修饰不看不能是static、final、private一句话记忆重载是同名不同参重写是父子同签名。publicclassStudentextendsPerson{// 这是重写与父类eat()方法签名完全一致Overridepublicvoideat(){System.out.println(吃学生餐);}// 这是重载方法名相同但参数列表不同publicvoideat(Stringfood){System.out.println(学生吃food);}}八、this关键字回顾在讲super之前先回顾一下this关键字。this代表当前对象只能在构造器和实例方法中使用不能在静态方法中使用。this有三种用法8.1 this.成员变量当方法的局部变量通常是形参与成员变量重名时用this.成员变量进行区分。publicclassPerson{privateStringname;publicPerson(Stringname){this.namename;// this.name是成员变量name是形参}}8.2 this.成员方法调用本类的其他成员方法完全可以省略this。publicvoidtest(){this.eat();// 等价于 eat();}8.3 this() 或 this(实参列表)调用本类的其他构造器必须在构造器首行。publicclassPerson{privateStringname;privateintage;publicPerson(){this(默认姓名,0);// 调用本类的有参构造必须在首行}publicPerson(Stringname,intage){this.namename;this.ageage;}}九、super关键字详解super用于引用父类声明的成员同样只能在构造器和实例方法中使用不能在静态方法中使用。super有三种用法。9.1 super.成员变量当子类成员变量与父类成员变量重名时用super.成员变量区分。查找范围从直接父类开始找不到会向上追溯直到Object为止。publicclassPerson{publicStringnamePerson的name;}publicclassStudentextendsPerson{publicStringnameStudent的name;// 不建议与父类重名publicvoidshow(){System.out.println(name);// 就近原则输出Student的nameSystem.out.println(this.name);// 从本类开始找输出Student的nameSystem.out.println(super.name);// 从父类开始找输出Person的name}}注意实际开发中应尽量避免子类成员变量与父类重名会增加理解成本。9.2 super.成员方法当子类重写了父类的某个方法但又想在子类中调用父类被重写的方法时使用super.成员方法()。publicclassPerson{publicvoideat(){System.out.println(吃饭);}}publicclassStudentextendsPerson{Overridepublicvoideat(){System.out.println(吃学生餐);}publicvoidtest(){eat();// 调用子类重写后的eat()输出吃学生餐super.eat();// 调用父类被重写的eat()输出吃饭}}9.3 super() / super(实参列表)调用父类构造器必须放在子类构造器的首行。super()调用父类的无参构造super(实参列表)调用父类的有参构造publicclassPerson{privateStringname;privateintage;privatechargender;publicPerson(){System.out.println(父类无参构造);}publicPerson(Stringname,intage,chargender){this.namename;this.ageage;this.gendergender;System.out.println(父类有参构造);}}publicclassStudentextendsPerson{privateintscore;publicStudent(){super();// 调用父类无参构造可省略默认存在}publicStudent(Stringname,intage,chargender,intscore){super(name,age,gender);// 调用父类有参构造this.scorescore;// 初始化子类自己的属性}}十、this与super的详细对比对比项thissuper含义当前对象父类的xx引用成员变量this.成员变量从本类开始找super.成员变量从直接父类开始找引用成员方法this.成员方法调用本类方法可省略super.成员方法调用父类被重写的方法引用构造器this()/this(实参列表)调用本类其他构造器super()/super(实参列表)调用直接父类构造器查找范围本类 - 父类 - 父类的父类 - … - Object直接父类 - 父类的父类 - … - Object可以使用的位置构造器、实例方法构造器、实例方法不能使用的位置静态方法静态方法是否必须在首行this()/this(实参)必须在构造器首行super()/super(实参)必须在构造器首行重点注意this()、this(实参列表)、super()、super(实参列表)这四句代码只能出现一句因为它们都必须在构造器首行。如果四句都没写编译器会默认加上super()。十一、子类对象实例化过程11.1 初始化顺序创建子类对象时先完成父类部分的初始化再完成子类部分的初始化。具体顺序如下父类成员变量默认初始化赋默认值如int为0引用为null父类构造器执行通过super()调用为父类成员变量显式赋值子类成员变量默认初始化子类构造器执行为子类成员变量显式赋值小贴士可以理解为先建地基再建房子。父类是地基必须先建好子类才能稳固地建在上面。11.2 super()默认存在的规则如果子类构造器首行没有显式写super()或super(实参列表)也没有写this()或this(实参列表)编译器会自动在首行添加super()调用父类的无参构造。publicclassStudentextendsPerson{publicStudent(){// 编译器会自动在这里加上 super();System.out.println(子类无参构造);}}这就要求如果父类没有无参构造器子类构造器必须显式调用super(实参列表)否则编译报错。11.3 只继承的情况下子类的构造器如何编写如果父类只有有参构造器没有手动写无参构造器那么子类的构造器必须显式调用super(实参列表)否则编译不通过。// 父类只有有参构造器publicclassPerson{privateStringname;publicPerson(Stringname){// 没有无参构造器this.namename;}}// 子类必须显式调用父类有参构造publicclassStudentextendsPerson{privateintscore;// 错误写法会编译报错因为默认super()找不到父类无参构造// public Student() { }// 正确写法显式调用父类有参构造publicStudent(Stringname,intscore){super(name);// 必须调用父类的有参构造this.scorescore;}}注意为避免上述麻烦建议每个类都显式写一个无参构造器这样子类就不会因为找不到无参构造而报错。十二、完整示例继承体系实战下面通过一个完整的人-学生-老师继承体系综合演示继承、重写、super的用法。12.1 父类Personpackagecom.atguigu.inherited;publicclassPerson{publicStringname;publicintage;privatechargender;// 私有属性子类不能直接访问publicPerson(){System.out.println(Person类的无参构造);}publicPerson(Stringname,intage,chargender){System.out.println(Person类的有参构造);this.namename;this.ageage;this.gendergender;}publicvoideat(){System.out.println(吃饭);}// 为private属性提供get/set方法publicchargetGender(){returngender;}publicvoidsetGender(chargender){this.gendergender;}OverridepublicStringtoString(){// 重写Object的toString方法return姓名name年龄age性别gender;}}12.2 子类Studentpackagecom.atguigu.inherited;publicclassStudentextendsPerson{publicintscore;// 子类新增属性publicStudent(){super();// 默认调用父类无参构造可省略}publicStudent(Stringname,intage,chargender,intscore){super(name,age,gender);// 显式调用父类有参构造this.scorescore;}publicvoidstudy(){// 子类新增方法System.out.println(学习);}Override// 重写父类的eat方法publicvoideat(){System.out.println(吃学生餐);}Override// 再次重写toString复用父类的实现publicStringtoString(){returnsuper.toString()成绩score;}}12.3 测试类packagecom.atguigu.inherited;publicclassPeopleTest{publicstaticvoidmain(String[]args){// 使用无参构造创建学生StudentsnewStudent();s.name张三;// 父类属性s.age23;// 父类属性s.score89;// 子类属性s.setGender(男);// 通过父类公共方法操作私有属性s.eat();// 调用子类重写后的方法s.study();// 调用子类自己的方法// 使用有参构造创建学生Students2newStudent(李四,24,男,85);System.out.println(s2);// 自动调用toString}}运行结果Person类的无参构造 吃学生餐 学习 Person类的有参构造 姓名李四年龄24性别男成绩85十三、包package与导入import在上面的示例中你会看到每个类的第一行都有package com.atguigu.inherited;这涉及Java的包机制。13.1 包的概念包package是Java中用于管理类命名空间的机制类似于文件夹。它的作用避免类名冲突不同包下可以有同名类分类管理将功能相关的类放在同一个包下访问控制包级别是默认访问权限的范围之一13.2 包的声明每个Java源文件的第一行除注释外使用package关键字声明包packagecom.atguigu.inherited;publicclassPerson{// ...}13.3 import导入当需要使用其他包下的类时使用import关键字导入packagecom.atguigu.test;importcom.atguigu.inherited.Student;// 导入指定类// import com.atguigu.inherited.*; // 导入包下所有类不推荐影响可读性publicclassTest{publicstaticvoidmain(String[]args){StudentsnewStudent();// 可以直接使用Student类}}小贴士java.lang包下的类如String、System、Object会被自动导入无需手动import。十四、小结本文系统讲解了Java继承体系的核心知识要点回顾如下继承的本质子类复用父类代码并扩展表达is-a关系继承语法使用extends关键字Java只支持单继承继承内容成员变量和方法可继承构造器不能继承private限制private成员虽被继承但子类不能直接访问需通过get/set方法方法重写子类可重写父类非私有方法需遵守签名规则和权限升级原则Override注解让编译器帮你检查重写是否正确建议保留this关键字引用当前对象的成员三种用法成员变量、成员方法、构造器super关键字引用父类的成员三种用法成员变量、成员方法、构造器super()默认规则子类构造器默认调用父类无参构造与this()互斥实例化顺序先父类初始化再子类初始化掌握继承、this、super是迈向面向对象高手的关键一步。下一篇我们将继续探讨多态它建立在继承的基础上能让代码更加灵活。编程建议学习继承时多动手画UML类图理清父子类关系遇到问题时跟踪super的调用链理解对象创建的完整过程很多疑惑就会迎刃而解。