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

示例1:

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

示例2:

class A {
    B b;

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

这两种方法都可以接受。注意,在后一种情况下,如果存在另一个构造函数,b=new b()可能无法初始化。将构造函数外部的初始化器代码看作一个公共构造函数,代码将被执行。


我认为例2更可取。我认为最佳实践是在构造函数外部声明,并在构造函数中初始化。


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


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

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

有关Java中初始化的更多细节,请参阅http://java.sun.com/docs/books/tutorial/java/javaOO/initial.html(以及关于初始化器块和其他不为人所知的初始化特性的解释)。


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

检查孙的解释和建议

在本教程中:

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

此外,您可能希望惰性地初始化字段。如果初始化字段是一个昂贵的操作,你可以在需要的时候立即初始化它:

ExpensiveObject o;

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

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


另一种选择是使用依赖注入。

class A{
   B b;

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

This removes the responsibility of creating the B object 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 A and B. A benefit that this gives you is that you can now pass any object that extends B (or implements B if it is an interface) to A's constructor and it will work. One disadvantage is that you give up encapsulation of the B object, so it is exposed to the caller of the A constructor. You'll have to consider if the benefits are worth this trade-off, but in many cases they are.


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

的开头声明所有变量 一块 使所有变量为final,除非它们 不能 每行声明一个变量 永远不要在某处初始化变量 宣布 只初始化a中的东西 构造函数时使用 的构造函数 初始化

我的代码是这样的:

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;.

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

你的里程可能会有所不同。


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

如果实例化需要的不仅仅是一个简单的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.
        }
    }

    // ...

}

使用依赖注入或延迟初始化总是更可取的,已经在其他回答中详细解释过了。

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

避免重复=如果你有多个构造函数,或者当你需要添加更多构造函数时,你将不必在所有构造函数体中重复初始化; 改进的可读性=你可以很容易地从类外部判断哪些变量需要初始化; 减少代码行数=在声明时每执行一次初始化,构造函数中就会减少一行。


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

class MyClass extends FooClass {
    String a = null;

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

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

看到错误了吗?结果是,在父类构造函数被调用之后才调用a = null初始化式。由于超类构造函数调用init(), a的初始化之后是a = null初始化。


There is one more subtle reason to initialize outside the constructor that no one has mentioned before (very specific I must say). If you are using UML tools to generate class diagrams from the code (reverse engineering), most of the tools I believe will note the initialization of Example 1 and will transfer it to a diagram (if you prefer it to show the initial values, like I do). They will not take these initial values from Example 2. Again, this is a very specific reason - if you are working with UML tools, but once I learned that, I am trying to take all my default values outside of constructor unless, as was mentioned before, there is an issue of possible exception throwing or complicated logic.


第二种选择更可取,因为它允许在ctors中使用不同的逻辑进行类实例化,并使用ctors链接。如。

class A {
    int b;

    // secondary ctor
    A(String b) {
         this(Integer.valueOf(b));
    }

    // primary ctor
    A(int b) {
         this.b = b;
    }
}

所以第二种选择更加灵活。


我在回复中没有看到以下内容:

在声明时进行初始化的一个可能的优势可能是,在当今的IDE中,您可以非常容易地跳转到变量的声明(大多数情况下) Ctrl-<hover_over_the_variable>-<left_mouse_click>)在你的代码中的任何地方。然后立即看到该变量的值。否则,你必须“搜索”初始化完成的位置(主要是:构造函数)。

这个优势当然是次要的,所有其他逻辑推理,但对一些人来说,“特征”可能更重要。


其实很不一样:

声明发生在构建之前。因此,如果在两个位置都初始化了变量(本例中为b),构造函数的初始化将取代在类级别所做的初始化。

所以在类级别声明变量,在构造函数中初始化它们。


    class MyClass extends FooClass {
    String a = null;

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

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

关于以上,

String a = null;

Null init可以避免,因为它是默认值。 然而,如果您需要另一个默认值, 然后,由于初始化顺序不受控制, 我将修改如下:

class MyClass extends FooClass 
{
    String a;
    {
        if( a==null ) a="my custom default value";
    }
    ...