在构造函数或声明中初始化类字段?

我最近一直在用c#和Java编程,我很好奇初始化我的类字段的最佳位置在哪里。

我应该在申报时申报吗?:

public class Dice
{
private int topFace = 1;
private Random myRand = new Random();


public void Roll()
{
// ......
}
}

或者在构造函数中?:

public class Dice
{
private int topFace;
private Random myRand;


public Dice()
{
topFace = 1;
myRand = new Random();
}


public void Roll()
{
// .....
}
}

我很好奇你们这些老兵认为最好的做法是什么。我想保持一致,坚持一种方法。

158114 次浏览

我的规则:

  1. 不要初始化声明中的默认值(nullfalse00.0…)
  2. 如果没有改变字段值的构造函数形参,最好在声明中初始化。
  3. 如果字段的值因构造函数形参而改变,则将初始化放在构造函数中。
  4. 坚持练习(这是最重要的规则)。

假设您的示例中的类型,肯定更喜欢在构造函数中初始化字段。例外情况包括:

  • 静态类/方法中的字段
  • 字段类型为static/final/et al

我总是认为类顶部的字段列表是目录(这里包含什么,而不是如何使用它),构造函数是介绍。方法当然是章节。

如果我告诉你,这要看情况?

我通常初始化所有东西,并以一致的方式进行。是的,它过于明确,但它也更容易维护。

如果我们担心性能,那么我只初始化必须要做的事情,并将其放在性价比最高的地方。

在实时系统中,我甚至怀疑我是否需要变量或常数。

在c++中,我经常在这两个地方都不初始化,而是将它移动到Init()函数中。为什么?好吧,在c++中,如果你初始化的东西可能会在对象构造过程中抛出异常,你就会导致内存泄漏。

我通常尝试构造函数不做任何事情,只是获取依赖项并初始化相关的实例成员。如果您想对类进行单元测试,这将使您的工作更加轻松。

如果要分配给实例变量的值不受要传递给构造函数的任何参数的影响,则在声明时分配它。

在c#中,这并不重要。您给出的两个代码示例完全相同。在第一个例子中,c#编译器(或者是CLR?)将构造一个空构造函数并初始化变量,就像它们在构造函数中一样(Jon Skeet在下面的评论中解释了其中的细微差别)。 如果已经有一个构造函数,那么任何“上面”的初始化都将被移动到它的顶部

就最佳实践而言,前者比后者更不容易出错,因为有人可能很容易添加另一个构造函数而忘记链接它。

在声明中设置值会略微提高性能。如果你在构造函数中设置它,它实际上被设置了两次(第一次为默认值,然后在ctor中重置)。

在这里,c#的语义与Java略有不同。在c#中,声明中的赋值在调用超类构造函数之前执行。在Java中,它是在允许使用'this'(对匿名内部类特别有用)之后立即执行的,这意味着两种表单的语义确实匹配。

如果可以的话,让这些字段成为最终结果。

我认为有一个警告。我曾经犯过这样一个错误:在派生类内部,我试图“初始化at声明”从抽象基类继承的字段。结果是存在两组字段,一组是“base”字段,另一组是新声明的字段,这花费了我相当多的时间来调试。

教训:要初始化继承了字段,你需要在构造函数内部做。

有许多不同的情况。

我只需要一个空列表

情况很清楚。我只需要准备我的列表,并防止在有人向列表添加项时抛出异常。

public class CsvFile
{
private List<CsvRow> lines = new List<CsvRow>();


public CsvFile()
{
}
}

我知道价值观

我确切地知道在默认情况下我想要什么值,或者我需要使用一些其他逻辑。

public class AdminTeam
{
private List<string> usernames;


public AdminTeam()
{
usernames = new List<string>() {"usernameA", "usernameB"};
}
}

public class AdminTeam
{
private List<string> usernames;


public AdminTeam()
{
usernames = GetDefaultUsers(2);
}
}

可能值的空列表

有时我希望默认情况下是一个空列表,并可以通过另一个构造函数添加值。

public class AdminTeam
{
private List<string> usernames = new List<string>();


public AdminTeam()
{
}


public AdminTeam(List<string> admins)
{
admins.ForEach(x => usernames.Add(x));
}
}

在Java中,带有声明的初始化式意味着字段总是以相同的方式初始化,无论使用哪个构造函数(如果有多个构造函数)或构造函数的参数(如果它们有参数),尽管构造函数随后可能会更改值(如果它不是final)。因此,使用带有声明的初始化式向读者表明,初始化的值是字段具有在所有情况下的值,而不管使用哪个构造函数,也不管传递给任何构造函数的参数。因此,只有当且总是当所有构造对象的值相同时,才使用带有声明的初始化式。

c#的设计表明,内联初始化是首选,否则就不会出现在语言中。只要可以避免代码中不同位置之间的交叉引用,通常情况下就会更好。

还有与静态字段初始化的一致性问题,需要内联以获得最佳性能。构造函数设计的框架设计指南是这样说的:

考虑内联初始化静态字段,而不是显式使用静态构造函数,因为运行时能够优化没有显式定义静态构造函数的类型的性能。

“考虑”在这里的意思是除非有很好的理由不这样做。对于静态初始化字段,一个很好的理由是初始化太复杂,不能内联编码。

保持一致很重要,但你要问自己这个问题: “我还有其他的构造函数吗?”< / p >

通常,我正在为数据传输创建模型,类本身除了作为变量的外壳外什么也不做。

在这些场景中,我通常没有任何方法或构造函数。对我来说,只为了初始化列表而创建构造函数会感觉很愚蠢,特别是因为我可以在声明中对它们进行内联初始化。

正如许多人所说,这取决于你的使用习惯。保持简单,不要做任何不必要的额外事情。

考虑一下有多个构造函数的情况。对于不同的构造函数初始化会不同吗?如果它们将是相同的,那么为什么对每个构造函数重复?这与kokos的说法是一致的,但可能与参数无关。比方说,你想保留一个标志来显示对象是如何创建的。然后,不管构造函数参数如何,该标志将对不同的构造函数进行不同的初始化。另一方面,如果对每个构造函数重复相同的初始化,则有可能在某些构造函数中(无意中)更改初始化参数,而在其他构造函数中则不更改。因此,这里的基本概念是,公共代码应该有一个公共的位置,并且不可能在不同的位置重复。所以我会说,总是把它放在声明中,直到你有一个特定的情况,它不再适用于你。

这不是对你关于最佳实践的问题的直接回答,但一个重要的和相关的复习点是,在泛型类定义的情况下,要么让编译器使用默认值初始化它,要么我们必须使用一个特殊的方法将字段初始化为它们的默认值(如果这对代码可读性绝对必要)。

class MyGeneric<T>
{
T data;
//T data = ""; // <-- ERROR
//T data = 0; // <-- ERROR
//T data = null; // <-- ERROR


public MyGeneric()
{
// All of the above errors would be errors here in constructor as well
}
}

将泛型字段初始化为默认值的特殊方法如下:

class MyGeneric<T>
{
T data = default(T);


public MyGeneric()
{
// The same method can be used here in constructor
}
}

当你不需要一些逻辑或错误处理时:

  • 在声明时初始化类字段

当你需要一些逻辑或错误处理时:

  • 在构造函数中初始化类字段

当初始化值可用且 初始化可以放在一行上。然而,这种形式 初始化因为简单而有局限性。如果 初始化需要一些逻辑(例如,错误处理或 对于循环填充复杂数组),简单的赋值是不够的。 实例变量可以在构造函数中初始化,其中error

.

.

From https://docs.oracle.com/javase/tutorial/java/javaOO/initial.html