在Java中使用Initializers与Constructors

时间:2009-04-29 22:39:11

标签: java constructor initializer static-initializer initialization-block

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

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

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

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

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

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

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

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

10 个答案:

答案 0 :(得分:53)

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

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

然而,当你说:

时,我很困惑
  

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

我假设你说的都是:

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

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

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

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

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

答案 1 :(得分:50)

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

答案 2 :(得分: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);
  }

  ...
}

现在可以使用一行代码完成此示例:

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

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

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

答案 3 :(得分: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"));
        }
    }

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

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

答案 4 :(得分: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");
  }

}

输出:

java CtorOrder
A ctor
B initX
B ctor

答案 5 :(得分:7)

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

通常,实例初始化器最适合匿名内部类。看一下JMock's cookbook,看一下使用它来创建代码更具可读性的创新方法。

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

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

答案 6 :(得分:5)

您需要考虑一个重要方面:

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

  1. 子类继承初始值设定项。 (虽然,可以被遮蔽)
    这意味着基本上保证子类按父类的预期初始化。
  2. 构造函数继承。 (他们只是隐式调用super() [即没有参数],或者您必须手动进行特定super(...)调用。)
    这意味着隐式或隐式super(...)调用可能不会按父类的意图初始化子类。
  3. 考虑初始化块的这个例子:

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

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

    现在考虑使用构造函数的这个例子:

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

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

答案 7 :(得分:4)

我还想补充一点以及上述所有精彩答案。当我们使用Class.forName(“”)在JDBC中加载驱动程序时,会发生类加载,并且会触发Driver类的静态初始化程序,并且其中的代码会将Driver注册到Driver Manager。这是静态代码块的重要用途之一。

答案 8 :(得分:3)

正如你所提到的,它在很多情况下并没有用,而且对于任何较少使用的语法,你可能想要避免它只是为了阻止下一个看你的代码的人花30秒时间把它拉出来保险库。

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

静态变量本身应该在某种程度上避免 - 并非总是如此,但是如果你使用了很多它们,或者你在一个类中使用了很多,你可能会发现不同的方法,你未来的自我会感谢你。

答案 9 :(得分:0)

请注意,执行一些副作用的静态初始值设定项的一个大问题是它们不能在单元测试中被模拟。

我见过图书馆这样做,这是一个很大的痛苦。

所以最好只保持那些静态初始化器纯。