Java构造函数链方向

Java constructor chain direction

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

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

归根结底,差别不大。我发现大多数人都上链,我可以给出三个我猜的原因。

一个 我给出的理由很简单,因为 class 的功能或逻辑完全在一个领域。这就是具有完整变量的构造函数。

想象一下:

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 中得到一个错误,这有点奇怪,为什么要调用一个更简单的版本?您是否认为它具有相同的功能,所有构造函数都导致简单构造函数,还是其中一些行为不同?出于某种原因,我希望所有构造函数都会导致更复杂的版本,但我不确定我是否会假设相反。

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

为了公平起见,这些总是令人担忧,但我认为如果在堆栈跟踪中调用更简单的构造函数而不是调用它可能是一个警告信号,至少对我而言。

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

这样一来,我们就可以将所有逻辑集中在一个地方。如果一个逻辑需要改变,修改起来会容易得多。

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

举个例子

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

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

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

看看第二个变体:

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) 构造函数执行该工作四次。

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

  1. 一样,参数列表不需要是彼此的子集。

    public A(TypeA a) {
        this(a, DEFAULT_B);
    }
    public A(TypeB b) {
        this (DEFAULT_A, b);
    }
    public A(TypeA a, TypeB b) {
        …
    }
    
  2. 字段可能是 final。然后,链中的最后一个构造函数(不调用此 class 的另一个构造函数)必须初始化所有 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. 部分字段定义在superclass中,需要通过构造函数进行初始化。类似于 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
        }
    }
    

由于很多情况下第二种变体不起作用,我也不会在工作场景中使用它,因为只要是一个风格问题,你应该争取一致性.