面试题答案
一键面试- 构造函数调用顺序和执行过程:
- 当实例化
C
类对象时,首先会调用顶层抽象类A
的构造函数。这是因为Java中在创建子类对象时,会默认先调用父类的构造函数,以确保父类的成员变量得到正确初始化。A
类构造函数中的初始化逻辑会被执行。 - 接着调用中间层抽象类
B
的构造函数。B
类构造函数同样会执行其内部的初始化逻辑,这些逻辑可能依赖于A
类已经初始化完成的状态。 - 最后调用具体类
C
的构造函数。C
类构造函数完成C
类特有的初始化操作。 - 例如代码如下:
- 当实例化
abstract class A {
public A() {
System.out.println("A类构造函数被调用");
}
}
abstract class B extends A {
public B() {
System.out.println("B类构造函数被调用");
}
}
class C extends B {
public C() {
System.out.println("C类构造函数被调用");
}
}
在main
方法中new C();
执行结果依次输出:
A类构造函数被调用
B类构造函数被调用
C类构造函数被调用
- 可能遇到的陷阱和注意事项:
- 成员变量初始化顺序:父类成员变量会在父类构造函数调用时初始化。如果子类依赖于父类成员变量的特定状态,要确保父类构造函数正确初始化这些变量。例如,如果
A
类有一个成员变量a
,在A
类构造函数中初始化a
,B
类和C
类可能会使用到a
,所以要保证a
的初始化逻辑正确。 - super关键字的使用:如果需要在子类构造函数中显式调用父类特定的构造函数,要使用
super
关键字。并且super
调用必须是子类构造函数的第一行代码。例如,如果A
类有一个带参数的构造函数A(int num)
,B
类构造函数想调用这个构造函数,代码如下:
- 成员变量初始化顺序:父类成员变量会在父类构造函数调用时初始化。如果子类依赖于父类成员变量的特定状态,要确保父类构造函数正确初始化这些变量。例如,如果
abstract class A {
public A(int num) {
System.out.println("A类带参数构造函数被调用,参数:" + num);
}
}
abstract class B extends A {
public B(int num) {
super(num);
System.out.println("B类构造函数被调用");
}
}
class C extends B {
public C(int num) {
super(num);
System.out.println("C类构造函数被调用");
}
}
在main
方法中new C(5);
执行结果依次输出:
A类带参数构造函数被调用,参数:5
B类构造函数被调用
C类构造函数被调用
- 避免在构造函数中调用可重写的方法:如果在
A
类或B
类构造函数中调用一个在C
类中重写的方法,由于此时C
类对象还未完全初始化,可能会导致未预期的行为。例如:
abstract class A {
public A() {
printValue();
}
public void printValue() {
System.out.println("A类的printValue方法");
}
}
abstract class B extends A {
public B() {
super();
}
}
class C extends B {
private int value = 10;
@Override
public void printValue() {
System.out.println("C类的printValue方法,value: " + value);
}
}
在main
方法中new C();
执行结果会输出:
C类的printValue方法,value: 0
这是因为在A
类构造函数调用printValue
方法时,C
类对象还未完全初始化,value
还是默认值0,而不是10,这可能导致错误的结果。