Java 要求,如果您在构造函数中调用 this()
或 super()
,它必须是第一条语句。为什么?
例如:
public class MyClass {
public MyClass(int x) {}
}
public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
int c = a + b;
super(c); // COMPILE ERROR
}
}
Sun 编译器说,call to super must be first statement in constructor
。 Eclipse 编译器说,Constructor call must be the first statement in a constructor
。
但是,您可以通过稍微重新安排代码来解决此问题:
public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
super(a + b); // OK
}
}
这是另一个例子:
public class MyClass {
public MyClass(List list) {}
}
public class MySubClassA extends MyClass {
public MySubClassA(Object item) {
// Create a list that contains the item, and pass the list to super
List list = new ArrayList();
list.add(item);
super(list); // COMPILE ERROR
}
}
public class MySubClassB extends MyClass {
public MySubClassB(Object item) {
// Create a list that contains the item, and pass the list to super
super(Arrays.asList(new Object[] { item })); // OK
}
}
因此,在调用 super()
之前,它不会阻止您执行逻辑。它只是阻止您执行无法放入单个表达式的逻辑。
调用 this()
也有类似的规则。编译器说,call to this must be first statement in constructor
。
为什么编译器有这些限制?你能给出一个代码示例,如果编译器没有这个限制,会发生一些不好的事情吗?
父类的构造函数需要在子类的构造函数之前调用。这将确保如果您在构造函数中调用父类的任何方法,则父类已经正确设置。
您正在尝试做的事情,将 args 传递给超级构造函数是完全合法的,您只需要在执行过程中内联构造这些 args,或者将它们传递给您的构造函数,然后将它们传递给 super
:
public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
super(myArray);
}
}
如果编译器没有强制执行此操作,您可以这样做:
public MySubClassB extends MyClass {
public MySubClassB(Object[] myArray) {
someMethodOnSuper(); //ERROR super not yet constructed
super(myArray);
}
}
在父类具有默认构造函数的情况下,编译器会自动为您插入对 super 的调用。由于 Java 中的每个类都继承自 Object
,因此必须以某种方式调用对象构造函数,并且必须首先执行它。编译器自动插入 super() 允许这样做。强制 super 首先出现,强制构造函数主体以正确的顺序执行,即:Object ->父->孩子-> ChildOfChild ->快快快
我通过链接构造函数和静态方法找到了解决这个问题的方法。我想做的看起来像这样:
public class Foo extends Baz {
private final Bar myBar;
public Foo(String arg1, String arg2) {
// ...
// ... Some other stuff needed to construct a 'Bar'...
// ...
final Bar b = new Bar(arg1, arg2);
super(b.baz()):
myBar = b;
}
}
所以基本上基于构造函数参数构造一个对象,将对象存储在一个成员中,并将对该对象的方法的结果传递给 super 的构造函数。使成员成为最终成员也相当重要,因为类的性质是它是不可变的。请注意,实际上,构造 Bar 实际上需要一些中间对象,因此在我的实际用例中它不能简化为单行。
我最终使它像这样工作:
public class Foo extends Baz {
private final Bar myBar;
private static Bar makeBar(String arg1, String arg2) {
// My more complicated setup routine to actually make 'Bar' goes here...
return new Bar(arg1, arg2);
}
public Foo(String arg1, String arg2) {
this(makeBar(arg1, arg2));
}
private Foo(Bar bar) {
super(bar.baz());
myBar = bar;
}
}
合法的代码,它完成了在调用超级构造函数之前执行多条语句的任务。
super
之前执行逻辑时,您最好使用组合而不是继承。
因为 JLS 是这么说的。是否可以以兼容的方式更改 JLS 以允许它?是的。
但是,这会使语言规范复杂化,而这已经足够复杂了。这不是一件非常有用的事情,并且有一些方法可以解决它(使用静态方法或 lambda 表达式 this(fn())
的结果调用另一个构造函数 - 该方法在另一个构造函数之前调用,因此也是超级构造函数)。所以做改变的功率重量比是不利的。
请注意,仅此规则不会阻止在超类完成构造之前使用字段。
考虑这些非法的例子。
super(this.x = 5);
super(this.fn());
super(fn());
super(x);
super(this instanceof SubClass);
// this.getClass() would be /really/ useful sometimes.
这个例子是合法的,但是是“错误的”。
class MyBase {
MyBase() {
fn();
}
abstract void fn();
}
class MyDerived extends MyBase {
void fn() {
// ???
}
}
在上面的示例中,如果 MyDerived.fn
需要来自 MyDerived
构造函数的参数,则需要使用 ThreadLocal
来简化它们。 ;(
顺便说一句,从 Java 1.4 开始,包含外部 this
的合成字段是在调用内部类超级构造函数之前分配的。这会在针对早期版本编译的代码中导致特殊的 NullPointerException
事件。
另请注意,在存在不安全发布的情况下,除非采取预防措施,否则可以通过其他线程重新排序查看构造。
2018 年 3 月编辑:在消息 Records: construction and validation 中,Oracle 建议删除此限制(但与 C# 不同,this
在构造函数链接之前将绝对未分配 (DU))。
从历史上看,this() 或 super() 必须在构造函数中排在第一位。这种限制从未流行过,并且被认为是任意的。有许多微妙的原因,包括对 invokespecial 的验证,导致了这种限制。多年来,我们已经在 VM 级别解决了这些问题,以至于考虑解除这个限制变得切实可行,不仅是为了记录,而是为了所有构造函数。
仅仅因为这是继承哲学。根据 Java 语言规范,构造函数的主体是这样定义的:
ConstructorBody: { ExplicitConstructorInvocation BlockStatementsopt }
构造函数主体的第一条语句可以是
显式调用同一类的另一个构造函数(使用关键字“this”);或者
直接超类的显式调用(通过使用关键字“super”)
如果构造函数体不是以显式构造函数调用开始,并且被声明的构造函数不是原始类 Object 的一部分,则构造函数体隐式以超类构造函数调用“super();”开始,调用它的直接超类不带参数。依此类推.. 将有一个完整的构造函数链一直调用到 Object 的构造函数; “Java 平台中的所有类都是对象的后代”。这个东西被称为“构造函数链接”。
为什么会这样?而Java之所以以这种方式定义ConstructorBody,是因为它们需要维护对象的层次结构。记住继承的定义;它正在扩展一个类。话虽如此,你不能扩展不存在的东西。需要先创建基类(超类),然后才能派生它(子类)。这就是为什么他们称它们为 Parent 和 Child 类;没有父母就不能生孩子。
在技术层面上,子类从其父类继承所有成员(字段、方法、嵌套类)。而且由于构造函数不是成员(它们不属于对象。它们负责创建对象),因此它们不会被子类继承,但可以调用它们。而且由于在创建对象时只执行了一个构造函数。那么我们如何在创建子类对象的时候保证超类的创建呢?因此,“构造函数链接”的概念;所以我们有能力从当前构造函数中调用其他构造函数(即super)。 Java 要求此调用是子类构造函数中的第一行,以维护和保证层次结构。他们假设如果您不首先显式创建父对象(就像您忘记了它一样),他们会为您隐式创建。
此检查在编译期间完成。但是我不确定运行时会发生什么,我们会得到什么样的运行时错误,如果 Java 在我们显式尝试从其中间的子类的构造函数中执行基本构造函数时没有抛出编译错误身体而不是从第一行开始......
this = [new object]
并要求在将 this
用作构造函数返回之前定义它在语义上就足够了达到既定目标。无法将父构造函数调用包装在 try-catch-rethrow
或 try/finally
块中,使得子类构造函数不可能承诺不抛出超类构造函数可能抛出的东西,即使子类能够保证......
try
块中调用构造函数,如果构造函数失败,则丢弃容器中的任何资源。
我相当确定(那些熟悉 Java 规范的人)这是为了防止您(a)被允许使用部分构造的对象,以及(b)强制父类的构造函数在“新鲜“ 目的。
“坏”事情的一些例子是:
class Thing
{
final int x;
Thing(int x) { this.x = x; }
}
class Bad1 extends Thing
{
final int z;
Bad1(int x, int y)
{
this.z = this.x + this.y; // WHOOPS! x hasn't been set yet
super(x);
}
}
class Bad2 extends Thing
{
final int y;
Bad2(int x, int y)
{
this.x = 33;
this.y = y;
super(x); // WHOOPS! x is supposed to be final
}
}
Bad1
和 Bad2
应该在那里扩展 Thing
吗?
Bad2
,因为 x
在 Thing
中声明,并且不能在其他任何地方设置。至于 Bad1
,您肯定是对的,但是当超级构造函数调用在子类中重写的方法访问子类的(尚未初始化的)变量时,可能会发生类似的事情。因此,限制有助于防止问题的一部分……恕我直言,这是不值得的。
您问为什么,而其他答案,imo,并没有真正说明为什么可以调用您的超级构造函数,但前提是它是第一行。原因是您并没有真正调用构造函数。在 C++ 中,等价的语法是
MySubClass: MyClass {
public:
MySubClass(int a, int b): MyClass(a+b)
{
}
};
当您像这样在左大括号之前看到初始化器子句时,您就知道它很特别。它在任何其他构造函数运行之前运行,实际上在任何成员变量被初始化之前运行。 Java 并没有什么不同。有一种方法可以让一些代码(其他构造函数)在构造函数真正开始之前运行,在子类的任何成员被初始化之前。这种方式就是将“调用”(例如super
)放在第一行。 (在某种程度上,super
或 this
在第一个左大括号之前,即使您在之后键入它,因为它将在您到达所有内容完全构造之前执行。)任何其他代码在左大括号(如 int c = a + b;
)之后让编译器说“哦,好的,没有其他构造函数,我们可以初始化所有内容。”所以它运行并初始化你的超类和你的成员等等,然后在左大括号之后开始执行代码。
如果在几行之后,它遇到一些代码说“哦,是的,当你构造这个对象时,这是我希望你传递给基类的构造函数的参数”,那就太晚了,它不会有任何意义。所以你得到一个编译器错误。
因此,它不会阻止您在调用 super 之前执行逻辑。它只是阻止您执行无法放入单个表达式的逻辑。
实际上,您可以使用多个表达式执行逻辑,您只需将代码包装在静态函数中并在 super 语句中调用它。
使用您的示例:
public class MySubClassC extends MyClass {
public MySubClassC(Object item) {
// Create a list that contains the item, and pass the list to super
super(createList(item)); // OK
}
private static List createList(item) {
List list = new ArrayList();
list.add(item);
return list;
}
}
super()/this()
之前不强制执行任何实例方法或继承方法调用,而不是选择将 super()/this()
作为第一个语句。所以也许 OP 正在询问为什么不这样做。
我完全同意,限制太强了。使用静态辅助方法(如 Tom Hawtin - tackline 建议的那样)或将所有“pre-super() 计算”推入参数中的单个表达式并不总是可行的,例如:
class Sup {
public Sup(final int x_) {
//cheap constructor
}
public Sup(final Sup sup_) {
//expensive copy constructor
}
}
class Sub extends Sup {
private int x;
public Sub(final Sub aSub) {
/* for aSub with aSub.x == 0,
* the expensive copy constructor is unnecessary:
*/
/* if (aSub.x == 0) {
* super(0);
* } else {
* super(aSub);
* }
* above gives error since if-construct before super() is not allowed.
*/
/* super((aSub.x == 0) ? 0 : aSub);
* above gives error since the ?-operator's type is Object
*/
super(aSub); // much slower :(
// further initialization of aSub
}
}
正如 Carson Myers 所建议的,使用“对象尚未构造”异常会有所帮助,但在每个对象构造期间检查此异常会减慢执行速度。我更喜欢 Java 编译器,它可以做出更好的区分(而不是间接禁止 if 语句但允许参数中的 ? 运算符),即使这会使语言规范复杂化。
?:
构造的类型如何让您感到惊讶的绝佳示例。我在阅读时在想,“这不是不可能 --- 只需使用三元操作......哦。”。
我找到了一个解决方法。
这不会编译:
public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
int c = a + b;
super(c); // COMPILE ERROR
doSomething(c);
doSomething2(a);
doSomething3(b);
}
}
这有效:
public class MySubClass extends MyClass {
public MySubClass(int a, int b) {
this(a + b);
doSomething2(a);
doSomething3(b);
}
private MySubClass(int c) {
super(c);
doSomething(c);
}
}
我的猜测是,他们这样做是为了让编写处理 Java 代码的工具的人的生活更轻松,在某种程度上也让阅读 Java 代码的人更轻松。
如果您允许 super()
或 this()
调用移动,则需要检查更多变体。例如,如果您将 super()
或 this()
调用移到条件 if()
中,它可能必须足够聪明才能将隐式 super()
插入 else
。如果您调用 super()
两次,或者同时使用 super()
和 this()
,它可能需要知道如何报告错误。它可能需要在调用 super()
或 this()
之前禁止接收器上的方法调用,并确定何时变得复杂。
让每个人都做这些额外的工作似乎是成本大于收益。
你能给出一个代码示例,如果编译器没有这个限制,会发生一些不好的事情吗?
class Good {
int essential1;
int essential2;
Good(int n) {
if (n > 100)
throw new IllegalArgumentException("n is too large!");
essential1 = 1 / n;
essential2 = n + 2;
}
}
class Bad extends Good {
Bad(int n) {
try {
super(n);
} catch (Exception e) {
// Exception is ignored
}
}
public static void main(String[] args) {
Bad b = new Bad(0);
// b = new Bad(101);
System.out.println(b.essential1 + b.essential2);
}
}
构造期间的异常几乎总是表明正在构造的对象无法正确初始化,现在处于不良状态,不可用,并且必须进行垃圾回收。但是,子类的构造函数能够忽略其超类之一中发生的异常并返回部分初始化的对象。在上面的示例中,如果给 new Bad()
的参数是 0 或大于 100,则 essential1
和 essential2
都没有正确初始化。
你可能会说忽略异常总是一个坏主意。好的,这是另一个例子:
class Bad extends Good {
Bad(int n) {
for (int i = 0; i < n; i++)
super(i);
}
}
有趣,不是吗?在这个例子中我们创建了多少个对象?一?二?或者也许什么都没有……
允许在构造函数中间调用 super()
或 this()
将打开一个由可恶构造函数组成的潘多拉魔盒。
另一方面,我理解在调用 super()
或 this()
之前经常需要包含一些静态部分。这可能是任何不依赖 this
引用的代码(事实上,它已经存在于构造函数的最开始,但在 super()
或 this()
返回之前不能有序使用)并且需要进行此类调用。此外,与任何方法一样,在调用 super()
或 this()
之前创建的一些局部变量可能会在它之后需要。
在这种情况下,您有以下机会:
使用此答案中提供的模式,可以规避限制。等待 Java 团队允许 pre-super() 和 pre-this() 代码。这可以通过对 super() 或 this() 可能出现在构造函数中的位置施加限制来完成。实际上,即使是今天的编译器也能够区分好和坏(或潜在的坏)情况,其程度足以安全地允许在构造函数的开头添加静态代码。事实上,假设 super() 和 this() 返回这个引用,反过来,你的构造函数有
return this;
在最后。以及编译器拒绝代码
public int get() {
int x;
for (int i = 0; i < 10; i++)
x = i;
return x;
}
public int get(int y) {
int x;
if (y > 0)
x = y;
return x;
}
public int get(boolean b) {
int x;
try {
x = 1;
} catch (Exception e) {
}
return x;
}
如果出现错误“变量 x 可能尚未初始化”,它可以对 this
变量执行此操作,就像对任何其他局部变量一样对其进行检查。唯一的区别是 this
不能通过 super()
或 this()
调用以外的任何方式分配(并且,像往常一样,如果在构造函数中没有这样的调用,则 super()
由编译器在开头隐式插入) 并且可能不会被分配两次。如果有任何疑问(例如在第一个 get()
中,实际上总是分配 x
),编译器可能会返回错误。这比在 super()
或 this()
之前有注释之外的任何构造函数上简单地返回错误要好。
您可以在调用其构造函数之前使用匿名初始化程序块来初始化子项中的字段。这个例子将演示:
public class Test {
public static void main(String[] args) {
new Child();
}
}
class Parent {
public Parent() {
System.out.println("In parent");
}
}
class Child extends Parent {
{
System.out.println("In initializer");
}
public Child() {
super();
System.out.println("In child");
}
}
这将输出:
在父项中 在初始化程序中 在子项中
System.out.println("In initializer")
作为第一行,不是吗?有用的是一种在构造 parent 之前 执行代码的方法。
构造函数按派生顺序完成执行是有道理的。因为超类不知道任何子类,所以它需要执行的任何初始化都与子类执行的任何初始化是分开的,并且可能是其先决条件。因此,它必须首先完成其执行。
一个简单的演示:
class A {
A() {
System.out.println("Inside A's constructor.");
}
}
class B extends A {
B() {
System.out.println("Inside B's constructor.");
}
}
class C extends B {
C() {
System.out.println("Inside C's constructor.");
}
}
class CallingCons {
public static void main(String args[]) {
C c = new C();
}
}
该程序的输出是:
Inside A's constructor
Inside B's constructor
Inside C's constructor
我知道我参加聚会有点晚了,但我已经使用过几次这个技巧(而且我知道这有点不寻常):
我用一种方法创建了一个通用接口 InfoRunnable<T>
:
public T run(Object... args);
如果我需要在将它传递给构造函数之前做一些事情,我就这样做:
super(new InfoRunnable<ThingToPass>() {
public ThingToPass run(Object... args) {
/* do your things here */
}
}.run(/* args here */));
实际上,super()
是构造函数的第一条语句,因为要确保在构造子类之前它的超类是完整的。即使您的第一条语句中没有 super()
,编译器也会为您添加它!
那是因为您的构造函数依赖于其他构造函数。要使您的构造函数正常工作,其他构造函数必须正常工作,这是依赖的。这就是为什么有必要首先检查在构造函数中由 this() 或 super() 调用的依赖构造函数。如果由 this() 或 super() 调用的其他构造函数有问题,那么点执行其他语句,因为如果调用的构造函数失败,所有语句都会失败。
Java为什么这样做的问题已经得到解答,但是由于我偶然发现了这个问题,希望找到一个更好的替代单线的方法,因此我将分享我的解决方法:
public class SomethingComplicated extends SomethingComplicatedParent {
private interface Lambda<T> {
public T run();
}
public SomethingComplicated(Settings settings) {
super(((Lambda<Settings>) () -> {
// My modification code,
settings.setting1 = settings.setting2;
return settings;
}).run());
}
}
调用静态函数应该执行得更好,但如果我坚持将代码“放在”构造函数中,或者如果我必须更改多个参数并发现定义许多静态方法不利于可读性,我会使用它。
语言:
其他答案已经解决了问题的“为什么”。我将提供一个解决此限制的技巧:
基本思想是劫持 super
语句与您的嵌入语句。这可以通过将您的语句伪装成 expressions 来完成。
管家:
考虑我们想要在调用 super()
之前对 Statement1()
执行 Statement9()
:
public class Child extends Parent {
public Child(T1 _1, T2 _2, T3 _3) {
Statement_1();
Statement_2();
Statement_3(); // and etc...
Statement_9();
super(_1, _2, _3); // compiler rejects because this is not the first line
}
}
编译器当然会拒绝我们的代码。因此,我们可以这样做:
// This compiles fine:
public class Child extends Parent {
public Child(T1 _1, T2 _2, T3 _3) {
super(F(_1), _2, _3);
}
public static T1 F(T1 _1) {
Statement_1();
Statement_2();
Statement_3(); // and etc...
Statement_9();
return _1;
}
}
唯一的限制是父类必须有一个构造函数,它至少接受一个参数,以便我们可以将语句作为表达式潜入。
这是一个更详细的示例:
public class Child extends Parent {
public Child(int i, String s, T1 t1) {
i = i * 10 - 123;
if (s.length() > i) {
s = "This is substr s: " + s.substring(0, 5);
} else {
s = "Asdfg";
}
t1.Set(i);
T2 t2 = t1.Get();
t2.F();
Object obj = Static_Class.A_Static_Method(i, s, t1);
super(obj, i, "some argument", s, t1, t2); // compiler rejects because this is not the first line
}
}
改造成:
// This compiles fine:
public class Child extends Parent {
public Child(int i, String s, T1 t1) {
super(Arg1(i, s, t1), Arg2(i), "some argument", Arg4(i, s), t1, Arg6(i, t1));
}
private static Object Arg1(int i, String s, T1 t1) {
i = Arg2(i);
s = Arg4(s);
return Static_Class.A_Static_Method(i, s, t1);
}
private static int Arg2(int i) {
i = i * 10 - 123;
return i;
}
private static String Arg4(int i, String s) {
i = Arg2(i);
if (s.length() > i) {
s = "This is sub s: " + s.substring(0, 5);
} else {
s = "Asdfg";
}
return s;
}
private static T2 Arg6(int i, T1 t1) {
i = Arg2(i);
t1.Set(i);
T2 t2 = t1.Get();
t2.F();
return t2;
}
}
事实上,编译器可以为我们自动化这个过程。他们只是选择不这样做。
super(F(), _2, _3);
应该是 super(F(_1), _2, _3);
在构造子对象之前,必须创建父对象。如您所知,当您编写这样的课程时:
public MyClass {
public MyClass(String someArg) {
System.out.println(someArg);
}
}
它转向下一个(扩展和超级只是隐藏):
public MyClass extends Object{
public MyClass(String someArg) {
super();
System.out.println(someArg);
}
}
首先我们创建一个 Object
,然后将此对象扩展到 MyClass
。我们不能在 Object
之前创建 MyClass
。简单的规则是必须在子构造函数之前调用父构造函数。但是我们知道类可以有多个构造函数。 Java 允许我们选择一个将被调用的构造函数(它将是 super()
或 super(yourArgs...)
)。因此,当您编写 super(yourArgs...)
时,您重新定义了将被调用以创建父对象的构造函数。您不能在 super()
之前执行其他方法,因为该对象尚不存在(但在 super()
之后将创建一个对象,您将能够做任何您想做的事情)。
那么为什么我们不能在任何方法之后执行 this()
呢?如您所知 this()
是当前类的构造函数。此外,我们的类中可以有不同数量的构造函数,并将它们称为 this()
或 this(yourArgs...)
。正如我所说,每个构造函数都有隐藏方法 super()
。当我们编写自定义 super(yourArgs...)
时,我们使用 super(yourArgs...)
删除 super()
。此外,当我们定义 this()
或 this(yourArgs...)
时,我们还会在当前构造函数中删除 super()
,因为如果 super()
与 this()
在同一方法中,它将创建多个父对象。这就是为什么对 this()
方法施加相同规则的原因。它只是将父对象的创建重新传输到另一个子构造函数,并且该构造函数调用 super()
构造函数来创建父对象。所以,代码实际上是这样的:
public MyClass extends Object{
public MyClass(int a) {
super();
System.out.println(a);
}
public MyClass(int a, int b) {
this(a);
System.out.println(b);
}
}
正如其他人所说,您可以执行如下代码:
this(a+b);
你也可以像这样执行代码:
public MyClass(int a, SomeObject someObject) {
this(someObject.add(a+5));
}
但是你不能执行这样的代码,因为你的方法还不存在:
public MyClass extends Object{
public MyClass(int a) {
}
public MyClass(int a, int b) {
this(add(a, b));
}
public int add(int a, int b){
return a+b;
}
}
此外,您的 this()
方法链中必须有 super()
构造函数。您不能像这样创建对象:
public MyClass{
public MyClass(int a) {
this(a, 5);
}
public MyClass(int a, int b) {
this(a);
}
}
class C
{
int y,z;
C()
{
y=10;
}
C(int x)
{
C();
z=x+y;
System.out.println(z);
}
}
class A
{
public static void main(String a[])
{
new C(10);
}
}
看例子,如果我们调用构造函数 C(int x)
那么 z 的值取决于 y 如果我们不在第一行调用 C()
那么这将是 z 的问题。 z 将无法获得正确的值。
在子类构造函数中添加 super() 的主要目的是编译器的主要工作是将所有类与 Object 类直接或间接连接,这就是编译器检查我们是否提供了 super (参数化)然后编译器不承担任何责任。以便所有实例成员都从 Object 初始化到子类。
这是官方重播:从历史上看,this() 或 super() 必须在构造函数中排在第一位。这种限制从未流行过,并且被认为是任意的。有许多微妙的原因,包括对 invokespecial 的验证,导致了这种限制。多年来,我们已经在 VM 级别解决了这些问题,以至于考虑解除这个限制变得切实可行,不仅是为了记录,而是为了所有构造函数。
Constructor(int x) { this.field1 = x; super(); }
的方式进行某些操作。当然,在您控制代码的理想世界中,您不需要这样做,但情况并非总是如此。我首先查找此内容的原因是因为我很生气,我无法使用它来解决 3rd 方代码中的缺陷。super
之前执行逻辑时,您最好使用组合而不是继承。