ChatGPT解决这个技术问题 Extra ChatGPT

为什么 this() 和 super() 必须是构造函数中的第一条语句?

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

为什么编译器有这些限制?你能给出一个代码示例,如果编译器没有这个限制,会发生一些不好的事情吗?

一个好问题。我在 valjok.blogspot.com/2012/09/… 和programmers.exchange 中开始了类似的操作,我在其中展示了在某些情况下必须在 super() 之前初始化子字段。因此,该功能增加了做事的复杂性,而关于“代码安全”的积极影响是否超过了消极影响尚不清楚。是的,超级总是首先有负面影响。令人惊讶的是,没有人提到这一点。我认为这是一个概念性的事情,必须在programmers.exchange中询问
最糟糕的是,这纯粹是一个 Java 限制。在字节码级别没有这样的限制。
好吧,在字节码级别有这个限制是不可能的——这篇文章中的所有例子都违反了这个限制,即使是那些将所有逻辑都塞进一个表达式的例子。

n
nanofarad

父类的构造函数需要在子类的构造函数之前调用。这将确保如果您在构造函数中调用父类的任何方法,则父类已经正确设置。

您正在尝试做的事情,将 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 ->快快快


我认为我不同意,原因有两个……(1)检查 super 是第一个语句不足以防止该问题。例如,您可以输入“super(someMethodInSuper());”在你的构造函数中。这会尝试在构造超类之前访问超类中的方法,即使 super 是第一条语句。 (2) 编译器似乎实现了一个不同的检查,它本身就足以防止这个问题。消息是“在调用超类型构造函数之前无法引用 xxx”。因此,不需要检查 super 是否是第一个语句。
@Joe您是对的,将 super() 作为第一条语句并不会阻止在调用父级之前调用父级上的方法。正如您所提到的,这是一项单独的检查。但是它确实强制执行构造函数的主体的顺序?同意?我相信这就是将 super() 调用作为第一个语句的原因。
考虑到编译器知道您何时访问父方法/字段,我不明白为什么不允许您按照 Constructor(int x) { this.field1 = x; super(); } 的方式进行某些操作。当然,在您控制代码的理想世界中,您不需要这样做,但情况并非总是如此。我首先查找此内容的原因是因为我很生气,我无法使用它来解决 3rd 方代码中的缺陷。
同意@JoeDaley,我认为 C# 没有这个限制这一事实足以表明这个问题可以用不那么笨拙的方式解决。
仅供参考,当您似乎需要在调用 super 之前执行逻辑时,您最好使用组合而不是继承。
A
Aleksandr Dubinsky

我通过链接构造函数和静态方法找到了解决这个问题的方法。我想做的看起来像这样:

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 需要很多参数或者你需要同时设置其他字段,创建一个静态内部类来保存所有变量,并使用它来将数据从静态方法传递给单参数构造函数。
仅供参考,当您似乎需要在调用 super 之前执行逻辑时,您最好使用组合而不是继承。
我花了一点时间才明白你的概念。所以基本上你创建静态方法并将其放入构造函数中。
@AleksandrDubinsky 您能否详细说明(提供示例代码)展示如何使用静态内部类同时设置多个超参数?也许在您可以链接到的另一篇文章中对此进行了更详细的讨论?
+1,这解决了 Java 的限制造成的问题。但它没有回答 OP 的问题,这就是为什么 Java 编译器有这些限制?
T
Tom Hawtin - tackline

因为 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 级别解决了这些问题,以至于考虑解除这个限制变得切实可行,不仅是为了记录,而是为了所有构造函数。


只是为了澄清:您在示例中使用的 fn() 应该是静态方法,对吗?
+1 提到这纯粹是 JLS 限制。在字节码级别,您可以在调用构造函数之前执行其他操作。
等等,这怎么会使语言规范复杂化?当规范说第一个语句可能是构造函数时,所有其他语句都不能是构造函数。当您删除限制时,规范将类似于“您内部只有语句”。这怎么更复杂?
@Uko 当您将其与相关的 JVM 规范部分进行比较时,您会得到答案。正如 Antimony 所说,这种限制在字节码层面是不存在的,但是当然,在调用超级构造函数之前调用超级构造函数并且不使用正在构造的对象的要求仍然存在。因此,正确代码的定义以及如何验证其正确性充满了整个页面。在 JLS 中提供相同的自由度需要类似的复杂性,因为 JLS 不允许在字节码级别上非法的事情。
对于“为什么 X 是这样的?”这个问题,我总是找到答案“因为它是这样指定的”。有点不满意。通常当人们问为什么 X 是现在这样时,他们实际上是在问如何决定让 X 变成现在这样。
M
MC Emperor

