在Java语言中,extends关键字用于建立类与类之间的继承关系,使子类能够复用父类的属性和方法,并在此基础上进行功能扩展。理解它的正确用法,是掌握面向对象编程的基础。继承不仅减少了代码重复,还通过多态机制提升了系统的可扩展性。

一、extends的基本语法形式
使用extends的语法非常直接,在定义子类时,将extends放在类名之后,再写上要继承的父类名称即可。Java只支持单继承,也就是说一个类只能通过extends显式继承一个直接父类,但父类还可以继续继承更上层类,从而形成继承链。
下面是一段最基础的示例代码,展示了如何用extends声明父子类关系:
// 父类
class Animal {
String name;
void eat() {
System.out.println(name + "在吃东西");
}
}
// 子类使用extends继承Animal
class Dog extends Animal {
void bark() {
System.out.println(name + "汪汪叫");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "小黑";
dog.eat(); // 调用继承自父类的方法
dog.bark(); // 调用子类自有方法
}
}
从上面代码可以看出,Dog类通过extends Animal获得了name字段和eat()方法,自身只额外定义了bark()。这种结构让公共逻辑集中在父类,子类专注差异点。
需要注意的是,extends并不继承父类的构造方法,也不继承用private修饰的成员。如果父类将某个字段设为私有,子类必须通过父类提供的public或protected方法间接访问,而不能直接操作该字段。
二、构造方法与super的调用规则
当父类没有显式定义无参构造方法时,子类构造器必须在其第一行通过super(参数)明确调用父类的有参构造,否则编译器会报错。这是因为子类对象实例化时,必须先完成父类部分的初始化。
观察下面这段容易出错的代码,以及修正后的写法:
// 错误示例:父类只有有参构造,子类未调用super
class Parent {
Parent(String msg) {
System.out.println(msg);
}
}
class Child extends Parent {
Child() {
// 编译错误:隐式super()找不到无参构造
}
}
// 正确示例
class ChildFix extends Parent {
ChildFix() {
super("初始化父类");
}
}
在正确示例中,ChildFix的构造方法首行写明了super("初始化父类"),从而把参数传递给父类构造器。如果子类构造器完全没有写super,Java会默认插入无参super(),这就要求父类必须存在可访问的无参构造。
另外,super还可以用于在子类方法中调用被重写的父类方法版本,例如super.eat()。这和构造调用不同,可以出现在子类实例方法的任意位置,用来保留父类原有行为。
三、方法重写与字段隐藏的区别
子类可以定义和父类签名相同的方法,这称为方法重写(Override)。重写要求方法名、参数列表、返回类型一致,且子类方法的访问权限不能比父类更严格。使用@Override注解可以帮助编译器检查是否正确重写。
很多开发者分不清重写和字段隐藏。如果子类和父类定义了同名非静态字段,子类字段会隐藏父类字段,而不是覆盖;通过父类引用访问时,拿到的是父类字段值。示例如下:
class Base {
int value = 10;
void show() {
System.out.println("Base: " + value);
}
}
class Derived extends Base {
int value = 20; // 隐藏父类字段
@Override
void show() {
System.out.println("Derived: " + value);
System.out.println("Base value: " + super.value);
}
}
在上面的结构中,Derived里的value并不会修改Base的value,只是作用域上优先可见。通过super.value仍能读取父类原始值。而show()方法由于加了@Override,在多态调用时会执行子类版本。
静态方法则不存在重写概念。如果子类和父类有相同签名的静态方法,称之为隐藏。通过父类类型调用时执行父类静态方法,通过子类类型调用时执行子类静态方法,不会因对象实际类型而变化。
四、继承中的访问权限控制
extends带来的成员可见性受Java访问修饰符约束。父类的public和protected成员在子类中直接可用;同包下默认权限成员也可见;而private成员仅父类内部可见,子类无法直取。
合理设计父类可见性,能有效防止子类破坏封装。例如将核心状态设为private,提供protected方法供子类扩展,比直接开放字段更安全。
class Account {
private double balance;
protected void addBalance(double amount) {
if (amount > 0) {
balance += amount;
}
}
public double getBalance() {
return balance;
}
}
class SavingAccount extends Account {
void deposit(double money) {
addBalance(money); // 合法:调用protected方法
}
}
上述代码里,balance被私有化,子类SavingAccount不能改值,只能通过addBalance这一受保护通道操作,保证了余额变动逻辑的统一。这种写法在业务系统里十分常见。
当项目规模扩大,滥用extends会导致类层级过重。若两个类之间并非“是一种”的关系,应优先考虑组合而非继承,以免后续维护时耦合度过高。
五、常见误用与规避建议
初学者常把extends当作代码复制工具,让子类继承毫无逻辑关联的类,结果父类修改引起子类大面积故障。继承应表达明确的“is-a”语义,例如Dog是一种Animal才合理。
另一个误区是在构造器里调用可被重写的方法。由于父类构造时子类字段尚未初始化,可能造成空指针或错误状态。看下面危险写法:
class Super {
Super() {
init(); // 调用重写方法
}
void init() {}
}
class Sub extends Super {
private String msg = "hello";
@Override
void init() {
System.out.println(msg.length()); // 可能抛空指针
}
}
在Sub对象创建时,先执行Super构造,其中init()实际绑定到子类重写版,但此时msg还未赋值,访问其方法就会出错。因此父类构造期间应避免调用非final的实例方法。
总结来看,extends关键字本身语法简单,但配合构造链、重写规则和访问权限后,细节较多。写子类前先确认继承关系是否成立,明确父类构造要求,谨慎处理方法重写,才能写出稳健的Java程序。