Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
146 views
in Technique[技术] by (71.8m points)

syntax - Why must delegation to a different constructor happen first in a Java constructor?

In a constructor in Java, if you want to call another constructor (or a super constructor), it has to be the first line in the constructor. I assume this is because you shouldn't be allowed to modify any instance variables before the other constructor runs. But why can't you have statements before the constructor delegation, in order to compute the complex value to the other function? I can't think of any good reason, and I have hit some real cases where I have written some ugly code to get around this limitation.

So I'm just wondering:

  1. Is there a good reason for this limitation?
  2. Are there any plans to allow this in future Java releases? (Or has Sun definitively said this is not going to happen?)

For an example of what I'm talking about, consider some code I wrote which I gave in this StackOverflow answer. In that code, I have a BigFraction class, which has a BigInteger numerator and a BigInteger denominator. The "canonical" constructor is the BigFraction(BigInteger numerator, BigInteger denominator) form. For all the other constructors, I just convert the input parameters to BigIntegers, and call the "canonical" constructor, because I don't want to duplicate all the work.

In some cases this is easy; for example, the constructor that takes two longs is trivial:

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

But in other cases, it is more difficult. Consider the constructor which takes a 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()));
  }

I find this pretty ugly, but it helps me avoid duplicating code. The following is what I'd like to do, but it is illegal in 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);
  }

Update

There have been good answers, but thus far, no answers have been provided that I'm completely satisfied with, but I don't care enough to start a bounty, so I'm answering my own question (mainly to get rid of that annoying "have you considered marking an accepted answer" message).

Workarounds that have been suggested are:

  1. Static factory.
    • I've used the class in a lot of places, so that code would break if I suddenly got rid of the public constructors and went with valueOf() functions.
    • It feels like a workaround to a limitation. I wouldn't get any other benefits of a factory because this cannot be subclassed and because common values are not being cached/interned.
  2. Private static "constructor helper" methods.
    • This leads to lots of code bloat.
    • The code gets ugly because in some cases I really need to compute both numerator and denominator at the same time, and I can't return multiple values unless I return a BigInteger[] or some kind of private inner class.

The main argument against this functionality is that the compiler would have to check that you didn't use any instance variables or methods before calling the superconstructor, because the object would be in an invalid state. I agree, but I think this would be an easier check than the one which makes sure all final instance variables are always initialized in every constructor, no matter what path through the code is taken. The other argument is that you simply can't execute code beforehand, but this is clearly false because the code to compute the parameters to the superconstructor is getting executed somewhere, so it must be allowed at a bytecode level.

Now, what I'd like to see, is some good reason why the compiler couldn't let me take this code:

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

And rewrite it like this (the bytecode would be basically identical, I'd think):

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

The only real difference I see between those two examples is that the "tmp" variable's scope allows it to be accessed after calling this(tmp) in the second example. So maybe a special syntax (similar to static{} blocks for class initialization) would need to be introduced:

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;
}
question from:https://stackoverflow.com/questions/508639/why-must-delegation-to-a-different-constructor-happen-first-in-a-java-constructo

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I find this pretty ugly, but it helps me avoid duplicating code. The following is what I'd like to do, but it is illegal in Java ...

You could also work around this limitation by using a static factory method that returns a new object:

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

    return new BigFraction(numerator, denominator);
}

Alternatively, you could cheat by calling a private static method to do the computations for your constructor:

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

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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...