仅仅因为这是继承哲学。根据 Java 语言规范,构造函数的主体是这样定义的:

ConstructorBody: { ExplicitConstructorInvocation BlockStatementsopt }

构造函数主体的第一条语句可以是

显式调用同一类的另一个构造函数(使用关键字“this”);或者

直接超类的显式调用(通过使用关键字“super”)

如果构造函数体不是以显式构造函数调用开始,并且被声明的构造函数不是原始类 Object 的一部分,则构造函数体隐式以超类构造函数调用“super();”开始,调用它的直接超类不带参数。依此类推.. 将有一个完整的构造函数链一直调用到 Object 的构造函数; “Java 平台中的所有类都是对象的后代”。这个东西被称为“构造函数链接”。

为什么会这样?而Java之所以以这种方式定义ConstructorBody,是因为它们需要维护对象的层次结构。记住继承的定义;它正在扩展一个类。话虽如此,你不能扩展不存在的东西。需要先创建基类(超类),然后才能派生它(子类)。这就是为什么他们称它们为 Parent 和 Child 类;没有父母就不能生孩子。

在技术层面上,子类从其父类继承所有成员(字段、方法、嵌套类)。而且由于构造函数不是成员(它们不属于对象。它们负责创建对象),因此它们不会被子类继承,但可以调用它们。而且由于在创建对象时只执行了一个构造函数。那么我们如何在创建子类对象的时候保证超类的创建呢?因此,“构造函数链接”的概念;所以我们有能力从当前构造函数中调用其他构造函数(即super)。 Java 要求此调用是子类构造函数中的第一行,以维护和保证层次结构。他们假设如果您不首先显式创建父对象(就像您忘记了它一样),他们会为您隐式创建。

此检查在编译期间完成。但是我不确定运行时会发生什么,我们会得到什么样的运行时错误,如果 Java 在我们显式尝试从其中间的子类的构造函数中执行基本构造函数时没有抛出编译错误身体而不是从第一行开始......


我知道构造函数不会作为函数调用处理,但我认为将每个超级构造函数调用解释为 this = [new object] 并要求在将 this 用作构造函数返回之前定义它在语义上就足够了达到既定目标。无法将父构造函数调用包装在 try-catch-rethrowtry/finally 块中,使得子类构造函数不可能承诺不抛出超类构造函数可能抛出的东西,即使子类能够保证......
...该异常不会发生。它还大大增加了安全链接需要获取资源并将其传递给父构造函数的构造函数的难度(子构造函数需要由为资源创建容器的工厂方法调用,在 try 块中调用构造函数,如果构造函数失败,则丢弃容器中的任何资源。
从技术上讲,它不是第一行,而是构造函数中的第一个可执行语句。在显式构造函数调用之前有注释是完全合法的。
J
Jason S

我相当确定(那些熟悉 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
    }        
}

Bad1Bad2 应该在那里扩展 Thing 吗?
我不同意 Bad2,因为 xThing 中声明,并且不能在其他任何地方设置。至于 Bad1,您肯定是对的,但是当超级构造函数调用在子类中重写的方法访问子类的(尚未初始化的)变量时,可能会发生类似的事情。因此,限制有助于防止问题的一部分……恕我直言,这是不值得的。
@maaartinus 不同之处在于,超类构造函数的作者负责调用可覆盖的方法。因此,可以以始终具有一致状态的方式设计超类,如果在调用超类构造函数之前允许子类使用对象,这是不可能的。
K
Kate Gregory

您问为什么,而其他答案,imo,并没有真正说明为什么可以调用您的超级构造函数,但前提是它是第一行。原因是您并没有真正调用构造函数。在 C++ 中,等价的语法是

