8

我最近在阅读一些 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];
    }

  }
}

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

4

10 回答 10

10

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

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

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

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

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

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

于 2008-12-31T08:19:33.580 回答
5

对象是可序列化的吗?

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

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

于 2008-12-31T08:39:26.660 回答
3

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

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

于 2008-12-31T08:20:27.010 回答
2

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

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

于 2008-12-31T08:29:15.357 回答
0

默认构造函数不是功能视图的好习惯。如果对象对方法具有全局可见性,则使用默认构造函数:例如,您希望在 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 对象是有害的。你可以阅读这篇文章

于 2008-12-31T10:47:37.447 回答
0

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

于 2008-12-31T10:55:35.903 回答
0

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

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

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

于 2008-12-31T13:51:10.557 回答
0

拥有一个默认且空(空白)的构​​造函数会阻止您拥有任何最终字段。这会导致很多可变性,而这些可变性通常是不需要的。

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

于 2009-06-23T16:30:04.400 回答
0

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

于 2016-10-07T01:26:40.493 回答
0

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

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

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

这是我的第一篇文章,但想从我的理解中有所收获。

于 2018-03-08T04:07:41.047 回答