Направление цепочки конструкторов Java

Я понимаю, что есть специальные классы, для которых этот общий вопрос не применяется, но для простых, когда у нас есть несколько конструкторов, а параметры одного являются чистым подмножеством другого, лучше ли вызвать конструктор с более длинным списком из одного с более коротким списком или наоборот? Почему?

public class A {
    int x;
    int y;
    int z;

    public A() {
        this(0);
    }
    public A(int x) {
        this (x, 0);
    }
    public A(int x, int y) {
        this(x, y, 0);
    }

    public A(int x, int y, int z) {
        this.x = x;
        this.y = y;
        this.z = z;
        // some setup stuff needed for all A
    }
}

или

public class A {
    int x;
    int y;
    int z;

    public A(int x, int y, int z) {
        this(x, y);
        this.z = z;
    }

    public A(int x, int y) {
        this(x);
        this.y = y;
    }

    public A(int x) {
        this();
        this.x = x;
    }

    public A() {
        // some setup stuff needed for all A
    }
}

3 ответов


взгляните на второй вариант:

public A(int x, int y, int z) {
    this(x, y);
    this.z = z;
}

public A(int x, int y) {
    this(x);
    this.y = y;
}

public A(int x) {
    this();
    this.x = x;
}

public A() {
    // some setup stuff needed for all A
}

обратите внимание, что этот "материал, необходимый для всех A", невозможно настроить, если для этого требуются фактические значения x, y, z. Единственный способ исправить это-позволить конструктору по умолчанию выполнять эту работу, используя значения по умолчанию x, y, z, а затем перезаписать его результаты в вызывающем конструкторе, используя указанные значения не по умолчанию. Это не-идти, если эти настройки работы имеют заметные побочные эффекты, но даже без побочных эффектов это также может отрицательно сказаться на производительности, учитывая худший случай, что A(int x, int y, int z) конструктор выполняет эту работу в четыре раза.

кроме того, есть (по крайней мере) три сценария, где ваш второй вариант не работает, даже без установки работает:

  1. как уже объясняется Codebender, списки параметров не обязательно должны быть подмножеством каждого другой.

    public A(TypeA a) {
        this(a, DEFAULT_B);
    }
    public A(TypeB b) {
        this (DEFAULT_A, b);
    }
    public A(TypeA a, TypeB b) {
        …
    }
    
  2. поля могут быть final. Затем последний конструктор в цепочке, который не вызывает другой конструктор этого класса, должен инициализировать all final поля, в то время как делегирующие конструкторы не могут писать в эти final поля вообще.

    public class A {
        final int x, y, z;
    
        public A() {
            this(0);
        }
        public A(int x) {
            this (x, 0);
        }
        public A(int x, int y) {
            this(x, y, 0);
        }
    
        public A(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
            // optionally, some setup stuff needed for all A
        }
    }
    
  3. некоторые из полей определены в суперклассе и должны быть инициализированы с помощью конструктора. Аналогично


в конце концов, нет большой разницы. Я считаю, что большинство людей цепляются, я могу дать три причины, я думаю.

один причина, которую я бы дал, просто потому, что функциональность или логика для класса полностью в одной области. И это конструктор с полными переменными.

представьте себе:

this.x = 2*x;

или

if(x>20 && y>20)....

самая большая проблема заключается в том, что конструкторы ниже не будут иметь доступа к "z", так как это только инициализируется после цепочки.

также много раз мы не просто сохраняем переменные, мы устанавливаем вещи на основе переменных. Таким образом, вся логика кода для установки находится в одном месте, где переменные также настроены.

было бы странно иметь весь этот код в конструкторе, где это не аргументы. Можно ли это сделать? Да! Но немного странно...

вторая причина это когда типы переменных разные, вы можете подключиться, но не обязательно на цепь.

public example(String aString){
  this(aString,0);
}
public example(int aNumber){
  this(null, aNumber);
}
public example(String aString, int aNumber){
  ...
}

если цепочки вниз:

public example(String aString, int aNumber){
  this(aString);
  this.aNumber = aNumber;
  /**
  You can only call one constructor lower, and you lose "aNumber"
  if you initialize here then your doubling that code and changing
  it means changing it twice. 

  Of course this is ONLY true, if you want to also have the constructor
  for just aNumber, if not there is no issue and it is like your example
  **/
}
public example(String aString){
  this(0);
  this.aString = aString;
}
public example(int aNumber){
  this();
  this.aNumber = aNumber;
  //only an issue if you want both this constructor and "aString" constructor
}

третья причина Я могу думать о том, что его также можно ожидать, как с точки зрения кода, так и с точки зрения ошибок... Люди будут путать с удобочитаемостью, так как они к ней не привыкли.

аналогично, представьте, что вы получаете ошибку от простого класса конструктора, это немного странно, почему была вызвана более простая версия? Вы предполагаете, что он имеет ту же функциональность, что и все конструкторы приводят к простому конструктору или некоторые из них ведут себя по-разному? По какой-то причине я ожидал, что все конструкторы приведут к более сложной версии, но я не уверен, что предположу обратное.

главное я бы нервничал, что простой конструктор вызывается, потому что некоторые аргументы даже не рассматриваются, и, возможно, этот конструктор с аргументами-это просто заглушка, которая будет разработана позже.

это всегда проблемы справедливости ради, но я думаю, что это может быть предупреждающим знаком, если вместо этого в трассировке стека был вызван более простой конструктор, по крайней мере для меня.


переход от подмножества к суперсету был бы намного аккуратнее, на мой взгляд.

таким образом, мы можем иметь всю логику в одном месте. И если логика нуждается в изменении, ее было бы намного легче изменить.

кроме того, большую часть времени, поступающего от большего конструктора к подмножеству, невозможно.

давайте возьмем пример

class Student {
    int id;
    String name;

    public Student(int id) {
        this(id, null);
    }

    public Student(String name) {
        this(0, name);
    }

    public Student(int id, String name) {
        this.id = id;
        this.name = name;
    }

теперь было бы невозможно написать это по-другому.

вы можете перейти от суперсета к подмножеству, только если каждый конструктор является надмножеством другого конструктора, кроме одного. и это может затруднить добавление нового конструктора в будущем.