MySubClass: MyClass {

public:

 MySubClass(int a, int b): MyClass(a+b)
 {
 }

};

当您像这样在左大括号之前看到初始化器子句时,您就知道它很特别。它在任何其他构造函数运行之前运行,实际上在任何成员变量被初始化之前运行。 Java 并没有什么不同。有一种方法可以让一些代码(其他构造函数)在构造函数真正开始之前运行,在子类的任何成员被初始化之前。这种方式就是将“调用”(例如super)放在第一行。 (在某种程度上,superthis 在第一个左大括号之前,即使您在之后键入它,因为它将在您到达所有内容完全构造之前执行。)任何其他代码在左大括号(如 int c = a + b;)之后让编译器说“哦,好的,没有其他构造函数,我们可以初始化所有内容。”所以它运行并初始化你的超类和你的成员等等,然后在左大括号之后开始执行代码。

如果在几行之后,它遇到一些代码说“哦,是的,当你构造这个对象时,这是我希望你传递给基类的构造函数的参数”,那就太晚了,它不会有任何意义。所以你得到一个编译器错误。


1. 如果 Java 设计者想要隐式超构造函数,他们可以这样做,更重要的是,这并不能解释为什么隐式超构造函数非常有用。 2. IMO,您的评论没有任何意义,没有任何意义。我记得我需要那个。你能证明我做了一些毫无意义的事情吗?
想象你需要进入一个房间。门是锁着的,所以你打碎了一扇窗户,伸手进去,让自己进去。在里面,穿过房间的一半,你找到一张带钥匙的便条,供你在进去的时候使用。但你已经进去了。同样,如果编译器在执行构造函数的过程中遇到了“这是在运行构造函数之前如何处理这些参数”它应该做什么?
如果它在现实中是愚蠢的,那么这是一个错误的类比。如果我能够决定走哪条路,我不会半途而废。规则是 supercall 必须是构造函数中的第一个,它促使我们打破窗口(在问题和答案中看到很多 warkingaround 的例子)而不是使用门。所以,当你试图为这条规则争论时,你把一切都颠倒了。因此,规则一定是错误的。
-1 这并不能反映代码在 Java 中的实际编译方式、对代码的约束,或者以这种方式设计 Java 的实际原因。
T
Tip-Sy

因此,它不会阻止您在调用 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;
    }
}

这仅在超类构造函数需要单个非 void 参数时才有效
语言设计者可以选择在调用 super()/this() 之前不强制执行任何实例方法或继承方法调用,而不是选择将 super()/this() 作为第一个语句。所以也许 OP 正在询问为什么不这样做。
D
DaveFar

我完全同意,限制太强了。使用静态辅助方法(如 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 语句但允许参数中的 ? 运算符),即使这会使语言规范复杂化。


我认为投反对票是因为您没有回答问题,而是对问题发表评论。在论坛中可以,但 SO/SE 不是一个:)
?: 构造的类型如何让您感到惊讶的绝佳示例。我在阅读时在想,“这不是不可能 --- 只需使用三元操作......哦。”。
V
Vouze

我找到了一个解决方法。

这不会编译:

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);
    }
}

问题不在于解决方法。事实上,可以在问题本身中找到解决方法。
这不是解决方法。您仍然无法编写多行代码。
S
Sai Kishore

我的猜测是,他们这样做是为了让编写处理 Java 代码的工具的人的生活更轻松,在某种程度上也让阅读 Java 代码的人更轻松。

如果您允许 super()this() 调用移动,则需要检查更多变体。例如,如果您将 super()this() 调用移到条件 if() 中,它可能必须足够聪明才能将隐式 super() 插入 else。如果您调用 super() 两次,或者同时使用 super()this(),它可能需要知道如何报告错误。它可能需要在调用 super()this() 之前禁止接收器上的方法调用,并确定何时变得复杂。

让每个人都做这些额外的工作似乎是成本大于收益。


为该功能编写一个健全的语法本身就非常困难 - 这样的语法将匹配一个语句树,其中最多一个叶节点是显式的超级构造函数调用。我可以想出一种写法,但我的方法会很疯狂。
J
John McClane

