为什么我们不能在(非静态)内部类中使用静态方法?


142

为什么我们不能在非静态内部类中使用静态方法?

如果我将内部类设为静态,则它可以工作。为什么?


4
因为现在Java是那个古老的COBOL :)
ses

底线是:因为他们尚未实现它。
intrepidis

1
“非静态内部”是一个重言式。
罗恩侯爵

如果您不想将内部类暴露给其他人并希望它包含静态方法,则可以将修饰符“ private”和“ static”都放在内部类上。
威利(Willie Z)

内部类根据定义不是静态的。您可以有一个静态的嵌套/成员类和一个非静态的嵌套/成员类。后者也称为内部类。(参考:JLS的8.1.3节指出“ 内部类是未显式或隐式声明的嵌套类static。”)
Erwin Bolwidt

Answers:


111

因为内部类的实例与外部类的实例隐式关联,所以它本身不能定义任何静态方法。由于静态嵌套类无法直接引用其封闭类中定义的实例变量或方法,因此只能通过对象引用使用它们,因此在静态嵌套类中声明静态方法是安全的。


7
我知道内部类与其外部类的实例相关联,并且我知道我们能够在内部类中声明静态成员是没有用的,但是我仍然在问为什么内部类不能声明静态成员?
Kareem

15
在C ++中可以使用,因此这是Java语言中的错误。
工业抗抑郁剂

39
错误一词...我认为这个词不代表您认为的意思。
塞斯·纳尔逊

