在Java中使用Initializers与Constructors

Ine*_*tic 94 java constructor initializer static-initializer initialization-block

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

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

  • static/instance initializers可用于设置"final"静态/实例变量的值,而构造函数则不能

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

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

然而,虽然这些并非微不足道的收益(特别是设置最终变量的能力),但似乎应该使用初始化程序的情况相当有限.

当然可以在构造函数中使用初始化器来完成很多工作,但我真的没有看到这样做的原因.即使一个类的所有构造函数共享大量代码,使用私有initialize()函数似乎比使用初始化程序更有意义,因为它不会阻止您在编写新代码时运行该代码构造函数.

我错过了什么吗?是否还有许多其他情况需要使用初始化程序?或者它是否真的只是在非常具体的情况下使用的相当有限的工具?

Edd*_*die 55

静态初始化器可用作提到的cletus,我以相同的方式使用它们.如果你有一个静态变量,要在加载类时进行初始化,那么静态初始化器就是要走的路,特别是因为它允许你进行复杂的初始化并且仍然有静态变量final.这是一个很大的胜利.

我发现"if(someStaticVar == null)//做东西"变得混乱且容易出错.如果它是静态初始化并声明的final,那么你就避免了它的可能性null.

但是,当你说:

static/instance initializers可用于设置"final"静态/实例变量的值,而构造函数则不能

我假设你说两个:

  • static initializers可用于设置"final"静态变量的值,而构造函数则不能
  • 实例初始值设定项可用于设置"最终"实例变量的值,而构造函数则不能

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

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这就是我说的是.我已经知道了,在宣布决赛时必须设置决赛,而不是只能设置一次.当我想到它时,这是一个愚蠢的想法,但它仍然在我脑海中.谢谢你清理它. (3认同)

Ale*_*lli 54

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


cle*_*tus 26

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

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);
  }

  ...
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是静态版本可以更加整洁,特别是当项目初始化时非常重要.

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

  • 然后将SUITS更改为PAST_GIRLFRIENDS或其他.但你是对的,因为经典卡片组改变的可能性大约为零,enums会更合适. (19认同)
  • 我不太喜欢你的具体例子,因为它更适合作为`enum`实现. (4认同)

Rob*_*bin 14

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

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

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

不要忘记,您现在必须在类同步,而不是实例级别.这会导致每个实例构建的成本,而不是在加载类时的一次性成本.另外,它很难看;-)


Dan*_*iel 13

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

/**
 * 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");
  }

}
Run Code Online (Sandbox Code Playgroud)

输出:

java CtorOrder
A ctor
B initX
B ctor
Run Code Online (Sandbox Code Playgroud)

  • 如果通过初始化块和静态初始化块扩展此示例,则会更有用. (4认同)

Yis*_*hai 7

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

通常,实例初始化器最适合匿名内部类.看一看JMock的食谱,看看用它来创造代码更具可读性的创新方法.

有时,如果你有一些复杂的逻辑链接到构造函数(比如说你是子类,你不能调用this(),因为你需要调用super()),你可以通过在实例中执行常见的东西来避免重复initalizer.然而,实例初始化是非常罕见的,它们对许多人来说是一种令人惊讶的语法,所以我避免使用它们,如果我需要构造函数行为,我宁愿让我的类具体而不是匿名.

JMock是一个例外,因为这就是框架的使用方式.


Van*_*kog 6

您必须选择一个重要方面:

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

  1. 初始化程序由子类继承。(尽管可能会被遮盖)
    这意味着基本上可以保证子类按照父类的预期进行初始化。
  2. 构造函数不是继承的。(它们仅super()隐式调用[即没有参数],否则您必须super(...)手动进行特定的调用。)
    这意味着隐式或显式super(...)调用可能未按父类的预期初始化子类。

考虑以下初始化块示例:

class ParentWithInitializer {
    protected final 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:
initializing in initializer block of: ChildOfParentWithInitializer init
->无论子类实现什么构造函数,都将初始化该字段。

现在考虑使用构造函数的示例:

class ParentWithConstructor {
    protected final 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:
Constructor of ChildOfParentWithConstructor inits to null null
->这将null默认将字段初始化为,即使它可能不是您想要的结果。

  • 你说的还是没有道理。基类应该始终设计其构造函数以正确初始化自身,这样您尝试发明的问题就不会存在。与构造函数相比,初始化程序没有任何优势。 (2认同)