【问题标题】:Why might one also use a blank constructor?为什么还可以使用空白构造函数?
【发布时间】:2008-12-31 08:10:03
【问题描述】:

我最近在阅读一些 Java 并遇到了一些新的东西(一个成语?):在程序中,具有多个构造函数的类也总是包含一个空白构造函数。例如:

public class Genotype {
  private boolean bits[];
  private int rating;
  private int length;
  private Random random;

  public Genotype() {              //  <= THIS is the bandit, this one right here
    random = new Random();
  }

  /* creates a Random genetoype */
  public Genotype(int length, Random r) {
    random = r;
    this.length = length;
    bits = new boolean[length];

    for(int i=0;i<length;i++) {
        bits[i] =random.nextBoolean();
    }
  }

  /* copy constructor */
  public Genotype(Genotype g,Random r) {
    random = r;
    bits = new boolean[g.length];
    rating = g.rating;
    length = g.length;

    for(int i=0;i<length;i++) {
        bits[i] = g.bits[i];
    }

  }
}

第一个构造函数似乎不是“真正的”构造函数,似乎在每种情况下都会使用其他构造函数中的一个。那么为什么要定义构造函数呢?

【问题讨论】:

    标签: java coding-style constructor


    【解决方案1】:

    我不确定您阅读的代码是否高质量(我过去曾查看过一些生物信息学代码,遗憾的是这些代码通常不是由专业开发人员编写的)。例如,第三个构造函数不是复制构造函数,通常这段代码存在问题,所以我不会“读太多”。

    第一个构造函数是默认构造函数。它只初始化最低限度,并让用户使用 getter 和 setter 设置其余部分。其他构造函数通常是“便利构造函数”,可以帮助创建具有较少调用的对象。但是,这通常会导致构造函数之间的不一致。事实上,最近的研究表明,带有后续调用 setter 的默认构造函数更可取。

    在某些情况下,默认构造函数也很重要。例如,某些框架,如 digester(用于直接从 XML 创建对象)使用默认构造函数。 JavaBeans 一般使用默认构造函数等。

    另外,一些类继承自其他类。当父对象的初始化“足够好”时,您可能会看到默认构造函数。

    在这种特定情况下,如果没有定义该构造函数,则必须提前了解所有细节。这并不总是可取的。

    最后,一些 IDE 会自动生成默认构造函数,编写该类的人可能害怕将其删除。

    【讨论】:

    • 研究表明了什么?我通常只根据需要编写尽可能多的构造函数(这往往是一个),但我在 stackoverflow 上看到了一些代码,类似于 CoffeeCup mug = new cupBuilder().setShape(fat).setPractical(true).setHandle(true).build()这就是你在说的吗?
    • 是的,只是你不会在一次调用中将它们链接起来,而是进行多次调用。
    • 这是一篇关于它的学术论文:Jeffrey Stylos 和 Steven Clarke。 “对象构造函数中要求参数的可用性影响”。国际软件工程会议(ICSE 2007)。 cs.cmu.edu/~NatProg/papers/Stylos2007CreateSetCall.pdf
    • 论文+1。我遇到的一个问题是它没有考虑每种情况下运行时缺陷花费了多少时间和痛苦。有时编译器/语言会以我不喜欢的方式限制我并减慢我的速度,但与在实时运行时场景中跟踪问题相比,回报是巨大的。因此,如果存在类必须操作的信息,并且在没有该信息的情况下尝试使用它会导致奇怪的行为,那么构造函数中的参数对我来说似乎是有意义的。
    • 也就是说,快速愉快地编码并不总是等于更好的代码。
    【解决方案2】:

    对象是Serializable吗?

    为了允许对不可序列化类的子类型进行序列化,子类型可能会负责保存和恢复超类型的公共、受保护和(如果可访问)包字段的状态。只有当它扩展的类具有可访问的无参数构造函数来初始化类的状态时,子类型才可以承担此责任。如果不是这种情况,则声明一个类 Serializable 是错误的。将在运行时检测到错误。

    在反序列化期间,不可序列化类的字段将使用类的公共或受保护的无参数构造函数进行初始化。可序列化的子类必须可以访问无参数构造函数。可序列化子类的字段将从流中恢复

    【讨论】:

    • 只有最派生的不可序列化的超类必须有一个无参数的构造函数。认为可序列化的类需要无参数构造函数是一个常见的错误。
    【解决方案3】:

    是的,我同意“空白”构造函数不应该总是存在(根据我的经验,初学者经常犯这个错误),尽管在某些情况下空白构造函数就足够了。但是,如果空白构造函数违反了所有成员在构造后正确实例化的不变量,则不应使用空白构造函数。如果构造函数比较复杂,最好将构造分成几个protected/private方法。然后根据需要使用 static 方法或另一个 Factory 类调用受保护的方法进行构造。

    我上面写的是理想的场景。但是,像 spring 这样的框架将构造函数逻辑从代码中移除并放入一些 xml 配置文件中。您可能有 getter 和 setter 函数,但可能会从界面中避免使用,as described here

    【讨论】:

      【解决方案4】:

      默认构造函数不是强制性的。

      如果类中没有定义构造函数,那么默认(空)构造函数将被自动创建。如果您提供了任何参数化构造函数,则默认构造函数将不会自动创建,最好自己创建。在运行时使用依赖注入和动态代理创建的框架通常需要默认构造函数。因此,这取决于您编写的类的用例。

      【讨论】:

        【解决方案5】:

        默认构造函数对于功能视图来说不是一个好习惯。 如果对象对方法具有全局可见性,则使用默认构造函数:例如,您希望在 try/catch 中记录对象的实际状态 你可以编码

        MyObejct myObject=null
        try{...
        }catch(Exception e){
            log.error(myObject);//maybe print null. information?
        }
        

        或者你更喜欢

        MyObejct myObject=new Object();
        try{...
        }catch(Exception e){
        log.error(myObject);//sure print  myobject.toString, never null. More information
        }
        

        ?

        另一方面,创建一个 EMPTY 对象并没有太多逻辑,但在我看来,将一个 NULL 对象设置为有害的。 你可以阅读this post

        【讨论】:

          【解决方案6】:

          这不是复制构造函数。基本上,在使用某些框架时,您需要空的构造函数。总是有一个空的构造函数,当然,公共的或私有的,但至少它允许你控制类是如何被实例化(或不被实例化)的。

          【讨论】:

            【解决方案7】:

            我通常编写一个完全初始化对象的构造函数;如果还有其他人,他们都用适当的默认值调用 this(...)。

            对象应在创建时 100% 初始化并准备好使用。

            一些框架,例如 Hibernate,需要一个无参数的构造函数。它们与最佳实践相冲突的方式有时让我感到不安。

            【讨论】:

              【解决方案8】:

              拥有一个默认的空(空白)构造函数会阻止您拥有任何最终字段。这会导致在通常不需要的地方出现很多可变性。

              构建器模式允许您混合这两种样式并允许更灵活的初始化,同时通过在工厂后面隐藏一个多参数构造函数来保持不变性。

              【讨论】:

                【解决方案9】:

                对于某些 POJO 类或简单类,当您有时想对使用它们的类进行单元测试时,默认构造函数很有用。您不需要模拟它们,您可以使用默认构造函数新建一个对象并测试值集并从中获取或将它们作为参数传递。

                【讨论】:

                  【解决方案10】:

                  您想为扩展它的类创建一个空白构造函数 类,因为它已经扩展了类......孩子现在有 super 引用它上面的类,它是父类。如果孩子没有指定 super(stuff)... 里面的东西来引用其他构造函数来使用它,现在将尝试引用空构造函数。

                  我不确定会出现什么错误我现在正在编写我的第一个父对象关系并且正在这里查找东西哈欢呼。

                  我想我应该添加一个不是空的构造函数的那一刻,你失去了默认的空构造函数,所以现在扩展类中默认的 super() 将没有指向的东西。当然,如果您创建扩展类来处理 super 通过指定哪些摆脱默认 super() 那么您会回避这一点,但是如果有人想使用您的类并从它扩展而没有意识到没有' t 一个空集,当你可以拥有时 刚刚创建了一个。

                  这是我的第一篇文章,但想了解一下我的理解。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-04-07
                    • 2017-06-18
                    • 2018-01-29
                    • 2013-03-20
                    • 2012-10-03
                    • 2012-05-01
                    • 2019-12-21
                    相关资源
                    最近更新 更多