24
一个更合适的词是“讨厌做个混蛋”。不明白为什么Java不允许这样做。有时,我希望内部类使用父类的属性,但保留静态方法以提高命名空间。这有天生的问题吗?:(
Angad

6
究竟。我想编写一个实用程序内部类。它的某些方法将从访问外部类中受益,因此我不能使其变为静态,但是其某些方法只是实用程序函数。为什么我不能A.B.sync(X)(甚至在A内)打电话B.sync(x)
Edward Falk 2015年

44

在非静态内部类中允许使用静态方法没有什么意义;您将如何访问它?如果不通过外部类实例,则不能访问(至少最初是)非静态内部类实例。没有创建纯静态内部类的纯静态方法。

对于外部类Outer,可以访问如下所示的静态方法test()

Outer.test();

对于静态内部类Inner,可以按如下方式访问其静态方法innerTest()

Outer.Inner.innerTest();

但是,如果Inner不是静态的,那么现在没有引用该方法的纯静态方法innertest。非静态内部类绑定到其外部类的特定实例。函数与常量的不同之处在于,以对函数的引用不是Outer.Inner.CONSTANT唯一的方式保证对它的引用是明确的Outer.Inner.staticFunction();。假设您有Inner.staticFunction()一个调用getState(),该调用在Outer。如果尝试调用该静态函数,则现在对Inner类的引用不明确。也就是说,您在内部类的哪个实例上调用静态函数?这很重要。请参见,由于隐式引用了外部对象,因此没有真正的静态方法可以引用该静态方法。

保罗·贝洛拉(Paul Bellora)是正确的,因为语言设计师可以允许这样做。然后,他们将不得不谨慎地禁止在非静态内部类的静态方法中访问对外部类的隐式引用。此时,如果不能(静态地除外)引用外部类,那么作为内部类的值是什么?如果静态访问很好,那为什么不将整个内部类声明为静态呢?如果仅将内部类本身设置为静态,那么您将没有对外部类的隐式引用,并且不再具有这种歧义。

如果实际上在非静态内部类上需要静态方法,则可能需要重新考虑设计。


6
-1我不同意您在这里所采取的角度。当然,我们可以参考内部类型,例如Outer.Inner i = new Outer().new Inner();此外,内部类允许声明静态常数根据JLS§15.28。
Paul Bellora

2
是的,内部类可以声明静态常量。这与静态方法无关!虽然您可以非静态地引用静态方法,但不建议这样做。所有代码质量工具都有理由抱怨这种参考。而你错过了我的观点。我从未说过没有办法引用静态内部类。我说过,没有静态方法可以引用非静态外部类的内部类的静态方法。因此,没有正确的方法来引用它。
艾迪

25
“在非静态内部类中允许使用静态方法没有什么意义;您将如何访问它?” 您可以拨打电话Outer.Inner.staticMethod(),就像可以访问一样Outer.Inner.CONSTANT。“如果不通过外部类实例,则无法访问...非静态内部类实例。” 为什么需要实例?您不需要Outer调用的实例Outer.staticMethod()。我知道这很挑剔,但我的意思是,用这种方式来描述您的答案没有任何意义。恕我直言,语言设计师可以根据需要允许它。
Paul Bellora

1
之间的区别Outer.Inner.CONSTANTOuter.Inner.staticMethod()是一个常数参考已经没有隐含地引用的实例机会OuterInner被实例化。所有引用Outer.staticMethod()共享相同的确切状态。所有引用Outer.Inner.CONSTANT共享相同的确切状态。但是,对的引用Outer.Inner.staticMethod()是模棱两可的:由于在的每个实例中都隐式引用了外部类,因此“静态”状态并不是真正的静态Inner。没有一种真正明确,静态的方式来访问它。
艾迪(Eddie)2013年

3
@Eddie您不能在静态方法中引用实例字段,因此与无法引用隐式实例字段没有冲突Outer.this。我确实同意Java语言设计者的观点,即没有理由在内部类中允许使用静态方法或非最终静态字段,因为内部类中的所有内容都应位于封闭类的上下文内。
Theodore Murdock 2014年

20

我有一个理论,可能正确也可能不正确。

首先,您应该了解有关Java内部类如何实现的一些知识。假设您有这个课程:

class Outer {
    private int foo = 0;
    class Inner implements Runnable {
        public void run(){ foo++; }
    }
    public Runnable newFooIncrementer(){ return new Inner(); }
}

当您对其进行编译时,生成的字节码将看起来像您编写了以下内容:

class Outer {
    private int foo = 0;
    static class Inner implements Runnable {
        private final Outer this$0;
        public Inner(Outer outer){
            this$0 = outer;
        }
        public void run(){ this$0.foo++; }
    }
    public Runnable newFooIncrementer(){ return new Inner(this); }
}

现在,如果我们确实允许在非静态内部类中使用静态方法,则您可能想要做这样的事情。

class Outer {
    private int foo = 0;
    class Inner {
        public static void incrFoo(){ foo++; }
    }
}

...看起来相当合理,因为Inner该类在每个Outer实例中似乎都有一个化身。但是,正如我们在上面看到的,非静态内部类实际上只是静态“内部”类的语法糖,因此最后一个示例大约等于:

class Outer {
    private int foo = 0;
    static class Inner {
        private final Outer this$0;
        public Inner(Outer outer){
            this$0 = outer;
        }
        public static void incrFoo(){ this$0.foo++; }
    }
}

...这显然this$0是行不通的,因为它是非静态的。这种解释说明了为什么不允许使用静态方法(尽管您可以声明只要不引用封闭对象,就可以允许使用静态方法),以及为什么不能使用非最终静态字段(如果来自不同对象的非静态内部类的实例共享“静态”,那将是违反直觉的。这也解释了为什么最终场允许的(只要他们不引用封闭的对象)。


7
但这只是一个普通的“试图从静态上下文中尝试访问非静态变量”类型错误-与顶级静态方法尝试访问其自身类的实例变量没有什么不同。
劳伦斯·多尔

2
我喜欢这个答案,因为它实际上解释了为什么即使从句法上看似乎不可能,但从技术上讲却是不可能的。
LoPoBo

@gustafc,我认为这是一个很好的解释。但是正如劳伦斯指出的那样,这只是一个失败,因为对foo的引用不是静态的。但是,如果我想编写public static double sinDeg(double theta) { ... }一个内部数学实用程序类怎么办?
Edward Falk 2015年

6

唯一的原因是“不是必须的”,那么为什么还要去支持它呢?

从句法上讲,没有理由禁止内部类具有静态成员。尽管的一个实例Inner与的一个实例相关联Outer,但仍然可以使用Outer.Inner.myStatic引用的一个静态成员Inner如果java决定这样做。

如果您需要在的所有实例之间共享某些内容Inner,则可以将它们Outer作为静态成员放入。这并不比您在中使用静态成员更糟Inner,在该静态成员中Outer仍然可以访问任何私有成员Inner(不会提高封装性)。

如果需要Inner在一个outer对象创建的所有实例之间共享某些内容,则将它们Outer作为普通成员放入类中更有意义。

我不同意“静态嵌套类几乎只是顶级类”的观点。我认为最好将静态嵌套类/内部类视为外部类的一部分,因为它们可以访问外部类的私有成员。外部阶级的成员也是“内部阶级的成员”。因此,不需要在内部类中支持静态成员。一个普通/静态的外部成员就足够了。


内部类也不是必须的。但是,由于该语言确实提供了内部类,因此应该提供它们的完整且有意义的实现。
intrepidis

4

来自:https : //docs.oracle.com/javase/tutorial/java/javaOO/nested.html

与实例方法和变量一样,内部类与其所在类的实例相关联,并且可以直接访问该对象的方法和字段。另外,由于内部类与实例相关联,因此它本身不能定义任何静态成员。

甲骨文的解释是肤浅的。由于没有技术或语法上的理由可以抢占内部类中的静态成员(在C#等其他语言中是允许的),因此Java设计人员的动机很可能是概念上的喜好和/或技术上的便利。

这是我的推测:

与顶级类不同,内部类是实例相关的:内部类实例与其每个外部类的实例相关联,并且可以直接访问其成员。这是在Java中使用它们的主要动机。用另一种方式表示:内部类是为了在外部类实例的上下文中实例化。如果没有外部类实例,则内部类的可用性不应超过外部类的其他实例成员。让我们将其称为依赖实例的精神内部类。

静态成员(非面向对象)的本质与内部类(面向对象)的实例相关精神冲突,因为您可以通过以下方式引用/调用内部类的静态成员而无需外部类实例:使用合格的内部类名称。

特别是静态变量可能会以另一种方式得罪:与外部类的不同实例相关联的内部类的两个实例将共享静态变量。由于变量是状态的组成部分,因此,两个内部类实例实际上将共享状态,而与它们所关联的外部类实例无关。并不是说静态变量以这种方式工作是不可接受的(我们在Java中接受它们是对OOP纯度的明智妥协),但可以通过允许它们进入实例已经与外部类实例结合在一起的内部类来进行更深层次的攻击通过设计。禁止内部类中的静态成员支持实例相关的精神,从而获得了抢先这种更深层次的OOP进攻的额外好处。

另一方面,静态常量不会带来这样的冒犯,静态常量不会有意义地构成状态,因此这些常量是允许的。为什么不为了与实例相关的精神最大化一致性而禁止使用静态常量?也许是因为常量不需要占用比必要更多的内存(如果被迫成为非静态常量,则它们将被复制到每个内部类实例中,这可能是浪费的)。否则我无法想象发生异常的原因。

这可能不是固执的推理,但IMO可以使Oracle在此问题上的粗略表述最有意义。


3

简短的答案:大多数程序员关于范围如何工作的思维模型不是javac使用的模型。匹配更直观的模型将需要对javac的工作方式进行重大更改。

内部类中需要静态成员的主要原因是为了代码清洁-仅内部类使用的静态成员应该存在于内部,而不是必须放置在外部类中。考虑:

class Outer {
   int outID;

   class Inner {
      static int nextID;
      int id = nextID++;

      String getID() {
         return outID + ":" + id;
      }
   }
}

考虑使用不合格标识符“ outID”时getID()中发生的情况。该标识符出现的范围如下所示:

Outer -> Inner -> getID()

在这里,再次因为这正是javac的工作方式,所以作用域的“外部”级别包括外部的静态成员和实例成员。这很令人困惑,因为通常会告诉我们将类的静态部分视为范围的另一个级别:

Outer static -> Outer instance -> instanceMethod()
         \----> staticMethod()

以这种方式思考,当然staticMethod()只能看到Outer的静态成员。但是,如果那是javac的工作方式,则在静态方法中引用实例变量将导致“名称无法解析”错误。真正发生的是在范围内找到了该名称,但是随后进行了额外的检查,并确定该名称是在实例上下文中声明的,并且是从静态上下文中引用的。

好的,这与内部类有什么关系?天真的,我们认为内部类没有静态作用域是没有原因的,因为我们正在描绘作用域,如下所示:

Outer static -> Outer instance -> Inner instance -> getID()
         \------ Inner static ------^

换句话说,内部类中的静态声明和外部类中的实例声明都在内部类的实例上下文中,但它们实际上都不嵌套在另一个中。两者都嵌套在外部的静态范围内。

这不是javac的工作方式-静态成员和实例成员都有一个范围级别,并且范围始终严格嵌套。通过将声明复制到子类中而不是分支并搜索超类范围来实现继承。

为了支持内部类的静态成员,javac将不得不拆分静态范围和实例范围,支持分支和重新合并范围层次结构,或者它必须扩展其简单的布尔“静态上下文”思想以进行更改以在所有级别上跟踪上下文的类型。当前范围内的嵌套类。


我认为,允许非静态内部类具有非恒定静态成员的一个更根本的困难是,程序员声明此类成员可能打算将它们绑定到外部类的实例,或者让它们真正是静态的。如果可以合理地将一个构造(如果合法)指定为两个不同事物中的任何一个,并且可以用其他明确的方式表示这两个事物,那么将构造指定为非法通常比将其指定为非法更好。有任何意义。
超级猫

3

为什么我们不能在非静态内部类中使用静态方法?

注意:非静态嵌套类称为内部类,因此您没有non-static inner class这样的类。

没有对应的外部类实例,内部类实例将不存在。内部类除编译时间常数外不能声明静态成员。如果允许,那么关于的含义就会含糊不清static。在这种情况下,可能会有一些混乱:

  1. 这是否意味着VM中只有一个实例?
  2. 还是每个外部对象只有一个实例?

这就是设计者可能决定完全不处理此问题的原因。

如果我将内部类设为静态,则它可以工作。为什么呢

同样,您不能将内部类设为静态,而是可以将静态类声明为嵌套的。在这种情况下,此嵌套类实际上是外部类的一部分,并且可以具有静态成员而没有任何问题。


3

这个话题引起了很多人的关注,但我仍将尝试用最简单的术语进行解释。

首先,参考http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.1,在首次出现/调用之前立即初始化类或接口static关键字开头的任何成员的。

  1. 因此,如果我们在内部类中放置一个静态成员,它将导致内部类的初始化,而不必进行外部/封闭类的初始化。因此,我们妨碍了类的初始化顺序。

  2. 还请考虑以下事实:非静态内部类与封闭/外部类的实例相关联。因此,与实例相关联意味着内部类将存在于外部类实例内部,并且实例之间将有所不同。

为简化这一点,为了访问静态成员,我们需要一个Outer类的实例,从中我们将再次需要创建一个非静态内部类的实例。静态成员不应该绑定到实例,因此您会收到编译错误。


2

内部类与静态嵌套类完全不同,尽管两者的语法相似。静态嵌套类仅是一种分组方法,而内部类与外部类具有很强的关联性,并且可以访问其所有值。您应该确定为什么要使用一个内部类,然后自然而然地就要使用哪个内部类。如果您需要声明一个静态方法,则可能是您仍然想要的静态嵌套类。


Benedikt,当您说“静态嵌套类只是一种分组方式”时,您是什么意思?
Ankur,5:

0

假设有两个外部类实例且它们都实例化了内部类,现在如果内部类具有一个静态成员,则它将仅将该成员的一个副本保留在堆区域中。在这种情况下,外部类的两个对象都将引用此对象单个副本,并且它们可以一起更改。这可能会导致“脏读取”情况,从而防止Java应用此限制。支持此参数的另一个优点是Java允许在这里使用final静态成员,其值不能为从任何一个外部类对象更改。如果我错了,请让我。


0

首先,为什么有人要在非静态内部类中定义静态成员?答案是,这样外部类成员可以只使用具有内部类名称的静态成员,对吗?

但是对于这种情况,我们可以直接在外部类中定义成员。它将与外部类实例中内部类的所有对象相关联。

像下面的代码,

public class Outer {

  class Inner {

    public static void method() {

    }

  }

}

可以这样写

public class Outer {

  void method() {

   }

   class Inner {


  }

}

因此,在我看来,不要使代码复杂化,Java设计器不允许使用此功能,否则,我们可能会在将来的版本中看到此功能,其中包含更多功能。


0

尝试将类视为普通字段,您将了解。

//something must be static. Suppose something is an inner class, then it has static keyword which means it's a static class
Outer.something 

-1

将内部类成员设置为静态是没有用的,因为您将无法首先访问它们。

想想看,要访问一个静态成员,您可以使用className.memberName,在我们的例子中,它应该类似于externalclassName.innerclassName.memberName,现在您知道为什么innerclass必须是静态的了。


-2

允许您在静态嵌套类上使用静态方法。例如

public class Outer {

  public static class Inner {

    public static void method() {

    }
  }
}
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.