Java入门(继承)

Java入门(继承) 目录一、为什么需要继承—— 解决代码冗余问题无继承的弊端代码重复率高二、继承的核心概念三、继承的基本语法基于继承重构猫狗案例继承的基础注意事项四、父类成员的访问规则4.1 子类访问父类的成员变量场景 1子类和父类无同名成员变量场景 2子类和父类有同名成员变量4.2 子类访问父类的成员方法场景 1子类和父类无同名成员方法场景 2子类和父类有同名成员方法五、super 关键字访问父类的同名成员5.1 super 的核心使用场景场景 1访问父类的同名成员变量场景 2访问父类的同名成员方法场景 3调用父类的构造方法5.2 super 关键字完整示例5.3 super 的注意事项六、子类的构造方法先有父后有子6.1 子类构造方法的核心规则6.2 子类构造方法示例示例 1父类有无参构造默认 / 显式示例 2父类只有有参构造必须显式调用七、super 和 this 的对比八、继承体系中的初始化顺序8.1 初始化相关的代码块回顾8.2 继承体系的初始化核心规则8.3 继承体系初始化完整示例九、访问修饰符protected 与继承的可见性9.1 4 种访问修饰符的访问范围9.2 protected 的核心特性步骤 1创建包 1 的父类 Baseprotected 修饰成员步骤 2创建包 2 的子类 Derived可访问自身的 b9.3 访问修饰符的使用原则十、Java 的继承方式与 final 关键字10.1 Java 支持的继承方式10.2 final 关键字限制继承final 修饰类的示例禁止继承10.3 继承的层次建议十一、继承与组合如何选择11.1 组合的实现方式11.2 继承与组合的对比示例11.3 继承与组合的选择原则十二、Java 继承高频面试题解析题 1以下关于 Java 继承的说法正确的是D题 2以下关于 Java 继承的说法错误的是C题 3子类构造方法中super (...) 的使用要求是什么题 4继承体系中静态代码块、实例代码块、构造方法的执行顺序是什么十三、总结在 Java 面向对象的三大特性封装、继承、多态中继承是实现代码复用、构建类层次结构的核心机制。它让我们可以基于已有类的特性扩展出新的类避免重复编写相同代码同时为多态的实现奠定基础。本文将从继承的核心需求出发由浅入深讲解继承的语法、使用细节、核心关键字以及与组合的区别结合完整可运行的代码示例让你彻底吃透 Java 继承的精髓。一、为什么需要继承—— 解决代码冗余问题在开发中我们会用类描述现实世界的实体但很多实体之间存在共性。比如狗和猫都属于动物都有name、age等属性也有eat()、sleep()等行为。如果单独为每个实体编写类会出现大量重复代码。无继承的弊端代码重复率高// 狗类 public class Dog { String name; int age; float weight; // 吃饭方法 public void eat() { System.out.println(name 正在吃饭); } // 睡觉方法 public void sleep() { System.out.println(name 正在睡觉); } // 狗的特有方法叫 void bark() { System.out.println(name 汪汪汪~~~); } } // 猫类 public class Cat { String name; int age; float weight; // 与Dog类重复的吃饭方法 public void eat() { System.out.println(name 正在吃饭); } // 与Dog类重复的睡觉方法 public void sleep() { System.out.println(name 正在睡觉); } // 猫的特有方法叫 void mew() { System.out.println(name 喵喵喵~~~); } }观察上述代码Dog和Cat类中60% 以上的代码是重复的这些共性的属性和方法分散在各个类中后期维护成本极高比如修改eat()方法的逻辑需要修改所有相关类。而继承的核心就是抽取共性实现代码复用让子类只需关注自身的特有特性即可。二、继承的核心概念继承机制允许程序员在保持原有类特性的基础上扩展新功能生成新的类。原有类称为父类 / 基类 / 超类新类称为子类 / 派生类。核心作用① 实现代码复用② 为多态提供基础。关系描述子类和父类是is-a的关系比如狗是动物、猫是动物。核心特性子类会继承父类的非私有成员属性和方法子类可在父类基础上新增自己的特有成员。类层次结构示意Animal(父类)属性(name/age/weight)、方法(eat/sleep) ↗️ ↘️ Dog(子类)新增bark()方法 Cat(子类)新增mew()方法三、继承的基本语法在 Java 中使用extends关键字表示类之间的继承关系语法格式如下// 修饰符 子类名 extends 父类名 { 子类特有成员 } 修饰符 class SubClass extends SuperClass { // 子类新增的属性和方法 }基于继承重构猫狗案例我们抽取Animal作为父类封装所有动物的共性Dog和Cat作为子类继承Animal仅实现自身特有方法/** * 动物父类封装所有动物的共性属性和方法 */ public class Animal { String name; // 名字 int age; // 年龄 float weight;// 体重 // 吃饭方法 public void eat() { System.out.println(name 正在吃饭); } // 睡觉方法 public void sleep() { System.out.println(name 正在睡觉); } } /** * 狗子类继承Animal父类仅实现特有方法 */ public class Dog extends Animal { // 狗的特有方法汪汪叫 void bark() { System.out.println(name 汪汪汪~~~); } } /** * 猫子类继承Animal父类仅实现特有方法 */ public class Cat extends Animal { // 猫的特有方法喵喵叫 void mew() { System.out.println(name 喵喵喵~~~); } } /** * 测试类验证继承的代码复用效果 */ public class TestExtend { public static void main(String[] args) { // 创建狗对象 Dog dog new Dog(); // 直接使用父类继承的属性 dog.name 大黄; dog.age 3; // 直接使用父类继承的方法 dog.eat(); dog.sleep(); // 调用子类特有方法 dog.bark(); System.out.println(分割线); // 创建猫对象 Cat cat new Cat(); // 直接使用父类继承的属性 cat.name 小白; cat.age 2; // 直接使用父类继承的方法 cat.eat(); cat.sleep(); // 调用子类特有方法 cat.mew(); } }运行结果大黄正在吃饭 大黄正在睡觉 大黄汪汪汪~~~ 分割线 小白正在吃饭 小白正在睡觉 小白喵喵喵~~~核心亮点子类无需重新定义父类的共性成员直接继承使用代码量大幅减少维护性提升。继承的基础注意事项子类会继承父类的非私有成员变量和成员方法私有成员被封装子类无法直接访问子类继承父类后必须新增自身特有成员否则就没有继承的意义直接使用父类即可Java 中一个子类只能有一个直接父类单继承不支持多继承。四、父类成员的访问规则子类继承了父类的成员那在子类中如何访问这些成员是否存在优先级核心遵循就近原则同时分成员变量和成员方法两种场景分析。4.1 子类访问父类的成员变量场景 1子类和父类无同名成员变量子类可直接访问父类的成员变量无任何限制/** * 父类Base */ public class Base { int a; int b; } /** * 子类Derived无同名成员变量 */ public class Derived extends Base { int c; // 子类特有变量 public void method() { a 10; // 直接访问父类的a b 20; // 直接访问父类的b c 30; // 访问子类自己的c } }场景 2子类和父类有同名成员变量当子类存在与父类同名的成员变量时优先访问子类自己的成员变量若子类无该变量才会访问父类的父类也无则编译报错。/** * 父类Base */ public class Base { int a 10; int b 20; int c 30; } /** * 子类Derived有同名成员变量 */ public class Derived extends Base { int a 100; // 与父类a同名同类型 char b b;// 与父类b同名不同类型 public void method() { a 200; // 访问子类自己的a而非父类的a b c; // 访问子类自己的b而非父类的b c 400; // 子类无c访问父类的c // d 500; // 编译报错父类和子类都无d } }4.2 子类访问父类的成员方法场景 1子类和父类无同名成员方法子类可直接访问父类的成员方法无任何限制遵循自己没有找父类的原则/** * 父类Base */ public class Base { public void methodA() { System.out.println(Base中的methodA()); } } /** * 子类Derived无同名方法 */ public class Derived extends Base { public void methodB() { System.out.println(Derived中的methodB()); } public void methodC() { methodB(); // 访问子类自己的methodB methodA(); // 子类无methodA访问父类的methodA // methodD(); // 编译报错整个继承体系无methodD } }场景 2子类和父类有同名成员方法当子类存在与父类同名的成员方法时优先访问子类自己的方法若方法是重载参数列表不同则根据传参匹配方法若方法是重写参数列表、返回值、方法名完全相同则直接访问子类的重写方法。/** * 父类Base */ public class Base { public void methodA() { System.out.println(Base中的methodA()); } public void methodB() { System.out.println(Base中的methodB()); } } /** * 子类Derived有同名方法 */ public class Derived extends Base { // 重写父类的methodA参数列表、方法名完全相同 public void methodA() { System.out.println(Derived中的methodA()); } // 重写父类的methodB public void methodB() { System.out.println(Derived中的methodB()); } public void methodC() { methodA(); // 访问子类重写的methodA methodB(); // 访问子类重写的methodB } }五、super 关键字访问父类的同名成员当子类和父类存在同名成员时子类无法直接访问父类的同名成员此时需要使用super 关键字—— 它的核心作用是在子类中访问父类的成员变量 / 方法 / 构造方法相当于子类对象中父类继承部分的引用。5.1 super 的核心使用场景场景 1访问父类的同名成员变量super.父类成员变量名场景 2访问父类的同名成员方法super.父类成员方法名(参数)场景 3调用父类的构造方法super(参数); // 必须是子类构造方法的第一行5.2 super 关键字完整示例/** * 父类Base */ public class Base { int a 10; int b 20; public void methodA() { System.out.println(Base中的methodA()); } public void methodB() { System.out.println(Base中的methodB()); } } /** * 子类Derived使用super访问父类同名成员 */ public class Derived extends Base { int a 100; // 与父类a同名 // 与父类methodA重载参数列表不同 public void methodA(int a) { System.out.println(Derived中的methodA(int) a); } // 与父类methodB重写原型完全相同 public void methodB() { System.out.println(Derived中的methodB()); } public void methodC() { // 1. 访问成员变量直接访问子类自己super父类 a 200; // 等价于this.a 200访问子类的a super.a 300; // 访问父类的a b 400; // 子类无b直接访问父类的b也可写super.b // 2. 访问成员方法重载通过参数区分重写通过super访问父类 methodA(); // 无参访问父类的methodA methodA(500); // 有参访问子类的methodA(int) methodB(); // 直接访问子类重写的methodB super.methodB();// super访问父类的methodB } } /** * 测试类 */ public class TestSuper { public static void main(String[] args) { Derived d new Derived(); d.methodC(); } }运行结果Base中的methodA() Derived中的methodA(int)500 Derived中的methodB() Base中的methodB()5.3 super 的注意事项只能在非静态方法中使用静态方法属于类不依赖对象而 super 是对象层面的引用不能和 this 同时在构造方法中使用两者都要求是构造方法的第一行super 不是对象只是一个指向父类继承部分的关键字无法单独打印或赋值。六、子类的构造方法先有父后有子在 Java 中构造子类对象时必须先构造父类对象—— 因为子类的成员由「父类继承的部分」「子类新增的部分」组成只有先初始化父类成员才能完整初始化子类对象。6.1 子类构造方法的核心规则默认调用父类无参构造如果子类构造方法中没有显式调用父类构造方法编译器会自动在子类构造方法的第一行添加super()调用父类的无参构造显式调用父类有参构造如果父类没有无参构造显式定义了有参构造子类必须在构造方法的第一行通过super(参数)显式调用父类的有参构造否则编译报错super (...) 只能出现一次子类构造方法中super(...)只能写一次且必须是第一行代码不能和 this (...) 同时使用this(...)是调用子类自己的其他构造方法也要求是第一行两者冲突。6.2 子类构造方法示例示例 1父类有无参构造默认 / 显式/** * 父类Base显式无参构造 */ public class Base { public Base() { System.out.println(Base的无参构造执行); } } /** * 子类Derived默认调用父类无参构造 */ public class Derived extends Base { public Derived() { // 编译器自动添加super()无需手动写 System.out.println(Derived的无参构造执行); } } /** * 测试类 */ public class TestConstructor1 { public static void main(String[] args) { Derived d new Derived(); } }运行结果Base的无参构造执行 Derived的无参构造执行示例 2父类只有有参构造必须显式调用/** * 父类Base只有有参构造无默认无参构造 */ public class Base { int a; public Base(int a) { this.a a; System.out.println(Base的有参构造执行a a); } } /** * 子类Derived必须显式调用父类有参构造 */ public class Derived extends Base { public Derived() { super(10); // 显式调用父类有参构造必须是第一行 System.out.println(Derived的无参构造执行); } public Derived(int a, int b) { super(a); // 显式调用父类有参构造 System.out.println(Derived的有参构造执行b b); } } /** * 测试类 */ public class TestConstructor2 { public static void main(String[] args) { Derived d1 new Derived(); System.out.println(分割线); Derived d2 new Derived(20, 30); } }运行结果Base的有参构造执行a10 Derived的无参构造执行 分割线 Base的有参构造执行a20 Derived的有参构造执行b30七、super 和 this 的对比super 和 this 都是 Java 的核心关键字都能访问成员变量 / 方法也能用于构造方法两者的异同点如下表所示帮你彻底区分对比维度thissuper核心含义代表当前对象的引用代表子类对象中父类继承部分的引用访问成员访问本类的成员变量 / 方法本类无则找父类直接访问父类的成员变量 / 方法构造方法this(...)调用本类的其他构造方法super(...)调用父类的构造方法存在位置非静态方法 / 构造方法中非静态方法 / 构造方法中第一行规则构造方法中使用时必须是第一行构造方法中使用时必须是第一行共存性不能和 super (...) 同时在构造方法中使用不能和 this (...) 同时在构造方法中使用编译器默认添加构造方法中不默认添加需手动写子类构造方法中默认添加 super ()父类有无参构造时核心总结this关注本类super关注父类两者都是面向对象中封装和继承的重要体现。八、继承体系中的初始化顺序在 Java 中类的初始化包含静态代码块、实例代码块、构造方法当存在继承关系时初始化顺序会有明确的优先级这是面试高频考点必须牢牢掌握。8.1 初始化相关的代码块回顾静态代码块用static{}修饰类加载时执行仅执行一次用于初始化静态变量实例代码块用{}修饰创建对象时执行每次创建对象都会执行执行在构造方法之前用于初始化实例变量构造方法创建对象时执行每次创建对象都会执行用于初始化对象的完整状态。8.2 继承体系的初始化核心规则优先级从高到低父类静态代码块 → 子类静态代码块静态代码块仅执行一次类加载时完成父类实例代码块 → 父类构造方法初始化父类继承部分子类实例代码块 → 子类构造方法初始化子类新增部分再次创建子类对象时静态代码块不再执行仅类加载时执行一次其余步骤重复。8.3 继承体系初始化完整示例/** * 父类Person */ public class Person { public String name; public int age; // 静态代码块 static { System.out.println(Person静态代码块执行); } // 实例代码块 { System.out.println(Person实例代码块执行); } // 构造方法 public Person(String name, int age) { this.name name; this.age age; System.out.println(Person构造方法执行); } } /** * 子类Student继承Person */ public class Student extends Person { // 静态代码块 static { System.out.println(Student静态代码块执行); } // 实例代码块 { System.out.println(Student实例代码块执行); } // 构造方法必须显式调用父类有参构造 public Student(String name, int age) { super(name, age); System.out.println(Student构造方法执行); } } /** * 测试类验证初始化顺序 */ public class TestInitOrder { public static void main(String[] args) { System.out.println(第一次创建Student对象); Student s1 new Student(张三, 19); System.out.println(第二次创建Student对象); Student s2 new Student(李四, 20); } }运行结果第一次创建Student对象 Person静态代码块执行 Student静态代码块执行 Person实例代码块执行 Person构造方法执行 Student实例代码块执行 Student构造方法执行 第二次创建Student对象 Person实例代码块执行 Person构造方法执行 Student实例代码块执行 Student构造方法执行结果分析静态代码块仅在第一次创建对象时执行类加载第二次创建对象时不再执行完全符合初始化规则。九、访问修饰符protected 与继承的可见性在 Java 中有 4 种访问修饰符private、default包访问、protected、public它们决定了成员的访问范围其中protected是为继承专门设计的核心作用是让不同包的子类能访问父类的成员。9.1 4 种访问修饰符的访问范围访问范围privatedefaultprotectedpublic同一类中✅✅✅✅同一包的不同类❌✅✅✅不同包的子类❌❌✅✅不同包的非子类❌❌❌✅9.2 protected 的核心特性protected修饰的成员不同包的子类只能通过自身对象访问不能访问其他子类对象的该成员这是容易踩坑的点示例说明步骤 1创建包 1 的父类 Baseprotected 修饰成员package com.demo1; /** * 父类com.demo1包 */ public class Base { protected int b 100; // protected修饰的成员变量 }步骤 2创建包 2 的子类 Derived可访问自身的 bpackage com.demo2; import com.demo1.Base; /** * 子类1com.demo2包继承Base */ public class Derived extends Base { public static void main(String[] args) { Derived d new Derived(); System.out.println(d.b); // 合法访问自身对象的protected成员 C c new C(); // System.out.println(c.b); // 编译报错不能访问其他子类对象的protected成员 } } /** * 子类2com.demo2包继承Base */ class C extends Base {}核心结论protected的可见性是 **“子类可见”**而非 “子类的对象可见”不同包的子类之间不能互相访问 protected 成员。9.3 访问修饰符的使用原则为了保证类的封装性使用修饰符时遵循 **“宁严勿宽”** 的原则仅类内部使用的成员用private同一包内的类使用的成员用default不同包的子类使用的成员用protected所有类都需要使用的成员用public成员变量优先用private封装通过get/set方法访问成员方法根据实际使用范围选择修饰符。十、Java 的继承方式与 final 关键字10.1 Java 支持的继承方式Java 是单继承语言一个子类只能有一个直接父类但支持多层继承子类继承父类父类再继承祖父类不支持多继承一个子类继承多个父类避免类层次结构混乱。继承方式示例是否支持单继承class B extends A{}✅多层继承class C extends B、class B extends A✅多继承class C extends A,B{}❌为什么不支持多继承如果多个父类有同名的方法子类继承后会出现方法冲突无法确定调用哪个父类的方法导致程序歧义。10.2 final 关键字限制继承final关键字可修饰变量、方法、类其中修饰类和方法时与继承直接相关修饰类表示该类不能被继承称为 “最终类”示例public final class String{}Java 的 String 类就是 final 类不能被继承修饰方法表示该方法不能被子类重写后序多态章节会详细讲解修饰变量表示该变量是常量值不能被修改。final 修饰类的示例禁止继承/** * final修饰的类不能被继承 */ public final class Animal { public void eat() { System.out.println(动物吃饭); } } /** * 编译报错Cannot inherit from final Animal */ public class Dog extends Animal { // 错误final类不能被继承 public void bark() { System.out.println(狗叫); } }10.3 继承的层次建议实际开发中不建议继承层次超过 3 层如果继承层次太深会导致类的耦合性过高代码可读性和维护性大幅下降。若层次过深建议考虑重构代码比如使用组合。十一、继承与组合如何选择除了继承组合也是 Java 中实现代码复用的重要方式两者的核心区别在于类之间的关系继承表示is-a关系狗是动物、学生是人组合表示has-a关系汽车有发动机、电脑有 CPU。11.1 组合的实现方式组合无需特殊关键字只需将一个类的对象作为另一个类的成员变量通过该对象访问其成员实现代码复用。11.2 继承与组合的对比示例以汽车为例汽车由发动机、轮胎、车载系统组成has-a使用组合实现奔驰是汽车的一种is-a使用继承实现。/** * 轮胎类组件类 */ class Tire { public void run() { System.out.println(轮胎转动汽车前进); } } /** * 发动机类组件类 */ class Engine { public void start() { System.out.println(发动机启动提供动力); } } /** * 车载系统类组件类 */ class VehicleSystem { public void open() { System.out.println(车载系统启动支持导航/音乐); } } /** * 汽车类通过组合复用组件类的功能 */ class Car { // 组合将组件类的对象作为成员变量 private Tire tire new Tire(); private Engine engine new Engine(); private VehicleSystem vs new VehicleSystem(); // 汽车的启动方法调用组件类的方法 public void startCar() { engine.start(); tire.run(); vs.open(); System.out.println(汽车成功启动); } } /** * 奔驰类通过继承复用汽车类的功能 */ class Benz extends Car { // 奔驰的特有方法 public void luxury() { System.out.println(奔驰高端内饰智能驾驶); } } /** * 测试类 */ public class TestCombine { public static void main(String[] args) { Benz benz new Benz(); benz.startCar(); // 继承自Car的方法 benz.luxury(); // 自身特有方法 } }运行结果发动机启动提供动力 轮胎转动汽车前进 车载系统启动支持导航/音乐 汽车成功启动 奔驰高端内饰智能驾驶11.3 继承与组合的选择原则核心建议能用组合尽量用组合原因如下组合的耦合性更低组合是 “使用关系”子类仅使用组件类的功能不依赖其内部实现继承是 “父子关系”子类依赖父类的实现耦合性高组合更灵活可随时替换组件类的实现而继承的父类一旦确定无法轻易修改组合避免单继承限制Java 不支持多继承但可通过组合多个组件类实现多 “功能” 的复用继承的适用场景只有当类之间确实是is-a的关系时才使用继承比如学生是人、狗是动物。十二、Java 继承高频面试题解析结合本文内容整理几道 Java 继承的高频面试题帮你巩固知识点题 1以下关于 Java 继承的说法正确的是DA. 一个类可以同时继承多个类B. 子类可以直接访问父类的私有 (private) 成员C. 使用 final 关键字修饰的类可以被继承D. 子类可以重写 (override) 父类的方法解析A 错误Java 不支持多继承B 错误私有成员被封装子类无法直接访问C 错误final 类不能被继承D 正确子类可重写父类的非 final 方法。题 2以下关于 Java 继承的说法错误的是CA. 子类可以通过 super 关键字调用父类的构造方法B. 所有的 Java 类都直接或间接继承自 java.lang.Object 类C. 构造方法可以被子类继承D. 使用 protected 修饰的成员可以被子类访问解析A 正确super (...) 可调用父类构造方法B 正确Object 是 Java 所有类的根父类C 错误构造方法不能被继承子类只能通过 super 调用D 正确protected 的核心作用是让子类访问。题 3子类构造方法中super (...) 的使用要求是什么答案① 必须是子类构造方法的第一行代码② 只能出现一次③ 不能和 this (...) 同时使用④ 若父类无无参构造子类必须显式调用 super (...)。题 4继承体系中静态代码块、实例代码块、构造方法的执行顺序是什么答案父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法静态代码块仅执行一次。十三、总结Java 继承是面向对象编程的核心机制其核心价值是抽取共性、实现代码复用同时为多态奠定基础。本文从实际需求出发讲解了继承的语法、父类成员访问规则、super 关键字、子类构造方法、初始化顺序、访问修饰符、final 关键字以及与组合的区别核心知识点可总结为以下几点继承使用extends关键字Java 支持单继承和多层继承不支持多继承子类继承父类的非私有成员必须新增自身特有成员否则无继承意义父类成员访问遵循就近原则同名成员需用 super 关键字访问子类构造时先构造父类super (...) 必须是子类构造方法的第一行继承体系的初始化顺序静态代码块父→子→ 实例代码块 构造方法父→子protected 为继承设计允许不同包的子类访问父类成员final 修饰的类不能被继承修饰的方法不能被重写继承是 is-a 关系组合是 has-a 关系实际开发中优先使用组合。掌握继承的核心知识点不仅能写出更简洁、易维护的代码还能为后续学习多态打下坚实的基础。建议结合本文的代码示例亲自运行、调试加深对知识点的理解。