目录
一. 继承的基本概念
二. 继承的语法
三. 继承的核心规则
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>
)。 -
提供基础方法:
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@1b6d3586System.out.println(obj.getClass()); // 输出 class MyClass}
}
示例2:通过反射验证父类
import java.lang.reflect.Modifier;public class Main {public static void main(String[] args) {Class<MyClass> clazz = MyClass.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)@Overridepublic 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; // 隐藏父类的同名变量,如果没有这句,和下面那句,输出的是10public void show() {int age = 30; // 局部变量(优先级最高),如果没有这句,输出的是20System.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
关键字具体用法对比
关键字 | 访问成员变量 | 访问构造方法 | 访问成员方法 |
---|---|---|---|
this | this.成员变量 :用于访问本类成员变量 | this(...) :用于访问本类构造方法 | this.成员方法(...) :用于访问本类成员方法 |
super | super.成员变量 :用于访问父类成员变量 | 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,访问局部变量 ageSystem.out.println(this.age); // 访问本类中的成员变量 ageSystem.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 {@Overridepublic 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
继承父类,可重写方法或扩展功能。 -
合理使用继承和组合,避免设计复杂的类层次结构。