我应该在Java中使用初始化程序块吗?


16

最近,我遇到了一个从未见过的Java构造,并且想知道是否应该使用它。它似乎被称为初始化程序块

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

代码块将复制到每个构造函数中,即,如果您有多个构造函数,则不必重写代码。

但是,我看到使用此语法的三个主要缺点:

  1. 这是Java中极少数情况下代码顺序很重要的情况之一,因为您可以定义多个代码块,并且它们将按照编写的顺序执行。这对我来说似乎很有害,因为仅更改代码块的顺序实际上就会更改代码。
  2. 使用它并没有带来任何好处。在大多数情况下,构造函数会使用一些预定义的值相互调用。即使不是这种情况,也可以将代码简单地放入私有方法中,并从每个构造函数中调用。
  3. 它会降低可读性,因为您可以将代码块放在类的末尾,而构造函数通常在类的开始。如果您认为不必要查看代码文件的完全不同的部分,这是违反直觉的。

如果我的上述说法是正确的,为什么(和何时)引入了这种语言结构?有合法的用例吗?


3
您发布的示例不包含任何类似于初始化程序块的内容。
西蒙B

6
@SimonBarker再次查看– { doStuff(); }在类级别上是一个初始化程序块。
阿蒙2014年

@SimonBarker周围的代码块doStuff()
恢复莫妮卡-迪尔克2014年


2
“ [S]暗示更改代码块的顺序实际上将更改代码。” 与更改变量初始化器或代码的顺序有什么不同?如果没有依赖关系,则不会造成伤害,如果存在依赖关系,则将依赖关系置于无序状态与将各个代码行的依赖关系错位相同。仅仅因为Java允许您在定义方法和类之前先引用它们,并不意味着依赖顺序的代码在Java中很少见。
JAB 2014年

Answers:


20

在两种情况下,我使用初始化程序块。

第一个用于初始化最终成员。在Java中,您可以在声明中内联初始化最终成员,也可以在构造函数中对其进行初始化。在一种方法中,禁止分配给最终成员。

这是有效的:

final int val = 2;

这也是有效的:

final int val;

MyClass() {
    val = 2;
}

这是无效的:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

如果您有多个构造函数,并且无法初始化最终成员内联(因为初始化逻辑太复杂),或者构造函数无法调用自身,则可以复制/粘贴初始化代码,也可以使用初始化程序块。

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

我对初始化程序块有的另一个用例是用于构建小型辅助程序数据结构。我声明一个成员,并在其声明之后的自己的初始化块中放置值。

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}

不是方法调用无效。初始化方法中的代码无效。只有构造函数和initalizer块才能分配给最终成员变量,因此init中的分配不会编译。
barjak

您的第四个代码块无法编译。Initalizer块所有构造函数之前运行,因此squareVal = val * val将抱怨访问未初始化的值。初始化程序块不能依赖传递给构造函数的任何参数。对于这种问题,我看到的通常解决方案是使用复杂的逻辑定义单个“基本”构造函数,并根据该逻辑定义所有其他构造函数。实际上,实例初始化程序的大多数用法都可以替换为该模式。
Malnormalulo

11

通常,请勿使用非静态初始化器块(也可能避免使用静态初始化器块)。

令人困惑的语法

看着这个问题,有3个答案,但是您用这种语法欺骗了4个人。我就是其中之一,并且我从事Java已有16年了!显然,语法可能容易出错!我会远离它。

伸缩式构造器

对于非常简单的内容,可以使用“ telescoping”构造函数来避免这种混淆:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

建造者模式

如果需要在每个构造函数或其他复杂的初始化末尾执行doStuff(),则也许最好使用构建器模式。 乔什·布洛赫(Josh Bloch)列出了为什么建造者是一个好主意的几个原因。建设者花了一些时间来编写,但是编写得当,使用起来很愉快。

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

静态初始化循环

我以前用静态初始化程序,但是偶尔会遇到循环,其中两个类在完全加载类之前依赖于彼此的静态初始化程序块被调用。这产生了“无法加载类”或类似的模糊错误消息。我必须将文件与源代码管理中的最新已知工作版本进行比较,以便找出问题所在。一点都没有乐趣。

延迟初始化

也许静态初始化程序在工作时出于性能方面的考虑是好的,并且不太令人困惑。但是总的来说,这些天我更喜欢惰性初始化而不是静态初始化。很明显,它们是做什么的,我还没有遇到与它们一起加载类的错误,并且它们在初始化情况下的工作比初始化程序块要多。

资料定义

现在,我使用Paguro的不可变数据定义帮助器函数来代替用于构建数据结构的静态初始化(与其他答案中的示例进行比较):

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

混乱

在Java刚开始时,初始化程序块是做某些事情的唯一方法,但是现在它们变得混乱,容易出错,并且在大多数情况下,它们已被更好的替代方法替代(上面有详细说明)。知道初始化程序块是很有趣的,以防您在遗留代码中看到它们,或者它们正在测试中,但是如果我正在进行代码审查并且看到新代码中的一个,我会要求您说明为什么没有一个在您的代码通过之前,上述替代方法是合适的。


3

除了声明为的实例变量的初始化之外final(请参见barjak的答案),我还要提到static初始化块。

您可以将它们用作“静态构造函数”。

这样,您可以在第一次引用该类时对静态变量进行复杂的初始化。

这是一个受barjak启发的示例:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}

1

就非静态初始化程序块而言,它们的主要功能是充当匿名类中的默认构造函数。基本上,这是他们存在的唯一权利。


0

我完全同意语句1、2、3。出于这些原因,我也从不使用块初始化程序,而且我也不知道为什么它存在于Java中。

但是,在一种情况下,我不得不使用静态块初始化程序:当我必须实例化其构造函数可能引发检查异常的静态字段时。

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

但是,您必须这样做:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

我觉得这个习惯很丑陋(它也阻止您将其标记contextfinal),但这是Java支持初始化此类字段的唯一方法。


我认为,如果context = null;在catch块中进行设置,则可以将上下文声明为final。
GlenPeterson

我尝试了@GlenPeterson,但无法编译:The final field context may already have been assigned
发现

哎呀!我敢打赌,如果您在静态块内引入局部变量,则可以使上下文最终确定:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.