首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >我应该在Java中使用初始化块吗?

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

Software Engineering用户
提问于 2014-05-12 14:50:09
回答 5查看 15.6K关注 0票数 22

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

代码语言:javascript
复制
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. 它降低了可读性,因为您可以将块放在类的末尾,构造函数通常位于类的开头。如果您不认为这是必要的,那么查看代码文件的一个完全不同的部分是完全违反直觉的。

如果我的上述陈述是正确的,为什么(以及何时)引入这个语言结构?是否有任何合法的用例?

EN

回答 5

Software Engineering用户

发布于 2014-05-12 15:27:40

有两种情况下,我使用初始化块。

第一个是初始化最终成员。在Java中,您可以根据声明初始化最终成员,也可以在构造函数中初始化它。在方法中,禁止向最终成员分配。

这是有效的:

代码语言:javascript
复制
final int val = 2;

这也是有效的:

代码语言:javascript
复制
final int val;

MyClass() {
    val = 2;
}

这是无效的:

代码语言:javascript
复制
final int val;

MyClass() {
    init();
}

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

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

代码语言:javascript
复制
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;
}

初始化程序块的另一个用例是构建小助手数据结构。我声明一个成员,并在其声明之后将值放在它自己的初始化器块中。

代码语言:javascript
复制
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");
}
票数 21
EN

Software Engineering用户

发布于 2016-06-24 19:15:56

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

混淆语法

看看这个问题,有3个答案,但你用这个语法愚弄了4个人。我是他们中的一员,我写Java已经16年了!显然,语法可能容易出错!我会远离它的。

伸缩构筑物

对于非常简单的内容,您可以使用“伸缩”构造器来避免这种混淆:

代码语言:javascript
复制
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;
    }
}

Builder模式

如果您需要在每个构造函数或其他复杂的初始化结束时使用doStuff(),那么最好使用构建器模式。乔希·布洛赫列出了为什么构建器是一个好主意的几个原因。建设者花点时间写,但写得恰到好处,他们都是喜悦用的。

代码语言:javascript
复制
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的不可变数据定义辅助函数

代码语言:javascript
复制
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"));

Conculsion

在Java开始时,初始化程序块是完成某些事情的唯一方法,但是现在它们很混乱,容易出错,并且在大多数情况下已经被更好的替代物所取代(上面详细介绍了)。了解初始化程序块是很有趣的,以防您在遗留代码中看到它们,或者它们出现在测试中,但是如果我正在进行代码评审,并且在新代码中看到了一个,我会要求您在对代码竖起大拇指之前,说明为什么上述任何选项都是合适的。

票数 15
EN

Software Engineering用户

发布于 2014-05-12 15:44:27

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

你可以用它们作为一种“静态结构”。

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

下面是一个由barjak的例子启发而来的例子:

代码语言:javascript
复制
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);
    }
}
票数 3
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/238820

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档