Java 中初始化器与构造器的使用

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/804589/
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-11 19:40:21  来源:igfitidea点击:

Use of Initializers vs Constructors in Java

javaconstructorinitializerstatic-initializerinitialization-block

提问by Inertiatic

So I've been brushing up on my Java skills as of late and have found a few bits of functionality that I didn't know about previously. Static and Instance Initializers are two such techniques.

所以最近我一直在复习我的 Java 技能,并发现了一些我以前不知道的功能。静态和实例初始化器就是两种这样的技术。

My question is when would one use an initializer instead of including the code in a constructor? I've thought of a couple obvious possibilities:

我的问题是什么时候会使用初始化程序而不是在构造函数中包含代码?我已经想到了几个明显的可能性:

  • static/instance initializers can be used to set the value of "final" static/instance variables whereas a constructor cannot

  • static initializers can be used to set the value of any static variables in a class, which should be more efficient than having an "if (someStaticVar == null) // do stuff" block of code at the start of each constructor

  • 静态/实例初始值设定项可用于设置“最终”静态/实例变量的值,而构造函数不能

  • 静态初始化器可用于设置类中任何静态变量的值,这应该比在每个构造函数的开头使用“if (someStaticVar == null) // do stuff” 代码块更有效

Both of these cases assume that the code required to set these variables is more complex than simply "var = value", as otherwise there wouldn't seem to be any reason to use an initializer instead of simply setting the value when declaring the variable.

这两种情况都假设设置这些变量所需的代码比简单的“var = value”更复杂,否则似乎没有任何理由使用初始化程序而不是在声明变量时简单地设置值。

However, while these aren't trivial gains (especially the ability to set a final variable), it does seem that there are a rather limited number of situations in which an initializer should be used.

然而,虽然这些不是微不足道的收获(尤其是设置 final 变量的能力),但似乎应该使用初始化器的情况相当有限。

One can certainly use an initializer for a lot of what is done in a constructor, but I don't really see the reason to do so. Even if all constructors for a class share a large amount of code, the use of a private initialize() function seems to make more sense to me than using an initializer because it doesn't lock you into having that code run when writing a new constructor.

对于构造函数中所做的很多事情,当然可以使用初始化程序,但我真的看不出这样做的原因。即使一个类的所有构造函数都共享大量代码,对我来说,使用私有 initialize() 函数似乎比使用初始化程序更有意义,因为它不会让你在编写新的代码时运行该代码构造函数。

Am I missing something? Are there a number of other situations in which an initializer should be used? Or is it really just a rather limited tool to be used in very specific situations?

我错过了什么吗?是否有许多其他情况应该使用初始化程序?或者它真的只是一个在非常特定情况下使用的相当有限的工具?

采纳答案by Eddie

Static initializers are useful as cletus mentioned and I use them in the same manner. If you have a static variable that is to be initialized when the class is loaded, then a static initializer is the way to go, especially as it allows you to do a complex initialization and still have the static variable be final. This is a big win.

正如 cletus 提到的,静态初始化器很有用,我以相同的方式使用它们。如果您有一个在类加载时要初始化的静态变量,那么静态初始化程序是一种可行的方法,特别是因为它允许您进行复杂的初始化并且仍然拥有静态变量 be final。这是一个很大的胜利。

I find "if (someStaticVar == null) // do stuff" to be messy and error prone. If it is initialized statically and declared final, then you avoid the possibility of it being null.

我发现“if (someStaticVar == null) // do stuff”很混乱并且容易出错。如果它是静态初始化并声明的final,那么您就避免了它的可能性null

However, I'm confused when you say:

但是,当您说:

static/instance initializers can be used to set the value of "final" static/instance variables whereas a constructor cannot

静态/实例初始值设定项可用于设置“最终”静态/实例变量的值,而构造函数不能

I assume you are saying both:

我假设你说的是:

  • static initializers can be used to set the value of "final" static variables whereas a constructor cannot
  • instance initializers can be used to set the value of "final" instance variables whereas a constructor cannot
  • 静态初始值设定项可用于设置“最终”静态变量的值,而构造函数不能
  • 实例初始值设定项可用于设置“最终”实例变量的值,而构造函数不能

