В конструкторе в Java, если вы хотите вызвать другой конструктор (или супер-конструктор), он должен быть первой строкой в конструкторе. Я предполагаю, что это связано с тем, что вам не разрешается изменять любые переменные экземпляра до запуска другого конструктора. Но почему у вас нет операторов перед делегацией конструктора, чтобы вычислить сложное значение для другой функции? Я не могу придумать какой-либо веской причины, и я попал в некоторые реальные случаи, когда я написал какой-то уродливый код, чтобы обойти это ограничение.
Так что мне просто интересно:
- Есть ли веская причина для этого ограничения?
- Есть ли планы разрешить это в будущих выпусках Java? (Или Солнце окончательно сказал, что этого не произойдет?)
Для примера того, о чем я говорю, рассмотрим некоторый код, который я написал, который я дал в qaru.site/info/15958/.... В этом коде у меня есть класс BigFraction, у которого есть числитель BigInteger и знаменатель BigInteger. "Канонический" конструктор - это форма BigFraction(BigInteger numerator, BigInteger denominator)
. Для всех других конструкторов я просто конвертирую входные параметры в BigIntegers и вызываю "канонический" конструктор, потому что я не хочу дублировать всю работу.
В некоторых случаях это легко; например, конструктор, который принимает два long
, тривиален:
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);
}
Обновление
Были хорошие ответы, но до сих пор не было получено никаких ответов, что я полностью удовлетворен, но мне все равно, чтобы начать щедрость, поэтому я отвечаю на свой вопрос (в основном, чтобы получить избавиться от этого раздражающего сообщения "вы считали, что сообщение принято" ).
Обходными решениями, которые были предложены, являются:
- Статический factory.
- Я использовал класс во многих местах, так что код сломался бы, если бы я внезапно избавился от публичных конструкторов и пошел с функциями valueOf().
- Похоже на обходное ограничение. Я не получил бы других преимуществ factory, потому что это не может быть подклассом и потому, что общие значения не кэшируются/интернированы.
- Частные статические методы-помощники-конструкторы.
- Это приводит к большому количеству раздувания кода.
- Код становится уродливым, потому что в некоторых случаях мне действительно нужно вычислить как числитель, так и знаменатель одновременно, и я не могу вернуть несколько значений, если не вернусь
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;
}