疯传!Java 继承底层逻辑大揭秘,看完直接拿捏面试官!
一、继承基础概念
1.1 什么是继承
继承是面向对象编程的四大特性之一(封装、继承、多态、抽象),它允许一个类(子类)基于另一个类(父类)来构建,继承父类的属性和方法,同时可以添加新的特性或修改继承的行为。
专业解释:继承是一种"is-a"关系,通过extends关键字实现类之间的层次结构,子类获得父类的非私有成员(属性和方法),并可进行扩展或覆盖。
通俗理解:就像儿子继承父亲的某些特征(如眼睛颜色),但也可以有自己的独特特征(如发型)。在编程中,继承让我们可以复用已有代码,减少重复劳动。
// 父类
class Animal {
String name;
public void eat() {
System.out.println(name + "正在吃东西");
}
}
// 子类继承父类
class Dog extends Animal {
String breed; // 子类特有属性
public void bark() {
System.out.println(name + "汪汪叫"); // 可以使用父类的name属性
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "旺财"; // 继承自Animal的属性
myDog.breed = "金毛"; // Dog自己的属性
myDog.eat(); // 继承自Animal的方法
myDog.bark(); // Dog自己的方法
}
}
1.2 继承的基本语法
继承使用extends关键字:
class SubClass extends SuperClass {
// 子类特有的属性和方法
}
1.3 继承的类型
继承类型 | 描述 | Java支持情况 |
单继承 | 一个子类只能有一个直接父类 | 支持 |
多继承 | 一个子类可以有多个直接父类 | 不支持(通过接口实现) |
多重继承 | 多层级的单继承(A继承B,B继承C) | 支持 |
层次继承 | 多个子类继承同一个父类 | 支持 |
混合继承 | 结合多种继承类型 | 部分支持 |
Java不支持类的多继承,但可以通过接口实现类似效果。
二、继承的核心特性
2.1 方法重写(Override)
当子类需要修改父类方法的行为时,可以重写该方法。
规则:
- 方法名和参数列表必须完全相同
- 返回类型可以是父类方法返回类型的子类(协变返回类型)
- 访问修饰符不能比父类更严格
- 不能抛出比父类方法更多或更宽泛的异常
class Animal {
public void makeSound() {
System.out.println("动物发出声音");
}
public Animal getAnimal() {
return new Animal();
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("喵喵叫");
}
// 协变返回类型示例
@Override
public Cat getAnimal() {
return new Cat();
}
}
2.2 super关键字
super用于引用父类的成员:
- super() - 调用父类构造方法(必须放在子类构造方法的第一行)
- super.method() - 调用父类的方法
- super.field - 访问父类的字段
class Vehicle {
int maxSpeed;
Vehicle(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
void display() {
System.out.println("最大速度: " + maxSpeed);
}
}
class Car extends Vehicle {
String brand;
Car(int maxSpeed, String brand) {
super(maxSpeed); // 调用父类构造方法
this.brand = brand;
}
@Override
void display() {
super.display(); // 调用父类方法
System.out.println("品牌: " + brand);
}
}
2.3 构造方法的继承
子类不继承父类的构造方法,但必须调用父类的构造方法:
- 如果子类构造方法没有显式调用父类构造方法,编译器会自动插入super()
- 如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法
class Parent {
Parent(int x) {
System.out.println("父类构造方法: " + x);
}
}
class Child extends Parent {
Child() {
super(10); // 必须显式调用,因为父类没有无参构造方法
System.out.println("子类构造方法");
}
}
三、继承的高级主题
3.1 访问控制与继承
Java的访问修饰符在继承中的表现:
修饰符 | 同类 | 同包 | 子类 | 不同包 |
private | ||||
default | ||||
protected | ||||
public |
继承中的访问规则:
- 子类可以继承父类的public和protected成员
- 子类不能继承父类的private成员
- 默认(default)访问权限的成员只有在同包时才能被继承
package com.example;
public class Parent {
private String secret = "父类秘密"; // 不能被继承
protected String familyName = "张"; // 可以被继承
String hobby = "钓鱼"; // 同包可继承
public String name = "张三"; // 可以被继承
}
class Child extends Parent {
void show() {
// System.out.println(secret); // 错误: secret在Parent中是private
System.out.println(familyName); // 可以访问
System.out.println(hobby); // 同包可以访问
System.out.println(name); // 可以访问
}
}
3.2 final关键字与继承
final关键字可以用于类、方法和变量:
- final class - 不能被继承
- final method - 不能被重写
- final variable - 值不能被修改(常量)
final class FinalClass { // 这个类不能被继承
final int MAX_VALUE = 100; // 常量
final void finalMethod() { // 这个方法不能被子类重写
System.out.println("这是final方法");
}
}
// class SubClass extends FinalClass {} // 错误: FinalClass不能被继承
class Parent {
void normalMethod() {
System.out.println("普通方法");
}
}
class Child extends Parent {
// @Override
// void finalMethod() {} // 错误: 不能重写final方法
@Override
void normalMethod() {
System.out.println("重写父类方法");
}
}
3.3 抽象类与继承
抽象类是不能实例化的类,用于被继承:
- 用abstract关键字声明
- 可以包含抽象方法(没有实现的方法)
- 子类必须实现所有抽象方法,除非子类也是抽象类
abstract class Shape {
String color;
abstract double area(); // 抽象方法
void setColor(String color) { // 具体方法
this.color = color;
}
}
class Circle extends Shape {
double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() { // 必须实现抽象方法
return Math.PI * radius * radius;
}
}
class Rectangle extends Shape {
double length, width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}
3.4 接口与继承
接口是一种完全抽象的类,Java支持多接口继承:
- 使用interface关键字定义
- 类使用implements实现接口
- 一个类可以实现多个接口
- 接口可以继承其他接口(支持多继承)
interface Flyable {
void fly(); // 默认是public abstract
}
interface Swimmable {
void swim();
}
// 接口可以多继承
interface Amphibious extends Flyable, Swimmable {
void liveBoth();
}
class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子飞");
}
@Override
public void swim() {
System.out.println("鸭子游泳");
}
}
class Frog implements Amphibious {
@Override
public void fly() {
System.out.println("青蛙偶尔滑翔");
}
@Override
public void swim() {
System.out.println("青蛙游泳");
}
@Override
public void liveBoth() {
System.out.println("青蛙两栖生活");
}
}
四、继承的进阶应用
4.1 多态与继承
多态是面向对象的重要特性,允许一个对象表现出多种形态:
- 编译时多态:方法重载
- 运行时多态:方法重写
class Animal {
void makeSound() {
System.out.println("动物声音");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("汪汪");
}
void fetch() {
System.out.println("叼回东西");
}
}
class Cat extends Animal {
@Override
void makeSound() {
System.out.println("喵喵");
}
}
public class TestPolymorphism {
public static void main(String[] args) {
Animal myAnimal; // 父类引用
myAnimal = new Dog(); // 向上转型
myAnimal.makeSound(); // 输出"汪汪" - 运行时多态
// myAnimal.fetch(); // 错误: Animal类没有fetch方法
myAnimal = new Cat();
myAnimal.makeSound(); // 输出"喵喵"
// 向下转型
if (myAnimal instanceof Dog) {
Dog myDog = (Dog) myAnimal;
myDog.fetch();
}
}
}
4.2 继承与组合的对比
继承(is-a)和组合(has-a)是两种代码复用方式:
特性 | 继承 | 组合 |
关系 | is-a | has-a |
耦合度 | 高 | 低 |
灵活性 | 较低 | 较高 |
复用方式 | 白盒复用 | 黑盒复用 |
运行时修改 | 不能 | 可以 |
多继承 | 不支持 | 支持 |
何时使用继承:
- 真正的"is-a"关系
- 需要多态特性
- 子类是父类的特殊化
何时使用组合:
- "has-a"关系
- 需要复用代码但不符合"is-a"
- 需要运行时动态改变行为
// 继承示例
class Engine {
void start() {
System.out.println("引擎启动");
}
}
// 组合示例
class Car {
private Engine engine; // Car has-a Engine
Car(Engine engine) {
this.engine = engine;
}
void start() {
engine.start();
System.out.println("汽车启动");
}
}
// 继承与组合对比
public class Test {
public static void main(String[] args) {
// 继承
Animal dog = new Dog();
dog.makeSound();
// 组合
Engine v8 = new Engine();
Car myCar = new Car(v8);
myCar.start();
}
}
4.3 继承的设计原则
- 里氏替换原则(LSP):子类必须能够替换它们的父类而不影响程序的正确性
- 单一职责原则(SRP):一个类应该只有一个引起变化的原因
- 开闭原则(OCP):对扩展开放,对修改关闭
违反LSP的例子:
class Rectangle {
protected int width, height;
void setWidth(int width) {
this.width = width;
}
void setHeight(int height) {
this.height = height;
}
int getArea() {
return width * height;
}
}
// 正方形是长方形,但行为不同,违反LSP
class Square extends Rectangle {
@Override
void setWidth(int width) {
super.setWidth(width);
super.setHeight(width);
}
@Override
void setHeight(int height) {
super.setWidth(height);
super.setHeight(height);
}
}
public class TestLSP {
static void testArea(Rectangle r) {
r.setWidth(5);
r.setHeight(4);
if (r.getArea() != 20) {
System.out.println("面积计算错误!");
}
}
public static void main(String[] args) {
testArea(new Rectangle()); // 通过
testArea(new Square()); // 失败 - 违反LSP
}
}
五、继承的特殊情况与技巧
5.1 静态成员的继承
静态成员(静态方法和静态变量)属于类而非实例,它们的继承有特殊规则:
- 静态方法可以被继承,但不能被重写(只是隐藏)
- 通过实例调用静态方法不推荐,应该使用类名调用
class Parent {
static String staticField = "父类静态字段";
static void staticMethod() {
System.out.println("父类静态方法");
}
}
class Child extends Parent {
// 这不是重写,只是隐藏父类的静态方法
static void staticMethod() {
System.out.println("子类静态方法");
}
}
public class TestStatic {
public static void main(String[] args) {
Parent.staticMethod(); // 父类静态方法
Child.staticMethod(); // 子类静态方法
Parent p = new Child();
p.staticMethod(); // 不推荐 - 输出"父类静态方法"(编译时绑定)
System.out.println(Child.staticField); // 继承静态字段
}
}
5.2 初始化顺序
对象初始化时的执行顺序:
- 父类静态代码块和静态变量初始化
- 子类静态代码块和静态变量初始化
- 父类实例变量初始化和代码块
- 父类构造方法
- 子类实例变量初始化和代码块
- 子类构造方法
class Parent {
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类实例代码块");
}
Parent() {
System.out.println("父类构造方法");
}
}
class Child extends Parent {
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类实例代码块");
}
Child() {
System.out.println("子类构造方法");
}
}
public class TestInitOrder {
public static void main(String[] args) {
new Child();
/* 输出:
父类静态代码块
子类静态代码块
父类实例代码块
父类构造方法
子类实例代码块
子类构造方法
*/
}
}
5.3 桥接方法
Java编译器在处理泛型方法重写时可能会生成桥接方法,这是为了保持类型安全。
class Parent<T> {
T value;
void setValue(T value) {
this.value = value;
}
}
class Child extends Parent<String> {
@Override
void setValue(String value) {
super.setValue(value.toUpperCase());
}
}
// 编译器会生成一个桥接方法:
// void setValue(Object value) {
// setValue((String)value);
// }
六、继承的最佳实践
6.1 何时使用继承
- 适合使用继承的情况:
- 真正的"is-a"关系
- 需要多态行为
- 父类是稳定的,不太可能改变
- 子类是父类的逻辑扩展
- 不适合使用继承的情况:
- 只是为了复用代码而没有"is-a"关系
- 父类经常变化
- 需要从多个类继承行为(使用组合+接口)
6.2 继承与接口的选择
考虑因素 | 继承 | 接口 |
关系类型 | is-a | can-do |
多重继承 | 不支持 | 支持 |
默认实现 | 有 | Java 8+可以有 |
耦合度 | 高 | 低 |
适合场景 | 代码复用+多态 | 定义行为契约 |
6.3 继承的替代方案
- 组合:将类作为成员变量
- 委托:将任务委托给其他类
- 策略模式:定义算法族,可以相互替换
- 装饰器模式:动态添加职责
// 使用策略模式替代继承的例子
interface PaymentStrategy {
void pay(int amount);
}
class CreditCardPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("信用卡支付: " + amount);
}
}
class AlipayPayment implements PaymentStrategy {
@Override
public void pay(int amount) {
System.out.println("支付宝支付: " + amount);
}
}
class ShoppingCart {
private PaymentStrategy paymentStrategy;
void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
void checkout(int amount) {
paymentStrategy.pay(amount);
}
}
public class TestStrategy {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(100);
cart.setPaymentStrategy(new AlipayPayment());
cart.checkout(200);
}
}
七、常见面试问题与解答
Q1: Java为什么不支持多继承?
A: Java不支持类的多继承主要是为了避免"菱形继承问题"(Diamond Problem),即当两个父类有相同方法时,子类无法确定继承哪个方法。Java通过接口实现多继承,因为接口只有方法声明而没有实现,不会产生歧义。
Q2: 继承和接口有什么区别?
A:
特性 | 继承 | 接口 |
关键字 | extends | implements |
数量 | 单继承 | 多实现 |
方法 | 可以有实现 | Java 8前全是抽象方法 |
变量 | 可以有实例变量 | 只能是public static final |
设计目的 | 代码复用 | 定义契约 |
Q3: 如何防止一个类被继承?
A: 使用final关键字修饰类:
final class CannotBeExtended {
// ...
}
Q4: 抽象类和接口如何选择?
A:
- 使用抽象类:
- 需要包含具体方法和抽象方法
- 需要定义非public static final的字段
- 需要定义构造方法
- 子类之间有明显的层次关系
- 使用接口:
- 需要定义行为契约
- 需要多重继承
- 希望不限制实现类的层次结构
- Java 8+需要提供默认方法实现
八、总结
继承是Java面向对象编程的核心概念之一,正确使用继承可以:
- 提高代码复用性
- 建立清晰的类层次结构
- 实现多态特性
然而,过度使用继承会导致:
- 类之间高耦合
- 系统僵化难以修改
- 违反设计原则
在实际开发中,应该:
- 优先考虑组合而非继承
- 遵循里氏替换原则
- 保持继承层次简单
- 合理使用抽象类和接口
Java继承就像祖传“代码盲盒”,子类拆到好用属性美滋滋,碰上“祖传BUG”,直接原地崩溃,开盒需谨慎!
转发到朋友圈,配文‘我朋友是个天才’,我们就是异父异母的亲兄弟姐妹!