为什么需要Java实例初始化器?

35

Java中的“实例初始化器”有什么意义?
我们不能将那一块代码放在构造函数的开始吗?


如果Java放弃这个特性,那也不是什么大问题。 - irreputable
10
@irreputable,我很少需要它们,但是匿名类不能有构造函数,但可以拥有实例初始化器,因此我们需要在语言中支持它们。 - Kaj
1
@Kaj 字段初始化器可以完成这项工作。 - irreputable
2
@irreputable,只有在需要给字段赋值时才需要使用它,而不是需要调用方法时。 - Kaj
4个回答

42

我经常使用它们,通常用于在一条语句中创建和填充Map(而不是使用丑陋的静态块):

private static final Map<String, String> CODES = new HashMap<String, String>() {
    {
        put("A", "Alpha");
        put("B", "Bravo");
    }
};

给这个操作增加一些趣味性和实用性的方法之一是在一个语句中创建一个不可修改的映射:

private static final Map<String, String> CODES = 
    Collections.unmodifiableMap(new HashMap<String, String>() {
    {
        put("A", "Alpha");
        put("B", "Bravo");
    }
});

这种方法比使用静态块并处理对final的单一赋值要更加简洁。

还有一个提示:不要害怕创建能够简化实例块的方法:

private static final Map<String, String> CODES = new HashMap<String, String>() {
    {
        put("Alpha");
        put("Bravo");
    }

    void put(String code) {
        put(code.substring(0, 1), code);
    }
};

3
对于使用实例初始化器来填充集合的人们,建议尝试使用Guava Library作为更优雅和强大的替代方案。Maps类提供了一些很好用的Map工具,而它们的Immutable*类则特别适合描述这里所说的用例,可以看看ImmutableMap - dimo414
1
@Bohemian,我有点困惑,你是如何在没有变量名的情况下调用put();函数的? - jantristanmilan
3
@goldencalf 这是一个匿名类,它是一个(即时生成的)子类*,因此put()是一个实例方法,它会隐式地在this上调用 - 就像您可以从任何实例方法中调用toString()而不必编写this.toString()一样。在示例中,我添加了一个重载版本的put(),它只接受一个参数,并且仅在类定义内可见。 - Bohemian
3
看起来就像是典型的只有程序员自己能够读懂的代码(在写完一年后,直到他忘记当初在想什么为止...);) - jackthehipster
1
@jack,实际上我发现这些更容易阅读,因为它们将对象及其数据绑定到单个结构中。分离的静态初始化块不明显地与正在初始化的对象相关联,实际上可能导致编译但在运行时崩溃的代码。无论如何,这就像大多数编程习惯一样;一旦你习惯了它们,它们就会变得自然而然。 - Bohemian
显示剩余4条评论

27

您确实可以在每个构造函数的开头放置代码。然而,这正是实例初始化器的作用:它的代码适用于所有构造函数,如果您有许多构造函数和一些公共的代码,则这非常方便。

(如果您刚开始学习编程,可能不知道可以为同一个类创建多个构造函数(只要它们采用不同的参数);这被称为构造函数重载。如果您只有一个构造函数,则实例初始化器确实没有什么用处(编辑:除非您以其他答案中所示的创造性方式滥用它)。)


此外,即使只有一个构造函数,声明和初始化一起进行比先在这里声明再在那里初始化更易读且稍微不那么冒险。 - Andy Thomas

7

当声明匿名类时,您可以使用实例初始化程序,例如,在执行双括号初始化习惯用法时。

List<String> mylist = new ArrayList<String>(){{add("a"); add("b"); add("c");}};

在这里,您可以初始化对象,即使您无法向构造函数添加任何内容(因为类是匿名的)。

4
在这种情况下,我建议使用java.util.Arrays.<T>asList(T... ts)来代替 :-) - Platinum Azure

6

由于这里的所有代码示例都使用匿名类,因此我编写了一个(有点可怕的)类,演示如何在“正式”类中使用实例初始化器。您可以使用它们来进行复杂处理或在初始化时处理异常。请注意,这些块在构造函数运行之前运行,但是构造函数在子类中的初始化器运行之前运行:

import java.util.Scanner;

public  class InstanceInitializer {
    int x;
    {
        try {
            System.out.print("Enter a number: ");
            x = Integer.parseInt(new Scanner(System.in).nextLine());
        } catch (NumberFormatException e) {
            x = 0;
        }
    }

    String y;
    {
        System.out.print("Enter a string: ");
        y = new Scanner(System.in).nextLine();
        for(int i = 0; i < 3; i++)
            y += y;
    }

    public InstanceInitializer() {
        System.out.println("The value of x is "+x);
        System.out.println("The value of y is "+y);
    }

    public static class ChildInstanceInitializer extends InstanceInitializer {
        {
            y = "a new value set by the child AFTER construction";
        }
    }

    public static void main(String[] args){
        new InstanceInitializer();
        new InstanceInitializer();
        System.out.println();
        System.out.println(new ChildInstanceInitializer().y);
        // This is essentially the same as:
        System.out.println(new InstanceInitializer(){
            {y = "a new value set by the child AFTER construction";}
        }.y);
    }
}

这将输出(类似于):
Enter a number: 1
Enter a string: a
The value of x is 1
The value of y is aaaaaaaa
Enter a number: q
Enter a string: r
The value of x is 0
The value of y is rrrrrrrr

Enter a number: 3
Enter a string: b
The value of x is 3
The value of y is bbbbbbbb
a new value set by the child AFTER construction
Enter a number: s
Enter a string: Hello
The value of x is 0
The value of y is HelloHelloHelloHelloHelloHelloHelloHello 
a new value set by the child AFTER construction

请注意,在调用父类构造函数之后,才会设置“新值”字符串。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接