你能给出一个代码示例,如果编译器没有这个限制,会发生一些不好的事情吗?

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,则 essential1essential2 都没有正确初始化。

你可能会说忽略异常总是一个坏主意。好的,这是另一个例子:

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() 之前有注释之外的任何构造函数上简单地返回错误要好。


这已经很晚了,但您也可以使用工厂模式。将构造函数设为私有。制作与构造函数关联的静态方法。让我们调用类 Foo、2 个构造函数 Foo() 和 Foo(int i),以及构造它的静态方法 createFoo() 和 createFoo(int i)。然后用 Foo.createFoo() 替换 this()。因此,你可以在 createFoo(int i) 中做一些事情,最后做 Foo.createFoo。或任何其他命令。它有点像工厂设计模式,但不是。
S
Savvas Dalkitsis

您可以在调用其构造函数之前使用匿名初始化程序块来初始化子项中的字段。这个例子将演示:

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");
    }
}

这将输出:

在父项中 在初始化程序中 在子项中


但这只是在“super()”之后添加 System.out.println("In initializer") 作为第一行,不是吗?有用的是一种在构造 parent 之前 执行代码的方法。
的确。如果您要尝试添加某些内容,则需要将计算的状态保存在某处。即使编译器允许你这样做,临时存储会是什么?为初始化再分配一个字段?但这是对内存的浪费。
这是不正确的。在父构造函数调用返回之后插入实例初始值设定项。
J
Jaydev

构造函数按派生顺序完成执行是有道理的。因为超类不知道任何子类,所以它需要执行的任何初始化都与子类执行的任何初始化是分开的,并且可能是其先决条件。因此,它必须首先完成其执行。

一个简单的演示:

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

在这个例子中,每个类都有默认构造函数,所以不需要紧急调用子类中的 super(...,...) 方法
m
mid

我知道我参加聚会有点晚了,但我已经使用过几次这个技巧(而且我知道这有点不寻常):

我用一种方法创建了一个通用接口 InfoRunnable<T>

public T run(Object... args);

如果我需要在将它传递给构造函数之前做一些事情,我就这样做:

super(new InfoRunnable<ThingToPass>() {
    public ThingToPass run(Object... args) {
        /* do your things here */
    }
}.run(/* args here */));

S
Sai Kishore

实际上,super() 是构造函数的第一条语句,因为要确保在构造子类之前它的超类是完整的。即使您的第一条语句中没有 super(),编译器也会为您添加它!


F
Farrukh Ahmed

那是因为您的构造函数依赖于其他构造函数。要使您的构造函数正常工作,其他构造函数必须正常工作,这是依赖的。这就是为什么有必要首先检查在构造函数中由 this() 或 super() 调用的依赖构造函数。如果由 this() 或 super() 调用的其他构造函数有问题,那么点执行其他语句,因为如果调用的构造函数失败,所有语句都会失败。


R
Raslanove

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());
    }
}

调用静态函数应该执行得更好,但如果我坚持将代码“放在”构造函数中,或者如果我必须更改多个参数并发现定义许多静态方法不利于可读性,我会使用它。


C
Community

语言:

其他答案已经解决了问题的“为什么”。我将提供一个解决此限制的技巧:

基本思想是劫持 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);
“父类必须有一个至少接受一个参数的构造函数”是不正确的——只需在你自己的类中创建另一个接受参数的构造函数。
O
Oleksandr

在构造子对象之前,必须创建父对象。如您所知,当您编写这样的课程时:

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);
        }
}

j
jww
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 将无法获得正确的值。


A
Adarsh Verma

在子类构造函数中添加 super() 的主要目的是编译器的主要工作是将所有类与 Object 类直接或间接连接,这就是编译器检查我们是否提供了 super (参数化)然后编译器不承担任何责任。以便所有实例成员都从 Object 初始化到子类。


Y
Yan Chun Tang

这是官方重播:从历史上看,this() 或 super() 必须在构造函数中排在第一位。这种限制从未流行过,并且被认为是任意的。有许多微妙的原因,包括对 invokespecial 的验证,导致了这种限制。多年来,我们已经在 VM 级别解决了这些问题,以至于考虑解除这个限制变得切实可行,不仅是为了记录,而是为了所有构造函数。