Boz*_*zho 261

  • 没有区别 - 实例变量初始化实际上是由编译器放在构造函数中的.
  • 第一个变体更具可读性.
  • 您不能使用第一个变体进行异常处理.
  • 还有初始化块,它也由编译器放在构造函数中:

    {
        a = new A();
    }
    
    Run Code Online (Sandbox Code Playgroud)

检查Sun的解释和建议

本教程:

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

此外,您可能希望懒洋洋地初始化您的字段.如果初始化字段是一项昂贵的操作,您可以在需要时立即对其进行初始化:

ExpensiveObject o;

public ExpensiveObject getExpensiveObject() {
    if (o == null) {
        o = new ExpensiveObject();
    }
    return o;
}
Run Code Online (Sandbox Code Playgroud)

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

  • `第一个变体更具"可读性"`这是可讨论的:如果你在构造函数中初始化所有字段,你就知道当你阅读代码时,你只有一个地方可以搜索... (8认同)
  • “最终(正如 Bill 所指出的),为了依赖管理,最好避免在类中的任何地方使用 new 运算符。相反,使用依赖注入更好”。至少你说更可取。这种鲍勃大叔主义如果狂热地遵循,会导致很多问题(例如工厂爆炸)。new 操作符没有任何问题,也不是所有依赖项都需要注入,特别是如果您对社交测试感到满意。 (2认同)

Bil*_*ard 36

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

class A{
   B b;

   A(B b) {
      this.b = b;
   }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 另一方面,它增加了耦合,因为现在你已经使"A"和"B"之间的联系更加明显.以前,使用"B"是"A"的内部问题,如果事实证明更好的设计不使用"B",那么你的建议就更难改变了. (11认同)
  • 无论如何,耦合是存在的 - A需要B.但是在类中实例化意味着"A需要_exactly this B_",而DI允许使用许多不同的B. (11认同)
  • @BilltheLizard你会使用这个成语,甚至是像`List <Integer> intList = new ArrayList <>();`?这可能完全是内部实现细节.将ArrayList传递给constuctor似乎恰好与良好的封装相反. (5认同)
  • 在这个*设计中,'A`需要'B`*现在*,我的观点是关于这种情况是否会发生变化. (4认同)
  • @jk:如果您将对象创建与业务逻辑分开 - 特别是在创建A的地方 - 使用DI和Factory类,则根本不难改变.它只需要在一个地方进行更改,即创建A对象的Factory.如果你对它保持一致,那就不难理解了.我认为好处大于成本.减少了耦合,整体设计更加便于测试和维护. (3认同)
  • @sprinter您将List(或Collection)传递给构造函数,而不是ArrayList.如果你传递一个类,那么你就会将构造函数与该实现联系起来.如果您决定稍后更改它,则必须在调用构造函数的任何位置更改它.始终使用最抽象的类型来处理方法参数和返回类型. (2认同)

Edw*_*alk 20

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

class MyClass extends FooClass {
    String a = null;

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

    @Override
    protected void init() {
        super.init();
        if (something)
            a = getStringYadaYada();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这里的教训永远不会从构造函数中调用可覆盖的函数!:)有效的Java,第17项有一个很好的讨论. (11认同)

Tof*_*eer 14

我个人的"规则"(几乎没有破坏)是:

  • 在块的开头声明所有变量
  • 使所有变量都是最终的,除非他们不能
  • 每行声明一个变量
  • 永远不会初始化声明的变量
  • 当需要构造函数中的数据进行初始化时,才初始化构造函数中的某些东西

所以我会有如下代码:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这样我总是100%确定在哪里寻找变量声明(在块的开头),以及它们的赋值(在声明之后一旦有意义).由于您永远不会使用未使用的值初始化变量(例如声明和init变量,然后在需要具有值的那些变量的一半之前抛出异常),因此这可能会更有效.你也不会做无意义的初始化(比如int i = 0;然后在之后,在使用"i"之前,做i = 5;.

我非常重视一致性,所以遵循这个"规则"是我一直在做的事情,这使得使用代码变得更加容易,因为你不必去找东西.

你的旅费可能会改变.

  • 这是罪恶的丑陋,这就是它的错.在使用三元运算符之前你会吃自己的眼睛是相当有趣的,但是在OOP适当的构造函数上更喜欢多个静态初始化块.你的方式完全打破了依赖注入(面值,是的,编译器基本上通过将所有东西都移到构造函数来为你修复它,但是你基本上教人们依赖编译器魔法而不是正确的东西),是不可维护的,并让我们回到C++糟糕的日子.新手读者,请不要这样做. (23认同)
  • 如果您要包含关于创建可以是final,final的变量的规则.那么你应该真的包括关于使所有变量可以是私有的私有的. (2认同)

Bal*_*usC 7

例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.
        }
    }

    // ...

}
Run Code Online (Sandbox Code Playgroud)