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