Java中的构造函数重载 - 最佳实践

117

有些主题与此类似,但我找不到一个有足够答案的。

我想知道Java中构造函数重载的最佳实践是什么。我已经对此有自己的想法,但我想听听更多的建议。

我所指的是简单类中的构造函数重载以及在继承已经重载了的类时的构造函数重载(也就是基类具有重载的构造函数)。

谢谢:)

5个回答

182

虽然没有"官方指南",但我遵循KISS和DRY原则。使过载的构造函数尽可能简单,最简单的方法是只调用this(...)。这样,您只需要一次检查和处理参数。

public class Simple {

    public Simple() {
        this(null);
    }

    public Simple(Resource r) {
        this(r, null);
    }

    public Simple(Resource r1, Resource r2) {
        // Guard statements, initialize resources or throw exceptions if
        // the resources are wrong
        if (r1 == null) {
            r1 = new Resource();
        }
        if (r2 == null) {
            r2 = new Resource();
        }

        // do whatever with resources
    }

}

从单元测试的角度来看,由于您可以将资源放入类中,因此测试该类将变得容易。如果该类具有许多资源(或某些OO极客所称的协作者),请考虑以下两种方法之一:

创建参数类

public class SimpleParams {
    Resource r1;
    Resource r2;
    // Imagine there are setters and getters here but I'm too lazy 
    // to write it out. you can make it the parameter class 
    // "immutable" if you don't have setters and only set the 
    // resources through the SimpleParams constructor
}

Simple 中的构造函数只需要拆分 SimpleParams 参数即可:
public Simple(SimpleParams params) {
    this(params.getR1(), params.getR2());
}

... 或者将 SimpleParams 设为属性:

public Simple(Resource r1, Resource r2) {
    this(new SimpleParams(r1, r2));
}

public Simple(SimpleParams params) {
    this.params = params;
}

创建工厂类

创建一个工厂类来初始化资源,如果初始化资源有一定的难度,这会更加方便:

public interface ResourceFactory {
    public Resource createR1();
    public Resource createR2();
}

构造函数与参数类相同的方式完成:
public Simple(ResourceFactory factory) {
    this(factory.createR1(), factory.createR2());
} 

将两者结合起来

是的...您可以根据当时的情况混合使用两种方式。参数类和简单工厂类基本上是相同的,因为它们都使用相同的Simple类。


我注意到在这个例子以及其他例子中,构造函数的定义是按照构造函数调用中使用的参数从最少到最多的顺序编写的。这是标准的Java风格吗?为什么?在我看来,相反地这样做更有意义,这样你应该首先看到具有所有细节的构造函数定义。 - Josie Thompson
2
@JosieThompson 我所知道的,这并没有在任何标准中定义。我同意将完整构造函数放在首位可以快速查看方法的所有参数;然而,按参数计数对它们进行排序可以让你沿着页面跟踪重载调用,这在考虑我们如何阅读和编写代码时会感觉更自然。 - Matt
在一个类中,是否可以有多个构造函数,每个构造函数只有一个参数但类型不同?例如:new Object(int aa) / new Object(String bb)? - Botea Florin

75

我认为最好的做法是有一个单一主构造函数,重载的构造函数通过调用this()并使用相关参数默认值来引用它。原因在于这使得对象的构造状态更加清晰-实际上,你可以将主构造函数视为唯一真正的构造函数,其他构造函数只是委托给它。

其中一个例子可能是JTable——主构造函数接受一个TableModel(加上列和选择模型),其他构造函数调用这个主构造函数。

对于子类其中父类已经有了重载构造函数,我倾向于假设任何父类的构造函数都可以被视为主要,并且认为没有一个单一的主构造函数是完全合理的。例如,在扩展Exception时,我经常提供3个构造函数:一个仅接受String消息,一个接受Throwable cause,以及另一个同时接受两者。每个构造函数直接调用super


我认为“已经重载的类”是指基类有几个重载构造函数。 - Chii
我已经根据这个澄清修改了我的答案。 - oxbow_lakes
我同意第一部分,但不同意继承已经重载的类的第二部分:假设我要将 Exception 继承到一个新类中,在这个新类中,我希望 Exception 的字符串以“bla”开头 - 这意味着我应该在接收字符串的构造函数中进行验证。如果我没有像基类中那样调用主构造函数,我必须复制此验证代码。 - Eyal Roth
我认为这是不必要的限制。 - Tom Hawtin - tackline
1
@Tom - 有一些场合我会偏离这种做法,但每次这样做时我都会仔细思考,因为我认为没有一个单一的主要构造函数是一个混乱类设计的很好的指标。 - oxbow_lakes

7
如果您有一个非常复杂的类,其中有很多选项,只有某些组合是有效的,请考虑使用Builder模式。从代码和逻辑上来说都非常好。
Builder是一个嵌套类,仅设计用于设置字段的方法,然后ComplexClass构造函数仅接受这样的Builder作为参数。
编辑:ComplexClass构造函数可以确保Builder中的状态是有效的。如果您只在ComplexClass上使用setter,这将非常难以实现。

非常同意使用建造者模式,而不是像 OP 那样使用过载构造函数,因为它违反了最少惊讶原则。 - Martin Spamer

2

这取决于类的种类,因为并非所有的类都是相同的。

作为一般指导原则,我建议有两个选择:

  • 对于值和不可变类(如Exception、Integer、DTO等),使用单个主构造函数,就像上面的答案建议的那样。
  • 对于其他所有类(会话Bean、服务、可变对象、JPA和JAXB实体等),仅使用默认构造函数,并在所有属性上设置合理的默认值,以便可以在不需要额外配置的情况下使用。

0

构造函数重载类似于方法重载。可以通过构造函数的不同重载方式来创建对象。

编译器根据构造函数中存在多少个参数以及参数传递的顺序等其他参数来区分构造函数。

有关Java构造函数的更多详细信息,请访问https://tecloger.com/constructor-in-java/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接