Java 我应该在声明时还是在构造函数中实例化实例变量?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1994218/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 02:13:54  来源:igfitidea点击:

Should I instantiate instance variables on declaration or in the constructor?

javaconstructorinstance-variables

提问by DonX

Is there any advantage for either approach?

这两种方法有什么优势吗?

Example 1:

示例 1:

class A {
    B b = new B();
}

Example 2:

示例 2:

class A {
    B b;

    A() {
         b = new B();
    }
}

采纳答案by Bozho

  • There is no difference - the instance variable initialization is actually put in the constructor(s) by the compiler.
  • The first variant is more readable.
  • You can't have exception handling with the first variant.
  • There is additionally the initialization block, which is as well put in the constructor(s) by the compiler:

    {
        a = new A();
    }
    
  • 没有区别 - 实例变量初始化实际上是由编译器放入构造函数中的。
  • 第一个变体更具可读性。
  • 第一个变体不能进行异常处理。
  • 另外还有初始化块,它也被编译器放在构造函数中:

    {
        a = new A();
    }
    

Check Sun's explanation and advice

查看Sun 的解释和建议

From this tutorial:

本教程

Field declarations, however, are not part of any method, so they cannot be executed as statements are. Instead, the Java compiler generates instance-field initialization code automatically and puts it in the constructor or constructors for the class. The initialization code is inserted into a constructor in the order it appears in the source code, which means that a field initializer can use the initial values of fields declared before it.

然而,字段声明不是任何方法的一部分,因此它们不能像语句那样执行。相反,Java 编译器会自动生成实例字段初始化代码并将其放入类的一个或多个构造函数中。初始化代码按照它在源代码中出现的顺序插入到构造函数中,这意味着字段初始值设定项可以使用在它之前声明的字段的初始值。

Additionally, you might want to lazily initializeyour field. In cases when initializing a field is an expensive operation, you may initialize it as soon as it is needed:

此外,您可能希望延迟初始化您的字段。如果初始化字段是一项昂贵的操作,您可以在需要时立即初始化它:

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}

And ultimately (as pointed out by Bill), for the sake of dependency management, it is better to avoidusing the newoperator anywhere within your class. Instead, using Dependency Injectionis preferable - i.e. letting someone else (another class/framework) instantiate and inject the dependencies in your class.

最后(正如 Bill 所指出的),为了依赖管理,最好避免new在类中的任何地方使用运算符。相反,最好使用依赖注入- 即让其他人(另一个类/框架)在您的类中实例化和注入依赖项。

回答by Chandra Patni

Both of the methods are acceptable. Note that in the latter case b=new B()may not get initialized if there is another constructor present. Think of initializer code outside constructor as a common constructor and the code is executed.

这两种方法都可以接受。请注意,在后一种情况下,b=new B()如果存在另一个构造函数,则可能不会被初始化。将构造函数外部的初始化代码视为通用构造函数并执行代码。

回答by jkeesh

I think Example 2 is preferable. I think the best practice is to declare outside the constructor and initialize in the constructor.

我认为示例 2 更可取。我认为最好的做法是在构造函数之外声明并在构造函数中初始化。

回答by fastcodejava

The second is an example of lazy initialization. First one is more simple initialization, they are essentially same.

第二个是延迟初始化的例子。第一个是更简单的初始化,它们本质上是一样的。

回答by Vinko Vrsalovic

I take it is almost just a matter of taste, as long as initialization is simple and doesn't need any logic.

我认为这几乎只是一个品味问题,只要初始化简单并且不需要任何逻辑即可。

The constructor approach is a bit more fragile if you don't use an initializer block, because if you later on add a second constructor and forget to initialize b there, you'll get a null b only when using that last constructor.

如果不使用初始化块,构造函数方法会更脆弱一些,因为如果稍后添加第二个构造函数并忘记在那里初始化 b ,则只有在使用最后一个构造函数时才会得到空 b 。

See http://java.sun.com/docs/books/tutorial/java/javaOO/initial.htmlfor more details about initialization in Java (and for explanations on initalizer blocks and other not well known initialization features).

请参阅http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html了解有关 Java 初始化的更多详细信息(以及有关初始化程序块和其他不知名的初始化功能的说明)。

回答by Bill the Lizard

Another option would be to use Dependency Injection.

另一种选择是使用Dependency Injection

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}

This removes the responsibility of creating the Bobject from the constructor of A. This will make your code more testable and easier to maintain in the long run. The idea is to reduce the coupling between the two classes Aand B. A benefit that this gives you is that you can now pass any object that extends B(or implements Bif it is an interface) to A's constructor and it will work. One disadvantage is that you give up encapsulation of the Bobject, so it is exposed to the caller of the Aconstructor. You'll have to consider if the benefits are worth this trade-off, but in many cases they are.

这消除了B从 的构造函数创建对象的责任A。从长远来看,这将使您的代码更易于测试且更易于维护。这个想法是为了减少两个类AB. 这给您带来的一个好处是,您现在可以将任何扩展B(或实现,B如果它是接口)的对象传递给A的构造函数,并且它将起作用。一个缺点是你放弃了B对象的封装,所以它暴露给A构造函数的调用者。您必须考虑这些好处是否值得进行这种权衡,但在许多情况下确实值得。