and you are correct on the first point, wrong on the second. You can, for example, do this:

你在第一点上是正确的,在第二点上是错误的。例如,您可以这样做:

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

Also, when a lot of code is shared between constructors, one of the best ways to handle this is to chain constructors, providing the default values. This makes is pretty clear what is being done:

此外,当构造函数之间共享大量代码时,处理此问题的最佳方法之一是链接构造函数,提供默认值。这很清楚正在做什么:

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

回答by Bill K

As you mentioned, it's not useful in a lot of cases and as with any less-used syntax, you probably want to avoid it just to stop the next person looking at your code from spending the 30 seconds to pull it out of the vaults.

正如您所提到的,它在很多情况下都没有用,并且与任何较少使用的语法一样,您可能希望避免它只是为了阻止下一个人查看您的代码花费 30 秒将其从保险库中取出。

On the other hand, it is the only way to do a few things (I think you pretty much covered those).

另一方面,这是做一些事情的唯一方法(我认为你几乎涵盖了这些)。

Static variables themselves should be somewhat avoided anyway--not always, but if you use a lot of them, or you use a lot in one class, you might find different approaches, your future self will thank you.

无论如何都应该避免静态变量本身——并非总是如此,但是如果你使用了很多静态变量,或者你在一个类中使用了很多,你可能会发现不同的方法,你未来的自己会感谢你。

回答by cletus

I most often use static initializer blocks for setting up final static data, especially collections. For example:

我最常使用静态初始化块来设置最终的静态数据,尤其是集合。例如:

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

Now this example can be done with a single line of code:

现在这个例子可以用一行代码完成:

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

but the static version can be far neater, particularly when the items are non-trivial to initialize.

但是静态版本可以更简洁,特别是当项目不是很容易初始化时。

A naive implementation may also not create an unmodifiable list, which is a potential mistake. The above creates an immutable data structure that you can happily return from public methods and so on.

一个简单的实现也可能不会创建一个不可修改的列表,这是一个潜在的错误。以上创建了一个不可变的数据结构,您可以愉快地从公共方法等返回。

回答by Alex Martelli

Anonymous inner classes can't have a constructor (as they're anonymous), so they're a pretty natural fit for instance initializers.

匿名内部类不能有构造函数(因为它们是匿名的),因此它们非常适合实例初始化器。

回答by Yishai

A static initializer is the equivalent of a constructor in the static context. You will certainly see that more often than an instance initializer. Sometimes you need to run code to set up the static environment.

静态初始化器相当于静态上下文中的构造函数。您肯定会比实例初始化程序更频繁地看到它。有时您需要运行代码来设置静态环境。

In general, an instance initalizer is best for anonymous inner classes. Take a look at JMock's cookbookto see an innovative way to use it to make code more readable.

一般来说,实例初始化器最适合匿名内部类。看看JMock 的说明书,看看有没有一种创新的方法可以使用它来提高代码的可读性。

Sometimes, if you have some logic which is complicated to chain across constructors (say you are subclassing and you can't call this() because you need to call super()), you could avoid duplication by doing the common stuff in the instance initalizer. Instance initalizers are so rare, though, that they are a surprising syntax to many, so I avoid them and would rather make my class concrete and not anonymous if I need the constructor behavior.

有时,如果你有一些复杂的逻辑在构造函数之间链接起来很复杂(假设你是子类化并且你不能调用 this() 因为你需要调用 super()),你可以通过在实例中做常见的事情来避免重复初始化器。然而,实例初始化器是如此罕见,以至于它们对许多人来说是一种令人惊讶的语法,所以我避免使用它们,如果我需要构造函数行为,我宁愿使我的类具体而不是匿名。

JMock is an exception, because that is how the framework is intended to be used.

JMock 是一个例外,因为这就是框架的用途。

回答by Robin

Just to add to some already excellent points here. The static initializer is thread safe. It is executed when the class is loaded, and thus makes for simpler static data initialization than using a constructor, in which you would need a synchronized block to check if the static data is initialized and then actually initialize it.

只是在这里添加一些已经很好的点。静态初始化程序是线程安全的。它在类加载时执行,因此比使用构造函数更简单的静态数据初始化,在构造函数中,您需要一个同步块来检查静态数据是否已初始化,然后实际初始化它。

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

versus

相对

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

Don't forget, you now have to synchronize at the class, not instance level. This incurs a cost for every instance constructed instead of a one time cost when the class is loaded. Plus, it's ugly ;-)

