Java构造函数链方向

时间:2015-07-23 16:42:14

标签: java constructor method-chaining

我意识到这个一般性问题并不适用于某些特殊类,但对于简单的类,当我们有多个构造函数,并且一个参数是另一个的干净子集时,最好调用它具有较短列表的列表中较长列表的构造函数,反之亦然?为什么呢?

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 个答案:

答案 0 :(得分: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
}

请注意,如果需要xyz的实际值,则无法设置此“所有A所需的内容”。解决这个问题的唯一方法是让默认构造函数使用默认值xyz执行该工作,然后使用调用构造函数覆盖其结果。指定的非默认值。如果这些设置工作有明显的副作用,这是不可取的,但即使没有副作用,它也可能对性能产生负面影响,考虑到最坏的情况,A(int x, int y, int z)构造函数执行该工作四次。 / p>

除此之外,还有(至少)三种情况,即使没有这样的设置工作,第二种变体也不起作用:

  1. 已经explained by 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。然后,链中的最后一个构造函数(不调用此类的另一个构造函数)必须初始化所有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. 某些字段在超类中定义,需要通过构造函数进行初始化。与final字段初始化类似,只有链中的最后一个构造函数可以调用super构造函数,而其他构造函数不能调用super构造函数,因此只有构造函数知道适当的值可以是最后一个。

    public class A extends B{
        int z;// B has x and y
    
        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) {
            super(x, y);
            this.z = z;
            // optionally, some setup stuff needed for all A
        }
    }
    
  4. 由于有很多情况下第二种变体不起作用,我也不会在工作场景中使用它,因为每当某些东西只是一个风格问题时,你应该努力一致性

答案 1 :(得分:2)

在一天结束时没有太大区别。我发现大多数人都联系在一起,我可以给出三个理由。

我要给出的一个原因仅仅是因为类的功能或逻辑完全在一个区域内。这是具有完整变量的构造函数。

想象:

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
}

我能想到的第三个原因是它也可能是人们所期望的,无论是在代码方面还是在错误方面......人们会对可读性感到困惑,因为他们不是习惯了。

同样,想象一下,你从简单的构造函数类中得到一个错误,它有点奇怪,为什么一个更简单的版本被调用?你是否认为它具有相同的功能所有构造函数导致简单的构造函数或者它们中的一些行为有何不同?出于某种原因,我希望所有构造函数都会导致更复杂的版本,但我不确定我会采取相反的方式。

更重要的是我会紧张的是,正在调用简单的构造函数,因为有些参数甚至没有处理过,也许这个带参数的构造函数只是稍后要开发的存根。

这些总是值得公平,但我认为如果在堆栈跟踪中调用更简单的构造函数,这可能是一个警告信号,至少对我而言。

答案 2 :(得分:1)

在我看来,从子集到超集会更加整洁。

这样,我们可以在一个地方拥有所有逻辑。如果逻辑需要改变,那么修改就会容易得多。

此外,大多数时间从较大的构造函数到子集都是不可能的。

我们举一个例子

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;
    }

现在,用另一种方式写这个是不可能的。

只有当每个构造函数都是另一个构造函数的超集时,才能从超集转到子集。 这可能会使将来很难添加新构造函数