回答by TofuBeer

my personal "rule" (hardly ever broken) is to:

我个人的“规则”(几乎从未被打破)是:

  • declare all variables at the start of a block
  • make all variables final unless they cannot be
  • declare one variable per line
  • never initialize a variable where declared
  • only initialize something in a constructor when it needs data from the constructor to do the initialization
  • 在块的开头声明所有变量
  • 使所有变量成为最终变量,除非它们不能
  • 每行声明一个变量
  • 永远不要在声明的地方初始化变量
  • 仅当需要来自构造函数的数据进行初始化时才在构造函数中初始化某些内容

So I would have code like:

所以我会有这样的代码:

public class X
{
    public static final int USED_AS_A_CASE_LABEL = 1; // only exception - the compiler makes me
    private static final int A;
    private final int b;
    private int c;

    static 
    { 
        A = 42; 
    }

    {
        b = 7;
    }

    public X(final int val)
    {
        c = val;
    }

    public void foo(final boolean f)
    {
        final int d;
        final int e;

        d = 7;

        // I will eat my own eyes before using ?: - personal taste.
        if(f)
        {
            e = 1;
        }
        else
        {
            e = 2;
        }
    }
}

This way I am always 100% certain where to look for variables declarations (at the start of a block), and their assignments (as soon as it makes sense after the declaration). This winds up potentially being more efficient as well since you never initialize a variable with a value that is not used (for example declare and init vars and then throw an exception before half of those vars needed to have a value). You also do not wind up doing pointless initialization (like int i = 0; and then later on, before "i" is used, do i = 5;.

通过这种方式,我始终 100% 确定在何处查找变量声明(在块的开头)及其分配(只要在声明之后有意义)。这最终可能会更有效,因为您永远不会使用未使用的值初始化变量(例如,声明和初始化变量,然后在需要具有值的变量的一半之前抛出异常)。你也不会做无意义的初始化(比如 int i = 0; 然后稍后,在使用“i”之前,做 i = 5;。

I value consistency very much, so following this "rule" is something I do all the time, and it makes it much easier to work with the code since you don't have to hunt around to find things.

我非常看重一致性,所以遵循这个“规则”是我一直在做的事情,它使代码的工作变得更加容易,因为你不必四处寻找东西。

Your mileage may vary.

你的旅费可能会改变。

回答by BalusC

Example 2 is less flexible. If you add another constructor, you need to remember to instantiate the field in that constructor as well. Just instantiate the field directly, or introduce lazy loading somewhere in a getter.

示例 2 不太灵活。如果添加另一个构造函数,则还需要记住在该构造函数中实例化该字段。只需直接实例化该字段,或者在 getter 中的某处引入延迟加载。

If instantiation requires more than just a simple new, use an initializer block. This will be run regardlessof the constructor used. E.g.

如果实例化需要的不仅仅是一个简单的new,请使用初始化块。无论使用的构造函数如何,这都将运行。例如

public class A {
    private Properties properties;

    {
        try {
            properties = new Properties();
            properties.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("file.properties"));
        } catch (IOException e) {
            throw new ConfigurationException("Failed to load properties file.", e); // It's a subclass of RuntimeException.
        }
    }

    // ...

}

回答by Marco Lackovic

Using either dependency injectionor lazy initializationis always preferable, as already explained thoroughly in other answers.

使用依赖注入延迟初始化总是可取的,正如其他答案中已经详细解释的那样。

When you don't want or can't use those patterns, and for primitive data types, there are three compelling reasons that I can think of why it's preferable to initialize the class attributes outside the constructor:

当您不想或不能使用这些模式时,对于原始数据类型,我可以想到三个令人信服的原因,为什么最好在构造函数之外初始化类属性:

  1. avoided repetition= if you have more than one constructor, or when you will need to add more, you won't have to repeat the initialization over and over in all the constructors bodies;
  2. improved readability= you can easily tell with a glance which variables will have to be initialized from outside the class;
  3. reduced lines of code= for every initialization done at the declaration there will be a line less in the constructor.
  1. 避免重复=如果您有多个构造函数,或者当您需要添加更多构造函数时,您将不必在所有构造函数体中一遍又一遍地重复初始化;
  2. 提高可读性= 一眼就能轻松看出哪些变量必须从类外部初始化;
  3. 减少代码行= 对于在声明中完成的每次初始化,构造函数中都会少一行。

回答by Edward Falk

I got burned in an interesting way today:

我今天以一种有趣的方式被烧毁:

class MyClass extends FooClass {
    String a = null;

    public MyClass() {
        super();     // Superclass calls init();
    }

    @Override
    protected void init() {
        super.init();
        if (something)
            a = getStringYadaYada();
    }
}

See the mistake? It turns out that the a = nullinitializer gets called afterthe superclass constructor is called. Since the superclass constructor calls init(), the initialization of ais followedby the a = nullinitialization.

看到错误了吗?事实证明,在a = null调用超类构造函数之后调用了初始化程序。由于超类构造函数调用init()的初始化a遵循a = null初始化。