当前位置: 首页 > 面试题库 >

为什么在Java构造函数中必须首先委派给其他构造函数?

郗唯
2023-03-14
问题内容

在Java的构造函数中,如果要调用另一个构造函数(或超级构造函数),则它必须是该构造函数的第一行。我认为这是因为不允许您在其他构造函数运行之前修改任何实例变量。但是,为什么不能在构造函数委派之前使用语句,以便计算另一个函数的复杂值?我想不出任何正当的理由,在一些实际案例中,我编写了一些丑陋的代码来解决此限制。

所以我只是想知道:

  1. 有此限制的充分理由吗?
  2. 有没有计划在将来的Java版本中允许这样做?(或者Sun明确表示这不会发生?)

关于我正在谈论的示例,请考虑我在答案中给出的一些代码。在该代码中,我有一个BigFraction类,该类具有BigInteger分子和BigInteger分母。“规范”构造函数是BigFraction(BigInteger numerator, BigIntegerdenominator)表单。对于所有其他构造函数,我只是将输入参数转换为BigIntegers,然后调用“规范”构造函数,因为我不想重复所有工作。

在某些情况下,这很容易。例如,采用两个longs 的构造函数是微不足道的:

  public BigFraction(long numerator, long denominator)
  {
    this(BigInteger.valueOf(numerator), BigInteger.valueOf(denominator));
  }

但是在其他情况下,则更加困难。考虑采用BigDecimal的构造函数:

  public BigFraction(BigDecimal d)
  {
    this(d.scale() < 0 ? d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale())) : d.unscaledValue(),
         d.scale() < 0 ? BigInteger.ONE                                             : BigInteger.TEN.pow(d.scale()));
  }

我觉得这很丑陋,但这可以帮助我避免重复代码。以下是我想做的事情,但是在Java中是非法的:

  public BigFraction(BigDecimal d)
  {
    BigInteger numerator = null;
    BigInteger denominator = null;
    if(d.scale() < 0)
    {
      numerator = d.unscaledValue().multiply(BigInteger.TEN.pow(-d.scale()));
      denominator = BigInteger.ONE;
    }
    else
    {
      numerator = d.unscaledValue();
      denominator = BigInteger.TEN.pow(d.scale());
    }
    this(numerator, denominator);
  }

更新资料

答案不错,但到目前为止,还没有提供我完全满意的答案,但我不太在意悬赏,所以我在回答自己的问题(主要是为了摆脱那个问题烦人的“您是否考虑过标记接受的答案”消息)。

建议的解决方法是:

  1. 静态工厂。
    • 我在很多地方都使用过该类,因此,如果我突然摆脱公共构造函数并使用valueOf()函数,则代码将被破坏。
    • 感觉是一种限制的解决方法。我不会获得工厂的任何其他好处,因为它不能被子类化,并且因为通用值没有被缓存/内插。
  2. 私有静态“构造函数助手”方法。
    • 这导致大量代码膨胀。
    • 代码变得丑陋,因为在某些情况下,我确实需要同时计算分子和分母,除非返回一个BigInteger[]或某种私有内部类,否则我无法返回多个值。

反对此功能的主要观点是,在调用超构造函数之前,编译器将必须检查您是否未使用任何实例变量或方法,因为该对象将处于无效状态。我同意,但是我认为这比确保所有最终实例变量始终在每个构造函数中初始化的检查要容易得多,无论采用哪种路径通过代码。另一个论点是您根本无法预先执行代码,但这显然是错误的,因为用于计算超构造函数参数的代码正在
某处 执行,因此必须在字节码级别上允许它。

现在,我希望看到的是编译器不允许我接受此代码的一些 很好的 理由:

public MyClass(String s) {
  this(Integer.parseInt(s));
}
public MyClass(int i) {
  this.i = i;
}

然后像这样重写它(字节码基本上是相同的,我想):

public MyClass(String s) {
  int tmp = Integer.parseInt(s);
  this(tmp);
}
public MyClass(int i) {
  this.i = i;
}

我看到的两个示例之间的唯一真正区别是,在第二个示例中tmp调用后,“
”变量的范围允许对其进行访问this(tmp)。因此,可能static{}需要引入一种特殊的语法(类似于用于类初始化的块):

public MyClass(String s) {
  //"init{}" is a hypothetical syntax where there is no access to instance
  //variables/methods, and which must end with a call to another constructor
  //(using either "this(...)" or "super(...)")
  init {
    int tmp = Integer.parseInt(s);
    this(tmp);
  }
}
public MyClass(int i) {
  this.i = i;
}

问题答案:

我觉得这很丑陋,但这可以帮助我避免重复代码。以下是我想做的事情,但是在Java中是非法的…

您还可以通过使用返回新对象的静态工厂方法来解决此限制:

public static BigFraction valueOf(BigDecimal d)
{
    // computate numerator and denominator from d

    return new BigFraction(numerator, denominator);
}

或者,您可以通过调用私有静态方法为构造函数进行计算来作弊:

public BigFraction(BigDecimal d)
{
    this(computeNumerator(d), computeDenominator(d));
}

private static BigInteger computeNumerator(BigDecimal d) { ... }
private static BigInteger computeDenominator(BigDecimal d) { ... }


 类似资料: