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

时间:2009-06-10 11:52:05

标签: java class static inner-classes

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

如果我使内部类静态,它就可以工作。为什么?

15 个答案:

答案 0 :(得分:108)

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

答案 1 :(得分:44)

在非静态内部类中允许静态方法没有多大意义;你怎么去访问它?您无法访问(至少最初)非静态内部类实例而无需通过外部类实例。创建非静态内部类没有纯粹的静态方法。

对于外部类Outer,您可以像这样访问静态方法test()

Outer.test();

对于静态内部类Inner,您可以像这样访问其静态方法innerTest()

Outer.Inner.innerTest();

但是,如果Inner不是静态的,那么现在没有纯粹的静态方法来引用方法innertest。非静态内部类与其外部类的特定实例相关联。函数与常量不同,因为对Outer.Inner.CONSTANT的引用保证是明确的,函数调用Outer.Inner.staticFunction();不是。假设您Inner.staticFunction()调用了getState()Outer定义了{{1}}。如果您尝试调用该静态函数,则现在对Inner类有一个不明确的引用。也就是说,你在内部类的哪个实例上调用静态函数?这很重要。请参阅,由于对外部对象的隐式引用,没有真正的静态方法来引用该静态方法。

保罗·贝洛拉(Paul Bellora)说语言设计师可以允许这样做是正确的。然后,他们必须小心地禁止对非静态内部类的静态方法中对外部类的隐式引用的任何访问。在这一点上,如果你不能引用外部类,除了静态之外,这是一个内部类的值是什么?如果静态访问没问题,为什么不将整个内部类声明为静态呢?如果你只是让内部类本身是静态的,那么你没有对外部类的隐式引用,并且你不再有这种歧义。

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

答案 2 :(得分: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是非静态的。这种情况解释了为什么不允许使用静态方法(尽管你可以设置允许静态方法的参数,只要它们不引用封闭对象),以及为什么你不能拥有非最终静态字段(如果来自不同对象的非静态内部类的实例共享“静态”,那将是违反直觉的。它还解释了为什么允许最终字段 (只要它们不引用封闭对象)。

答案 3 :(得分:6)

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

从语法上讲,没有理由禁止内部类具有静态成员。虽然Inner的实例与Outer的实例相关联,但如果java决定这样做,则仍然可以使用Outer.Inner.myStatic来引用Inner的静态成员。 / p>

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

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

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

答案 4 :(得分: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的静态成员和实例成员。这很令人困惑,因为我们通常会被告知将类的静态部分视为范围的另一个级别:

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

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

好的,这与内部课程有什么关系?天真地,我们认为内部类没有理由不具有静态范围,因为我们正在描绘范围如下:

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

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

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

为了支持内部类的静态成员,javac必须分割静态和实例作用域支持分支和重新加入作用域层次结构,否则它必须将其简单的布尔“静态上下文”思想扩展到更改以跟踪当前范围内所有级别的嵌套类的上下文类型。

答案 5 :(得分:3)

  

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

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

如果没有相应的外部类实例,内部类实例就不存在。内部类不能声明除编译时常量之外的静态成员。如果允许,那么static的意义就会模糊不清。在这种情况下会出现一些混淆:

  1. 这是否意味着VM中只有一个实例?
  2. 每个外部对象只有一个实例?
  3. 这就是为什么设计师可能会决定不处理这个问题。

      

    如果我使内部类静态,它就可以工作。为什么?

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

答案 6 :(得分:3)

这个话题引起了很多人的注意,我仍然会用最简单的术语来解释。

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

  1. 因此,如果我们在内部类中使用静态成员,它将导致内部类的初始化,而不一定是外部/封闭类。因此,我们阻碍了类初始化序列。

  2. 还要考虑一个事实,即非静态内部类与封闭/外部类的实例相关联。因此,与实例关联意味着内部类将存在于外部类实例中,并且在实例之间将是不同的。

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

答案 7 :(得分:3)

发件人:https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

  

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

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

这是我的推测:

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

静态成员(非面向对象)的本质与内部类(面向对象)的依赖实例的精神冲突,因为您可以引用/调用静态成员通过使用合格的内部类名称来定义没有外部类实例的内部类。

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

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

这可能不是一成不变的推理,但海事组织(IMO)可以最充分地理解Oracle在此问题上的粗略表述。

答案 8 :(得分:2)

内部类与静态嵌套类完全不同,尽管两者在语法上都相似。静态嵌套类只是一种分组方式,而内部类具有强大的关联 - 并且可以访问其外部类的所有值。您应该确定为什么要使用内部类,然后它应该是非常自然的,您必须使用它。如果你需要声明一个静态方法,它可能是你想要的静态嵌套类。

答案 9 :(得分:1)

JDK16 添加记录的工作还提到静态方法和字段现在可以与内部类一起使用,甚至允许 main() 启动类。

例如这在JDK16中编译和运行,并且可以选择main()作为java Outerjava Outer$Inner运行:

public class Outer {
    public static void main(String[] args) {
        System.out.println("Outer class main xxx="+Inner.xxx+" nnn="+(++Inner.nnn)+" iii="+(--iii));
        aaa();
        Inner.zzz();
    }
    public static void aaa() {
        System.out.println("aaa() nnn="+(++Inner.nnn)+" iii="+(--iii));
    }
    public static int iii = 100;

    class Inner {
        public static final String xxx= "yyy";
        public static int nnn = 0;

        public static void zzz() {
            System.out.println("zzz() "+" nnn="+(++nnn)+" iii="+(--iii));
        }
        public static void main(String[] args) {
            System.out.println("Inner class main xxx="+xxx+" nnn="+(++nnn)+" iii="+(--iii));
            zzz();
            aaa();
        }
    }
}

答案 10 :(得分:0)

假设有两个外部类和实例的实例。它们都有实例化的内部类。现在如果内部类有一个静态成员,那么它只会在堆区域中保留该成员的一个副本。在这种情况下,外部类的两个对象都将引用这个单个副本&它们可以一起改变它。这可能导致“脏读”情况因此阻止这个Java已经应用了这个限制。支持这个论点的另一个强点是java允许这里的最终静态成员,那些值不能从任何一个改变外类对象。 如果我错了,请告诉我。

答案 11 :(得分:0)

首先,为什么有人想在非静态内部类中定义静态成员?答案是,所以外部类成员只能使用那些带有内部类名的静态成员,对吗?

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

如下面的代码,

public class Outer {

  class Inner {

    public static void method() {

    }

  }

}

可以像这样写

public class Outer {

  void method() {

   }

   class Inner {


  }

}

因此,在我看来,不要使代码复杂化,java设计师不允许使用此功能,或者我们可能会在将来的版本中看到此功能以及更多功能。

答案 12 :(得分:0)

尝试将该类视为普通字段,然后您将理解。

exit 0

答案 13 :(得分:-1)

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

想想这个,访问一个静态成员你使用className.memberName ,,在我们的例子中,它应该像outerclassName.innerclassName.memberName ,,,现在你明白为什么innerclass必须是静态的.... / p>

答案 14 :(得分:-2)

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

public class Outer {

  public static class Inner {

    public static void method() {

    }
  }
}