不要忘记,您现在必须在类而不是实例级别进行同步。这会为每个构造的实例带来成本,而不是加载类时的一次性成本。另外,它很丑;-)

回答by kmrinal

I would also like to add one point along with all the above fabulous answers . When we load a driver in JDBC using Class.forName("") the the Class loading happens and the static initializer of the Driver class gets fired and the code inside it registers Driver to Driver Manager. This is one of the significant use of static code block.

除了以上所有精彩的答案,我还想补充一点。当我们使用 Class.forName("") 在 JDBC 中加载驱动程序时,类加载发生,驱动程序类的静态初始化程序被触发,其中的代码将驱动程序注册到驱动程序管理器。这是静态代码块的重要用途之一。

回答by Daniel

I read a whole article looking for an answer to the init order of initializers vs. their constructors. I didn't find it, so I wrote some code to check my understanding. I thought I would add this little demonstration as a comment. To test your understanding, see if you can predict the answer before reading it at the bottom.

我读了整篇文章,寻找初始化器与构造器的 init 顺序的答案。我没有找到,所以我写了一些代码来检查我的理解。我想我会添加这个小演示作为评论。为了测试你的理解,看看你是否可以在阅读底部之前预测答案。

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

Output:

输出:

java CtorOrder
A ctor
B initX
B ctor

回答by Vankog

There is one important aspect that you have to consider in your choice:

您在选择时必须考虑一个重要方面:

Initializer blocks are membersof the class/object, while constructors are not. This is important when considering extension/subclassing:

初始化块是类/对象的成员,而构造函数不是. 这在考虑扩展/子类化时很重要:

  1. Initializers are inheritedby subclasses. (Though, can be shadowed)
    This means it is basically guaranteed that subclasses are initialized as intended by the parent class.
  2. Constructors are notinherited, though. (They only call super()[i.e. no parameters] implicitly or you have to make a specific super(...)call manually.)
    This means it is possible that a implicit or exclicit super(...)call might not initialize the subclass as intended by the parent class.
  1. 初始化器由子类继承。(虽然,可以被屏蔽)
    这意味着基本上可以保证子类按照父类的预期进行初始化。
  2. 但是,构造函数不是继承的。(它们仅super()隐式调用[即无参数],或者您必须super(...)手动进行特定调用。)
    这意味着隐式或显式super(...)调用可能无法按照父类的预期初始化子类。

Consider this example of an initializer block:

考虑这个初始化块的例子:

    class ParentWithInitializer {
        protected String aFieldToInitialize;

        {
            aFieldToInitialize = "init";
            System.out.println("initializing in initializer block of: " 
                + this.getClass().getSimpleName());
        }
    }

    class ChildOfParentWithInitializer extends ParentWithInitializer{
        public static void main(String... args){
            System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
        }
    }

output:

输出:

initializing in initializer block of: ChildOfParentWithInitializer
init

-> No matter what constructors the subclass implements, the field will be initialized.

-> 不管子类实现了什么构造函数,字段都会被初始化。

Now consider this example with constructors:

现在考虑这个带有构造函数的例子:

    class ParentWithConstructor {
        protected String aFieldToInitialize;

        // different constructors initialize the value differently:
        ParentWithConstructor(){
            //init a null object
            aFieldToInitialize = null;
            System.out.println("Constructor of " 
                + this.getClass().getSimpleName() + " inits to null");
        }

        ParentWithConstructor(String... params) {
            //init all fields to intended values
            aFieldToInitialize = "intended init Value";
            System.out.println("initializing in parameterized constructor of:" 
                + this.getClass().getSimpleName());
        }
    }

    class ChildOfParentWithConstructor extends ParentWithConstructor{
        public static void main (String... args){
            System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
        }
    }

output:

输出:

Constructor of ChildOfParentWithConstructor inits to null
null

-> This will initialize the field to nullby default, even though it might not be the result you wanted.

-> 这会将字段初始化为null默认值,即使它可能不是您想要的结果。