Java为什么禁止内部类中的静态字段?


85
class OuterClass {
 class InnerClass {
  static int i = 100; // compile error
  static void f() { } // compile error
 }
} 

虽然无法使用来访问静态字段OuterClass.InnerClass.i,但是如果我想记录一些应该是静态的内容(例如,创建的InnerClass对象的数量),则使该字段静态化将很有帮助。那么,为什么Java禁止内部类中的静态字段/方法呢?

编辑:我知道如何使编译器对静态嵌套类(或静态内部类)感到满意,但是我想知道的是为什么Java禁止从语言设计和内部类(或普通内部类)内部的静态字段/方法实施方面,如果有人对此有更多了解。


3
我最喜欢的示例是为内部类配备一个Logger。它不能像其他所有Logger一样是静态的。
Piotr Findeisen

Answers:


32

内部类背后的想法是在封闭实例的上下文中进行操作。以某种方式允许静态变量和方法与这种动机相矛盾?

8.1.2内部类和封闭实例

内部类是未显式或隐式声明为static的嵌套类。内部类不得声明静态初始化器(第8.7节)或成员接口。内部类不得声明静态成员,除非它们是编译时常量字段(第15.28节)。


18
也许它只是决定这样
格雷戈里Pakosz

3
您不能在没有父引用的情况下实例化非静态内部,但仍可以对其进行初始化
skaffman

如果ClassLoader保留一个表示“ Class X已经初始化”的缓存,则它们的逻辑不能用于初始化Class [表示X的对象]的多个实例(当必须将Class对象实例化为几个内部对象的内部类时,需要这样做)不同的对象)。
Erwin Smout 2015年

@skaffman仍然没有意义。内部类的静态属性将只初始化一次,那么会是什么问题呢?现在,我有一个静态哈希图,并且有大约4种方法只能操作此图,这使得将所有内容组合到一个内部类中更为理想。但是,静态哈希图现在必须位于外部,并且可能还有其他相关内容,这简直是愚蠢的。初始化静态属性会有什么问题?
mmm

54

我想知道的是为什么Java禁止内部类内部使用静态字段/方法

因为那些内部类是“实例”内部类。也就是说,它们就像封闭对象的实例属性。

由于它们是“实例”类,因此允许static功能没有任何意义,因为static它首先要在没有实例的情况下工作。

就像您尝试同时创建一个static / instance属性一样。

请看以下示例:

class Employee {
    public String name;
}

如果创建雇员的两个实例:

Employee a = new Employee(); 
a.name = "Oscar";

Employee b = new Employee();
b.name = "jcyang";

很明显,为什么每个人对财产都有自己的价值name,对吗?

内部类也是如此。每个内部类实例都独立于另一个内部类实例。

因此,如果尝试创建counter类属性,则无法在两个不同的实例之间共享该值。

class Employee {
    public String name;
    class InnerData {
        static count; // ??? count of which ? a or b? 
     }
}

创建实例时ab在上面的示例中,静态变量的正确值是count什么?无法确定它,因为InnerData类的存在完全取决于每个封闭的对象。

这就是为什么当类声明为时static,它不再需要活动的实例来进行活动。现在没有依赖关系了,您可以自由声明一个静态属性。

我认为这听起来有些重复,但是如果您考虑实例属性与类属性之间的差异,那将是有道理的。


4
我将为您解释内部类的静态属性,但是正如@skaffman在对我的答案的注释中指出的那样,静态方法呢?似乎应该允许方法而不必强制将它们与任何实例分离。确实,在Java中,您可以在实例上调用静态方法(尽管它被认为是不良样式)。BTW:我问一个同事试图编译OP的代码为C#和它编译。因此,C#显然允许这样做,这表明OP想要执行的操作没有违反一些基本的OO原则。
Asaph,

2
发生的方法完全相同。这里的重点不是属性方法是否处于静态状态,而是其自身的内部类是实例“东西”的事实。我的意思是,这里的问题是在创建外部类之前,此类内部类的实例不存在。因此,如果什么都没有,那么将调度什么方法。您只会大吃一惊,因为首先需要一个实例。
OscarRyz

1
关于C#...好。OO无效只是因为C#允许这样做,我并不意味着它是错误的,但是C#包含几个范例,即使以一致性为代价,也可以简化开发工作(您必须在每个.NET版本中学习新知识),并且它允许这种以及其他种类的事物。我认为那是一件好事。如果社区觉得额外的功能很酷,那么C#将来可能会拥有它。
OscarRyz

2
@OscarRyz为什么需要内部类的实例才能使用其静态方法/字段?内部类中[use]静态方法的一个示例private helper方法。
Leonid Semyonov 2014年

