自己之前答题的时候没考虑太多,,真的答案有一点点问题,重新考虑了一下,希望和大家重新讨论一下。
首先还是对于成员内部类(声明在成员变量的位置,内部类不被static修饰)来说内部类的地位等同于成员变量。反编译之后可以看到内部类内保持着外部实例的引用
对于这一点我们可以通过一个例子进行类比
class Student{
public String name;
}
//...Student s1 = new Student();
s1.name = "alice";
Student s2 = new Student();
s2.name = "bob";
我们可以看到这种非静态成员变量的作用就是隔离——让每个实例的这个变量不会相互干扰,有自己的含义。
[1] 那么同等地位的成员内部类也应该是这样的作用。
那么我们现在可以做一个假设,如果成员内部类中有一个静态变量,那么这个静态变量应该作为一个什么语义的存在呢,我们有两种认知方式:这个成员内部类的每个实例,都有着一致的静态变量。(本题的争议点,大家觉得这样毫无问题,可是Java确实会报一个编译错误)
持有相同外部类实例引用的内部类实例,有着一致的静态变量。
解释一下第二点。比如Student中有一个Grade的内部类,第二点的意思是,在s1的实例内部,如果构建了
个Grade实例,那么
个实例的静态域是一致的;在s2的实例内构建的
个Grade实例的静态域同样一致,但是和前面所说的s1内的
个Grade实例不相同。
这一点是类比[1]的结论得来的另一个比较合理的“静态域”语义的思考方式。
[2] 那么这样的“静态”语义就有了两种解法,而且都是有合理性的。
为了规避这种二义性,在成员内部类中禁止了静态域的写法。这样同时也解释了题主问题中所说的我们希望一个静态域只有一个实例(这句说的是所有类实例的静态域值一样), 不过对于每个外部对象, 会分别有一个单独的内部类实例。如果这个域不是final , 它可能就不是唯一的(上面所说的第二点的静态语义)。
而且值得注意的是,这种限制是仅仅存在于java编译器的。也就是说,对于内部类的这个操作,只是java编译器在做手脚,java编译器不承认内部类的静态域操作,所以直接在语法解析阶段抛了编译错误。但是JVM似乎对此毫不知情。
所以想要从内存加载的角度,来解释这个问题的人,可能也很难解释清楚。(我真的已经相信这就是一个规定的问题了。。。)
而对于 @风雨歇 所提到的,内部类继承一个拥有静态域的其他类,那么编译是成功的,可以在没有任何实例的情况下,用内部类去访问这个静态域。
这样的情况则是由于引入了新的类,而使得情况完全无歧义的符合第一个语义的,所以通过编译毫无问题,这也一定程度佐证了JVM不知情的事实(。。。
所以,成员内部类内不能使用静态域的原因可能真的是因为规定的原因。。
(一开始没答好,可耻的匿了
@迷离若虚 hi 你好~
你的答案里的加粗论断是我详细论证的部分。我认为你的这个认为语义完全没有歧义的论断有点粗糙。。。因为这个语法的二义性体现在语义上 而非实现上。就像c中的未定义行为i++++i一样,编译器可以实现并且有对它的实现,不代表这个语句是没有二义性、无歧义的。因此你说的方法放在方法区所以和实例无关,是从实现层面来说的,它并不能证明 成员内部类内不能使用静态域 就不存在二义性。。。
因此我希望你可以详细谈谈你认为这个语句语义毫无问题、没有歧义的原因。
而且我的答案中最后也强调了,JVM对这件事是丝毫不知情的,如果我们开心我们可以通过操纵字节码的方式来实现成员内部类内使用静态域的结果。所以你说的方法信息加载在方法区,即从JVM的角度来分析这个问题,是得不到一个合理的解释的。
programmer被限制行为的地点位于编译器前端而非后端。因此我认为中文社区里,一票企图从JVM的角度去论证这个问题的,可能都会得到非常confused的结论。
最后我认为这属于一个定义问题或者规约问题。旨在减少语言未定义行为带来的危害。因此我完全不认为「成员内部类内不能使用静态域 这就是一个规定」是一个错误的理解。因为这确实是从它的语言表现出发进行理解得出的结论,是sound的,讲的通的。