java继承是面向对象编程中复用代码的重要方式,通过extends关键字可以让子类获得父类的非私有属性和方法,但在使用过程中有很多细节需要开发者格外注意,否则很容易出现逻辑错误或者编译异常。

继承的基本限制注意点
单继承限制
java中的类只支持单继承,一个子类只能有一个直接父类,无法像某些语言那样同时继承多个父类,这是为了避免多继承带来的菱形调用歧义问题。如果需要复用多个类的功能,可以通过实现多个接口来达成。
// 错误示例:java不支持多继承
class Parent1 {}
class Parent2 {}
// 以下代码会编译报错
// class Child extends Parent1, Parent2 {}
// 正确做法:通过接口实现多能力复用
interface Ability1 {}
interface Ability2 {}
class Child extends Parent1 implements Ability1, Ability2 {}
父类构造方法的调用规则
子类在实例化时,会先调用父类的构造方法完成父类部分的初始化,再执行子类自身的构造方法逻辑。如果子类构造方法没有显式调用父类构造方法,默认会调用父类的无参构造方法;如果父类没有无参构造方法,子类必须显式通过super关键字调用父类的有参构造方法,否则会编译报错。
class Parent {
private String name;
// 父类只有有参构造方法,没有无参构造
public Parent(String name) {
this.name = name;
}
}
class Child extends Parent {
private int age;
// 必须显式调用父类有参构造,否则编译报错
public Child(String name, int age) {
super(name); // 调用父类构造方法必须是构造方法的第一行
this.age = age;
}
}
成员访问相关注意点
私有成员无法被继承
父类中被private修饰的属性和方法属于父类的私有成员,子类无法直接访问,也不算被继承到子类中。如果父类提供了对应的public或者protected的访问方法,子类可以通过这些方法间接操作父类的私有成员。
class Parent {
private String secret = "父类私有属性";
// 提供protected访问方法,子类可以调用
protected String getSecret() {
return secret;
}
}
class Child extends Parent {
public void printSecret() {
// 直接访问secret会编译报错
// System.out.println(secret);
// 通过父类提供的访问方法获取
System.out.println(getSecret());
}
}
访问权限不能降低
如果子类重写了父类的方法,重写方法的访问权限不能比父类原方法的访问权限更严格。比如父类方法是public的,子类重写的方法也必须是public,不能是protected或者private;父类方法是protected的,子类重写的方法可以是protected或者public,不能是private。
class Parent {
public void test() {
System.out.println("父类public方法");
}
protected void demo() {
System.out.println("父类protected方法");
}
}
class Child extends Parent {
// 正确:重写public方法,权限保持public
@Override
public void test() {
System.out.println("子类重写public方法");
}
// 正确:重写protected方法,权限提升为public
@Override
public void demo() {
System.out.println("子类重写方法,权限public");
}
// 错误示例:重写public方法权限改为protected,编译报错
// @Override
// protected void test() {}
}
方法重写相关注意点
重写方法的签名必须一致
子类重写父类方法时,方法名、参数列表、返回值类型都必须和父类方法保持一致。如果是协变返回类型,子类重写方法的返回值可以是父类方法返回值的子类类型,除此之外返回值类型必须完全相同。同时重写方法不能抛出比父类方法更宽泛的受检异常。
class Animal {}
class Dog extends Animal {}
class Parent {
public Animal getAnimal() {
return new Animal();
}
public void show() throws NullPointerException {}
}
class Child extends Parent {
// 正确:协变返回类型,返回值是Animal的子类Dog
@Override
public Dog getAnimal() {
return new Dog();
}
// 错误示例:参数列表不同,属于方法重载不是重写,编译不会报错但不符合重写逻辑
// public Animal getAnimal(String name) {}
// 正确:抛出的异常是NullPointerException,是RuntimeException,不要求更严格
@Override
public void show() throws NullPointerException {}
}
静态方法不能被重写
父类的静态方法属于类本身,不属于实例,因此不能被子类重写。如果子类中定义了和父类静态方法签名完全一致的方法,属于子类的静态方法隐藏了父类的静态方法,调用时取决于引用的类型,而不是实例的实际类型。
class Parent {
public static void staticMethod() {
System.out.println("父类静态方法");
}
}
class Child extends Parent {
// 这里不是重写,是隐藏父类的静态方法
public static void staticMethod() {
System.out.println("子类静态方法");
}
}
class Test {
public static void main(String[] args) {
Parent obj1 = new Child();
obj1.staticMethod(); // 输出:父类静态方法,取决于引用类型Parent
Child obj2 = new Child();
obj2.staticMethod(); // 输出:子类静态方法,取决于引用类型Child
}
}
其他常见注意点
final修饰的类不能被继承
如果一个类被final关键字修饰,那么这个类无法被其他类继承,比如java中的String类就是final类,无法被继承扩展。如果尝试继承final类,会直接编译报错。
final class FinalClass {}
// 以下代码编译报错,无法继承final类
// class SubClass extends FinalClass {}
避免循环继承
继承关系不能形成闭环,比如A继承B,B继承C,C又继承A,这种循环继承在java中是不允许的,编译时会直接报错。设计继承关系时要保证继承链是单向的,从子类到父类最终可以追溯到一个顶层父类比如Object。
super关键字的使用限制
super关键字用来访问父类的成员或者构造方法,只能在子类的方法、构造方法或者实例初始化块中使用,不能在静态方法中使用super,否则会编译报错。同时super调用构造方法时必须放在子类构造方法的第一行,否则也会编译错误。
class Parent {
public int num = 10;
}
class Child extends Parent {
public int num = 20;
public void printNum() {
System.out.println(num); // 输出20,子类自己的num
System.out.println(super.num); // 输出10,父类的num
}
// 错误示例:静态方法中使用super,编译报错
// public static void staticTest() {
// System.out.println(super.num);
// }
}