1
使用时final,在Java内部类中允许使用静态字段。您如何解释这种情况?
Number945 '18

34

InnerClass不能拥有static成员,因为它属于的一个实例OuterClass。如果声明InnerClassstatic将其与实例分离,则代码将编译。

class OuterClass {
    static class InnerClass {
        static int i = 100; // no compile error
        static void f() { } // no compile error
    }
}

顺便说一句:您仍然可以创建的实例InnerClassstatic在这种情况下,允许在没有封闭实例的情况下发生这种情况OuterClass


6
InnerClass属于OuterClass实例它做。这两个阶级本身没有这种关系。为什么还不能使用静态方法的问题InnerClass仍然存在。
skaffman

9

实际上,如果静态字段是常量并且是在编译时编写的,则可以声明它们。

class OuterClass {
    void foo() {
        class Inner{
            static final int a = 5; // fine
            static final String s = "hello"; // fine
            static final Object o = new Object(); // compile error, because cannot be written during compilation
        }
    }
}

8
  1. class初始化顺序 是一个关键原因。

由于内部类依赖于封闭类/外部类的实例,因此需要在初始化内部类之前初始化外部类。
这是JLS关于类初始化的说明。 我们需要指出的是,如果

  • 使用由T声明的静态字段,并且该字段不是常量。

因此,如果内部类具有静态字段访问权限,这将导致初始化内部类,但是这不能确保封闭类被初始化。

  1. 这将违反一些基本规则您可以跳到最后一部分(至two cases),避免出现菜鸟味

关于的事情,有的时候,它的行为在各个方面都像普通的类,并且与Outer类相关联。static nested classnested classstatic

但是Inner class/的概念将与外部/封闭类相关联。请注意与实例关联的不是类。现在,与实例关联显然意味着(从实例变量的概念出发)它将存在于实例内部,并且在实例之间会有所不同。 non-static nested classinstance

现在,当我们使某些东西静态时,我们期望它会在加载类时被初始化,并且应该在所有实例之间共享。但是由于是非静态的,即使内部类本身(您现在肯定可以忘记内部类的实例)也没有与外部/封闭类的所有实例共享(至少在概念上),那么我们如何期望某个变量内部类的实例将在所有内部类实例之间共享。

因此,如果Java允许我们在静态嵌套类中使用静态变量。将有两种情况

  • 如果与内部类的所有实例共享,则将违反context of instance(实例变量)的概念。那是不。
  • 如果未与所有实例共享它,则将违反静态概念。再次没有。

5

这是我发现最适合此“限制”的动机:您可以将内部类的静态字段的行为实现为外部对象的实例字段; 因此,您不需要静态字段/方法。我的意思是,某个对象的所有内部类实例共享一个字段(或方法)。

因此,假设您想计算所有内部类实例,则可以这样做:

public class Outer{
    int nofInner; //this will count the inner class 
                  //instances of this (Outer)object
                  //(you know, they "belong" to an object)
    static int totalNofInner; //this will count all 
                              //inner class instances of all Outer objects
    class Inner {
        public Inner(){
            nofInner++;
            totalNofInner++;
        }
    }
}

2
但是问题是,在声明静态字段时允许它们的原因是什么final
安慰

如果您查看[ stackoverflow.com/a/1954119/1532220](上面的OscarRyzs答案):他的动机是,值不能与变量关联。当然,如果变量是最终变量,则可以很容易地知道要分配的值(必须知道)。
钢琴家2016年

2

简而言之,非静态内部类是外部类的实例变量,并且仅当在运行时创建外部类并在运行时创建外部类对象而在类加载时创建静态变量时才创建非静态内部类。因此,非静态内部类是运行时事物,这就是为什么静态而不是非静态内部类的一部分。

注意:将内部类始终像外部类的变量一样对待,它们可以像其他任何变量一样是静态的或非静态的。


但是内部类可以具有static final常量。
下雨


1

因为这将导致“静态”含义中的歧义。

内部类不能声明除编译时常量以外的静态成员。关于“静态”的含义可能会有歧义。这是否意味着虚拟机中只有一个实例?还是每个外部对象只有一个实例?语言设计师决定不解决此问题。

摘自Cay S. Horstmann的“ Core for不耐烦的Java SE 9”。Pg 90第2.6.3章


-1

我想这是为了保持一致性。尽管似乎没有任何技术限制,但是您将无法从外部访问内部类的静态成员,即,OuterClass.InnerClass.i因为中间步骤不是静态的。


但是内部类可以具有static final常量。
下雨
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.