【问题标题】:Why can't an inner class use static initializer?为什么内部类不能使用静态初始化器?
【发布时间】:2014-10-16 23:49:15
【问题描述】:

QJLS #8.1.3:

内部类不能声明静态初始化器 (§8.7)......

证明如下:

class A {
    class B {
        static { // Compile-time Error: Cannot define static initializer in inner type A.B
            System.out.println("Class is initializing...");
        }
    }
}

既然 Java 的内部(非静态)类像其他所有类一样由 class loaders 加载,为什么我们不能为它们使用静态初始化程序?

造成这种限制的原因是什么?

【问题讨论】:

  • IMO 没有充分的理由这样做,只需在封闭类的静态初始化程序中声明即可。这也可能意味着它是实例绑定的?
  • @xTrollxDudex,外部类的静态初始化程序中的代码将在外部类加载时运行,即使内部类尚未加载。允许我们在内部类中有静态初始化器意味着我们可以延迟加载内部类的初始化代码,这是一件好事。

标签: java inner-classes jls static-initializer


【解决方案1】:

我认为这是因为 Inner 类本身是非静态的。从 Java 的角度来看,它是一个实例变量,我认为(1) 类加载器并非旨在爬入内部非静态类以查找和初始化潜在静态对象。

但这不是不可能的问题,看下面的例子:

public class Outer {
    public static class Inner {
        Outer owner;
        static String constant;

        {
            constant = "foo";
        }

        private Inner(Outer owner) {
            if (owner == null) {
                throw new NullPointerException();
            }
            this.owner = owner;
        }
    }

    public Inner newInner() {
        return new Inner(this);
    }
}

甚至没有警告,因为 Inner 被声明为静态的。

但仔细一看,它有一个指向封闭Outer实例的指针,只能通过Outer创建,因为它只有一个私有构造函数,并且它的所有者不能为空。从程序员的角度来看,它具有非静态内部类的所有约束,可以像一个一样使用(除了像Outer.this 这样的特殊习惯用法),但从编译器的角度来看,它是静态的,它的静态字段将首先被正确初始化Outer类初始化。

(1) : Pacerier 在下面的评论中解释了为什么这是不正确的。

【讨论】:

  • 您说限制的原因是因为“从Java的角度来看,它[内部类]是一个实例变量”。这是不正确的。内部类是一个独立的类,可以由类加载器加载,而无需先加载外部类。这可以通过运行带有-verbose 标志的java 并执行以下代码来验证:Class.forName("Outer$Inner"); Class.forName("Outer");。我们可以看到输出显示“loaded Outer$Inner”之前它显示“loaded Outer”。
【解决方案2】:

无效使用

只是我的意见,不胜感激争论/辩论

请阅读以下主题。

This explains, Why does Java prohibit static fields in inner classes

IMO 同样的原因也适用于static initializer。毕竟,造成问题的是关键字static

除了上面帖子中解释的原因,我可以再给出一个蹩脚的理由
块的名称static initializer 提示我们何时以及为何使用此块。不能简单地使用静态初始化程序块来打印 hello world [在此处插入 meme]。
使用这个块的主要原因显然是初始化静态变量。

现在作为内部类/非静态嵌套类不允许静态变量,允许静态初始化器有什么意义?

【讨论】:

    【解决方案3】:

    根据定义存在矛盾:

    来自JLS §8.1.3

    一个语句或表达式出现在静态上下文中当且仅当 最里面的方法,构造函数,实例初始化器,静态 初始值设定项、字段初始值设定项或显式构造函数调用 包含语句或表达式的语句是静态方法, 静态初始化器,静态变量的变量初始化器,或 显式构造函数调用语句(第 8.8.7 节)。

    ...

    当一个内部类(其声明不出现在静态 context) 指的是一个实例变量,它是 词法封闭类,对应词法的变量 使用封闭实例。

    【讨论】:

    • 这不能回答问题。虽然内部类确实与其封闭类的实例相关联,但它仍然具有静态初始化器。这并没有解释原因为什么 JLS 不允许它们使用静态初始化器。 (另外,我说的是静态初始化器,而不是静态字段。)
    • 关键句是“内部类与其封闭类的实例相关联”。正如您所提到的,静态初始化程序/变量/方法仅在加载类时加载一次,如果它们“绑定”到单个 外部 实例,则存在冲突的危险可以从其他外部实例访问。
    • InnerClass 的一个实例与OuterClass 的一个实例相关联,但InnerClass 的所有实例共享同一个Class 对象。当InnerClassClass 对象被初始化时,将没有OuterClass 的关联实例。它像其他所有 Class 对象一样被初始化。那么,为什么我们不能为这个 Class 对象设置静态初始化器呢?
    • @alfasin 有什么危险?
    • @EJP 我找不到简单的说法:任何实例都可以访问静态类成员。但是同一类的两个实例不能相互访问,除非特别“给予许可”(例如,其中一个作为参数传递给另一个)。内部类与其周围的外部实例相关联,并且可以访问其成员。假设 static scope 在内部类中是允许的,这将创建一种情况,即不同的实例可以在没有“许可”的情况下使用这些静态变量/方法/范围进行访问和相互影响。这甚至没有提到反射......
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-06
    • 1970-01-01
    • 2014-04-13
    • 1970-01-01
    • 2013-04-26
    相关资源
    最近更新 更多