java——继承

目录
一. 继承的基本概念
二. 继承的语法
三. 继承的核心规则
1.单继承:
2.子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。
①访问权限:
②构造方法:
3.一个类没有显示继承任何类时,默认继承java.lang.Object类。
(1).为什么需要隐式继承Object类?
(2).验证隐式继承的示例
(3).特殊情况说明
(4).隐式继承的底层逻辑
(5).总结
四. 继承的示例
父类 Animal
子类 Cat
测试类
五、继承中变量的访问特点
1.情况 1:完整代码(子类有成员变量 + 局部变量)
2.情况 2:去掉局部变量 age=30
3.情况 3:去掉子类成员变量 age 和局部变量 age
4.总结表格
5.关键规则
六. super 关键字
1. super 关键字用法及与 this 关键字对比
1.1 this 与 super 关键字概念
1.2 this 与 super 关键字具体用法对比
1.3 代码示例及结果
七、继承中构造方法的访问特点
1.代码示例及结果
2.父类只有带参构造方法的处理办法
八、继承中成员方法的访问特点
情况一:子类有该方法,父类无该方法
情况二:子类无该方法,父类有该方法
情况三:子类和父类都有该方法(方法重写)
情况四:子类和父类都无该方法
九、继承的用途
十、方法重写(Override)
十一、 继承的注意事项
十二、 继承与接口的区别
十三. 总结
在 Java 中,继承(Inheritance) 是面向对象编程(OOP)的核心特性之一,允许一个类(子类/派生类)继承另一个类(父类/基类)的属性和方法,从而实现代码复用和逻辑分层。以下是关于 Java 继承的详细讲解:
一. 继承的基本概念
父类(Superclass):被继承的类,包含通用的属性和方法。
子类(Subclass):继承父类的类,可以扩展或修改父类的功能。
核心思想:子类“是一个(is-a)”父类,例如 Cat 是 Animal。
继承相关的术语:当B类继承A类时
A类称为:父类、超类、基类、superclass
B类称为:子类、派生类、subclass
继承是面向对象的三大特征之一,可以使得子类具有父类的属性和方法,还可以在子类中重新定义,追加属性和方法。继承是指在原有类的基础上,进行功能扩展,创建新的类型。继承的本质是对某一批类的抽象,从而实现对现实世界更好的建模。JAVA中类只有单继承,没有多继承!继承是类和类之间的一种关系。除此之外,类和类之间的关系还有依赖、组合、聚合等。extends的意思是“扩展”,子类是父类的扩展。
二. 继承的语法
使用 extends 关键字实现继承:
class ParentClass {
// 父类的属性和方法
}
class ChildClass extends ParentClass {
// 子类可以添加新的属性和方法,或重写父类方法
}
三. 继承的核心规则
1.单继承:
Java 不支持多继承(一个子类只能有一个直接父类)。
语法错误:java不支持多继承,不能同时直接继承多个类。只能“直接”继承1个类
重复的类:` C ` 类不能扩展多个类
解决方法:Java不支持多继承,但支持多重继承(多层继承)。
//Java 支持多层继承(链式继承)
class A {}
class B extends A {}
class C extends B {} // C 间接继承 A
2.子类继承父类后,除私有的不支持继承、构造方法不支持继承。其它的全部会继承。
①访问权限:
public 和 protected 成员:子类可以继承父类的public和protected成员(字段和方法),并直接访问它们。
private 成员:子类无法继承父类的private成员。若需操作父类的private成员,需通过父类提供的public或protected方法(如getter/setter)。
默认权限(包级私有):若子类与父类在同一个包内,子类可以继承默认权限的成员;否则不能。
示例:
class Parent {
private int privateVar; // 不可继承
protected int protectedVar; // 可继承
public int publicVar; // 可继承
private void privateMethod() {} // 不可继承
protected void protectedMethod() {} // 可继承
public void publicMethod() {} // 可继承
}
class Child extends Parent {
void accessMembers() {
// System.out.println(privateVar); // 编译错误
System.out.println(protectedVar); // 允许
System.out.println(publicVar); // 允许
// privateMethod(); // 编译错误
protectedMethod(); // 允许
publicMethod(); // 允许
}
}
②构造方法:
子类构造方法默认调用父类的无参构造方法(通过 super())。 如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法(super(参数))。
3.一个类没有显示继承任何类时,默认继承java.lang.Object类。
在Java中,如果一个类没有显式继承其他类(即没有使用extends关键字),它会自动隐式继承java.lang.Object类。这是Java语言设计的核心机制,确保所有类都有一个共同的根类,形成统一的类型体系。
(1).为什么需要隐式继承Object类?
统一类型系统:所有对象(包括数组)都直接或间接继承Object,这使得Object可以作为通用类型使用(如泛型容器List
提供基础方法:Object类定义了对象的基本行为方法,如:
toString():返回对象的字符串表示。
equals():比较对象是否相等。
hashCode():返回对象的哈希码。
getClass():获取对象的运行时类信息。
clone():创建对象的副本。
finalize():垃圾回收前的清理操作(已弃用)。
(2).验证隐式继承的示例
示例1:自定义空类
// 没有显式继承任何类
public class MyClass {
// 空类
}
public class Main {
public static void main(String[] args) {
MyClass obj = new MyClass();
// 调用Object类的方法
System.out.println(obj.toString()); // 输出类似 MyClass@1b6d3586
System.out.println(obj.getClass()); // 输出 class MyClass
}
}
示例2:通过反射验证父类
import java.lang.reflect.Modifier;
public class Main {
public static void main(String[] args) {
Class
Class> superClass = clazz.getSuperclass();
System.out.println("MyClass的父类是: " + superClass.getName());
// 输出: MyClass的父类是: java.lang.Object
}
}
(3).特殊情况说明
接口不继承Object:
接口(interface)虽然不能显式继承Object,但实现接口的类仍然继承Object。
接口中声明的方法(如toString())默认是public abstract的,而Object中的同名方法是具体实现,二者不冲突。
数组类型:
所有数组类型(如int[]、String[])也隐式继承Object。
int[] arr = new int[5];
System.out.println(arr instanceof Object); // 输出 true 枚举类:
枚举类(enum)隐式继承java.lang.Enum,而Enum本身继承Object,因此枚举类间接继承Object。
(4).隐式继承的底层逻辑
当编译以下代码时:
public class MyClass { }
编译器会自动补全继承关系,等效于:
public class MyClass extends java.lang.Object { }
(5).总结
场景继承关系类未显式继承任何父类默认继承java.lang.Object类显式继承其他类直接继承指定的父类,间接继承Object(除非父类链中断)接口(interface)不继承Object,但实现类仍继承Object
通过隐式继承Object,Java确保了所有对象的行为一致性和可操作性,这是实现多态、反射、垃圾回收等高级特性的基础。
四. 继承的示例
父类 Animal
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + " is eating.");
}
}
子类 Cat
public class Cat extends Animal {
public Cat(String name, int age) {
super(name, age); // 调用父类构造方法
}
// 子类扩展方法
public void meow() {
System.out.println(name + " says meow!");
}
// 重写父类方法(方法重写/Override)
@Override
public void eat() {
System.out.println(name + " is eating fish.");
}
}
测试类
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("Tom", 2);
cat.eat(); // 输出:Tom is eating fish.
cat.meow(); // 输出:Tom says meow!
}
}
五、继承中变量的访问特点
在子类方法中访问一个变量
最先在子类局部范围找,如果没有就在子类成员范围找,最后在父类成员范围找,如果都没有就报错(不考虑父亲的父亲...)。
1.情况 1:完整代码(子类有成员变量 + 局部变量)
例如:创建一个父类Fu
public class Fu {
public int age = 10; // 父类成员变量
}
创建一个子类Zi
public class Zi extends Fu {
public int heigth = 180; // 子类独有变量
public int age = 20; // 隐藏父类的同名变量,如果没有这句,和下面那句,输出的是10
public void show() {
int age = 30; // 局部变量(优先级最高),如果没有这句,输出的是20
System.out.println(age); // 输出局部变量
System.out.println(heigth);
}
}
创建一个测试类Test
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show(); // 调用方法
}
}
运行结果:
解释: 局部变量 age=30 优先级最高,直接输出;heigth 访问子类成员变量。
2.情况 2:去掉局部变量 age=30
父类 Fu
public class Fu {
public int age = 10; // 父类成员变量
}
子类 Zi
public class Zi extends Fu {
public int heigth = 180;
public int age = 20; // 隐藏父类的同名变量
public void show() {
// int age = 30; // 注释掉局部变量
System.out.println(age); // 访问子类成员变量
System.out.println(heigth);
}
}
测试类 Test(同上)
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果:
解释: 没有局部变量时,访问子类成员变量 age=20。
3.情况 3:去掉子类成员变量 age 和局部变量 age
父类 Fu
public class Fu {
public int age = 10; // 父类成员变量
}
子类 Zi
public class Zi extends Fu {
public int heigth = 180;
// public int age = 20; // 注释掉子类成员变量
public void show() {
// int age = 30; // 注释掉局部变量
System.out.println(age); // 访问父类成员变量
System.out.println(heigth);
}
}
测试类 Test(同上)
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果:
解释: 子类和局部都没有 age 时,向上查找父类变量 age=10。
4.总结表格
情况代码变化输出结果访问顺序子类有成员变量 + 局部变量保留所有变量30,180局部 → 子类 → 父类仅子类有成员变量去掉局部变量20,180子类 → 父类仅父类有成员变量去掉子类成员变量和局部变量10,180父类
5.关键规则
就近原则:优先访问离当前作用域最近的变量(局部 > 子类 > 父类)。
显式访问父类变量:若需要强制访问父类变量,使用 super.age。
变量隐藏:子类定义同名变量会隐藏父类变量,但不会覆盖(父类变量仍存在)。
编译报错:如果局、子类、父类都没有 age,编译报错。
六. super 关键字
作用:访问父类的成员(属性、方法、构造方法)。
1. super 关键字用法及与 this 关键字对比
1.1 this 与 super 关键字概念
this 关键字:代表本类对象的引用,指向调用该方法的对象。通常在当前类里使用,所以常说它代表本类对象的引用。super 关键字:代表父类存储空间的标识,可理解为父类对象引用。
1.2 this 与 super 关键字具体用法对比
关键字访问成员变量访问构造方法访问成员方法thisthis.成员变量:用于访问本类成员变量this(...):用于访问本类构造方法this.成员方法(...):用于访问本类成员方法supersuper.成员变量:用于访问父类成员变量super(...):用于访问父类构造方法super.成员方法(...):用于访问父类成员方法
1.3 代码示例及结果
// 父类 Fu
public class Fu {
public int age = 10;
}
// 子类 Zi
public class Zi extends Fu {
public int age = 20;
public void show() {
int age = 30;
System.out.println(age); // 输出 30,访问局部变量 age
System.out.println(this.age); // 访问本类中的成员变量 age
System.out.println(super.age); // 访问 Fu 类中的成员变量 age
}
}
// 测试类 Test
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果:
七、继承中构造方法的访问特点
子类中所有的构造方法默认都会访问父类中无参的构造方法。原因在于子类会继承父类的数据,可能还会使用这些数据,所以在子类初始化之前,必须先完成父类数据的初始化。
每个子类构造方法的第一条语句默认都是 super()。
1.代码示例及结果
创建一个父类Fu
// 父类 Fu
public class Fu {
public Fu() {
System.out.println("Fu 中无参构造方法被调用");
}
public Fu(int age) {
System.out.println("Fu 中带参构造方法被调用");
}
}
创建一个子类Zi
// 子类 Zi
public class Zi extends Fu {
public Zi() {
// super(); 即使不写,默认也会有
System.out.println("Zi 中无参构造方法被调用");
}
public Zi(int age) {
// super(); 即使不写,默认也会有
System.out.println("Zi 中带参构造方法被调用");
}
}
测试:Test
// 测试类 Test
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println("-------------------");
Zi zi = new Zi(18);
}
}
运行结果:
2.父类只有带参构造方法的处理办法
如果父类中没有无参构造方法,只有带参构造方法,有以下两种解决办法:
使用 super 关键字显式调用父类带参构造方法:在子类构造方法里,通过 super(参数) 的形式显式调用父类的带参构造方法。在父类中提供无参构造方法:为父类添加一个无参构造方法,这样子类构造方法就可以默认调用它。推荐采用这种方式,能避免很多不必要的错误。
例如:创建一个父类Fu
// 父类 Fu
public class Fu {
public Fu(int age) {
System.out.println("Fu 中带参构造方法被调用");
}
}
创建一个子类Zi
// 子类 Zi
public class Zi extends Fu {
public Zi() {
super(18);
System.out.println("Zi 中无参构造方法被调用");
}
public Zi(int age) {
super(18);
System.out.println("Zi 中带参构造方法被调用");
}
}
测试:Test
// 测试类 Test
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
System.out.println("-------------------");
Zi zi = new Zi(18);
}
}
运行结果:
八、继承中成员方法的访问特点
通过子类对象访问一个方法:
先子类成员范围找,如果找不到就在父类成员范围找,如果都没有就报错(不考虑父亲的父亲...)
情况一:子类有该方法,父类无该方法
当子类中定义了某个方法,而父类中没有定义该方法时,子类对象调用此方法会直接执行子类中的实现。
示例代码:
// 父类
class Fu {
// 父类没有定义 method 方法
}
// 子类
class Zi extends Fu {
public void method() {
System.out.println("Zi 中 method() 方法被调用");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.method();
}
}
运行结果为:
解释:
因为在子类对象 z 调用 method 方法时,会先在子类成员范围查找,找到了该方法,所以直接执行子类的 method 方法。
情况二:子类无该方法,父类有该方法
若子类中没有定义某个方法,但父类中定义了,子类对象调用此方法会执行父类中的实现。
示例代码:
// 父类
class Fu {
public void show() {
System.out.println("Fu 中 show() 方法被调用");
}
}
// 子类
class Zi extends Fu {
// 子类没有定义 show 方法
}
// 测试类
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果为:
解释:
当子类对象 z 调用 show 方法时,在子类成员范围未找到该方法,就会到父类成员范围查找,找到后执行父类的 show 方法。
情况三:子类和父类都有该方法(方法重写)
当子类和父类都定义了相同签名(方法名、参数列表和返回类型相同)的方法时,这就是方法重写,子类对象调用该方法会执行子类重写后的实现。若需要调用父类的原始方法,可以使用 super 关键字。
示例代码:
// 父类
class Fu {
public void show() {
System.out.println("Fu 中 show() 方法被调用");
}
}
// 子类
class Zi extends Fu {
@Override
public void show() {
super.show();
System.out.println("Zi 中 show() 方法被调用");
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
运行结果为:
解释:
子类对象 z 调用 show 方法时,先在子类成员范围找到重写后的 show 方法。在子类的 show 方法里,通过 super.show() 调用了父类的 show 方法,所以先输出父类 show 方法的信息,接着输出子类 show 方法的信息。
情况四:子类和父类都无该方法
如果子类和父类中都没有定义某个方法,子类对象调用该方法会导致编译错误。
// 父类
class Fu {
// 父类没有定义 test 方法
}
// 子类
class Zi extends Fu {
// 子类也没有定义 test 方法
}
// 测试类
public class Test {
public static void main(String[] args) {
Zi z = new Zi();
// 下面这行代码会编译报错
// z.test();
}
}
结果及解释
由于 test 方法在子类和父类中都未定义,当尝试调用 z.test() 时,编译器会报错,提示找不到该方法。
综上所述,在继承中通过子类对象访问成员方法时,Java 会按照先子类成员范围、再父类成员范围的顺序查找方法,若都找不到则会报错。
九、继承的用途
代码复用:子类可以直接使用父类的属性和方法。
扩展功能:子类可以添加新的属性和方法。
多态实现:子类可以重写父类方法,实现多态行为。
十、方法重写(Override)
🟩回顾方法重载 overload
* 1. 什么时候考虑使用方法重载?
* 在一个类中,如果功能相似,可以考虑使用方法重载。
* 这样做的目的是:代码美观,方便编程。
*
* 2. 当满足什么条件的时候构成方法重载?
* 条件1:在同一个类中。
* 条件2:相同的方法名。
* 条件3:不同的参数列表:类型,个数,顺序
*
* 3. 方法重载机制属于编译阶段的功能。(方法重载机制是给编译器看的。)
*
* 🟦方法覆盖/override/方法重写/overwrite
* 1. 什么时候考虑使用方法重写?
* 当从父类中继承过来的方法,无法满足子类的业务需求时。
*
* 2. 当满足什么条件的时候,构成方法重写?
* 条件1:方法覆盖发生在具有继承关系的父子类之间。
* 条件2:具有相同的方法名(必须严格一样)
* 条件3:具有相同的形参列表(必须严格一样)
* 条件4:具有相同的返回值类型(可以是子类型)
*
* 3. 关于方法覆盖的细节:
* 3.1 当子类将父类方法覆盖之后,将来子类对象调用方法的时候,一定会执行重写之后的方法。
* 3.2 在java语言中,有一个注解,这个注解可以在编译阶段检查这个方法是否是重写了父类的方法。
* @Override注解是JDK5引入,用来标注方法,被标注的方法必须是重写父类的方法,如果不是重写的方法,编译器会报错。
* @Override注解只在编译阶段有用,和运行期无关。
* 3.3 如果返回值类型是引用数据类型,那么这个返回值类型可以是原类型的子类型
* 3.4 访问权限不能变低,可以变高。
* 3.5 抛出异常不能变多,可以变少。(后面学习异常的时候再说。)
* 3.6 私有的方法,以及构造方法不能继承,因此他们不存在方法覆盖。
* 3.7 方法覆盖针对法的是实例方法。和静态方法无关。(讲完多态再说。)
* 3.8 方法覆盖针对的是实例方法。和实例变量没有关系。
定义:子类重新实现父类的方法。
规则:
方法名、参数列表和返回类型必须与父类方法相同。
访问权限不能比父类方法更严格(如父类是 public,子类不能是 private)。
使用 @Override 注解显式声明重写(非强制,但推荐)。
十一、 继承的注意事项
避免过度继承:继承层次过深会导致代码复杂度增加。
优先使用组合:如果逻辑关系不是“is-a”,应使用组合(Composition)而非继承。
final 类:被 final 修饰的类不能被继承。
十二、 继承与接口的区别
特性继承接口(Interface)关系“is-a” 关系(父子关系)“can-do” 关系(能力定义)多继承不支持支持(一个类可实现多个接口)实现方式extends 关键字implements 关键字成员类型可以有属性、具体方法、构造方法只能有抽象方法和默认方法
十三. 总结
继承 是 Java 实现代码复用和多态的核心机制。
子类通过 extends 继承父类,可重写方法或扩展功能。
合理使用继承和组合,避免设计复杂的类层次结构。