繁体   English   中英

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

[英]Why can't we have static method in a (non-static) inner class (pre-Java 16)?

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

public class Foo {
    class Bar {
        static void method() {} // Compiler error
    }
}

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

public class Foo {
    static class Bar { // now static
        static void method() {}
    }
}

在 Java 16+ 中,这两个都是有效的。

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

在非静态内部类中允许静态方法没有多大意义; 你将如何访问它? 您不能在不通过外部类实例的情况下访问(至少最初是)非静态内部类实例。 没有纯粹的静态方法来创建非静态内部类。

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

Outer.test();

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

Outer.Inner.innerTest();

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

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

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

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

首先,您应该了解 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是非静态的。 这种解释了为什么不允许使用静态方法(尽管您可以提出这样的论点,即只要静态方法不引用封闭对象,您就可以允许它们),以及为什么不能有非最终静态字段(如果来自不同对象的非静态内部类的实例共享“静态状态”,那将是违反直觉的)。 这也解释了为什么最终字段允许的(只要他们不引用封闭的对象)。

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

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

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

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

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

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

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

甲骨文的解释是肤浅的,而且是随波逐流。 由于在内部类中没有抢占静态成员的技术或语法原因(在其他语言如 C# 中是允许的),Java 设计者的动机可能是概念品味和/或技术方便问题。

这是我的猜测:

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

静态成员(不是面向对象的)的本质与内部类(面向对象)的依赖实例的精神相冲突,因为您可以引用/调用没有外部类实例的内部类的静态成员使用限定的内部类名。

特别是静态变量可能会以另一种方式冒犯:与外部类的不同实例相关联的内部类的两个实例将共享静态变量。 由于变量是状态的一个组成部分,两个内部类实例实际上将独立于它们关联的外部类实例共享状态。 并不是说静态变量以这种方式工作是不可接受的(我们在 Java 中接受它们作为对 OOP 纯度的明智妥协),但可以说通过允许它们在其实例已经与外部类实例耦合的内部类中存在更深层次的冒犯按设计。 禁止内部类中的静态成员以支持依赖实例的精神可以获得额外的好处,即抢占这种更深层次的 OOP 攻击。

另一方面,静态常量不会导致此类冒犯,静态常量没有意义地构成状态,因此是允许的。 为什么不禁止静态常量以最大程度地与依赖实例的精神保持一致? 也许是因为常量不需要占用比必要更多的内存(如果它们被迫是非静态的,那么它们会被复制到每个内部类实例中,这可能会造成浪费)。 否则我无法想象异常的原因。

它可能不是坚如磐石的推理,但在 IMO 中,它最能理解甲骨文对此事的粗略评论。

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

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

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

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

这就是为什么设计师可能决定根本不处理这个问题。

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

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

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

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

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

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

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

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();
        }
    }
}

简短回答:大多数程序员对作用域如何工作的心理模型不是 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 必须要么拆分静态范围和实例范围,支持分支和重新加入范围层次结构,要么必须扩展其简单的布尔“静态上下文”思想来更改以跟踪所有级别的上下文类型当前范围内的嵌套类。

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

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

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

像下面的代码,

public class Outer {

  class Inner {

    public static void method() {

    }

  }

}

可以这样写

public class Outer {

  void method() {

   }

   class Inner {


  }

}

所以在我看来,不要让代码复杂化,java 设计者不允许这个功能,或者我们可能会在未来的版本中看到这个功能有更多的特性。

用 Java 语言设计者自己的话来说

自从嵌套类首次引入 Java 以来,内部的嵌套类声明就被禁止声明静态成员......它简化了语言解析和验证对范围内变量、方法等的引用的任务。

从来没有任何特别宏大的概念或哲学理由来禁止这一点。

为语言简化事情被认为不足以继续维持这一限制。 随着 Java 16 中记录的引入,他们决定放宽限制。

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

试着把类当成一个普通的字段,你就会明白。

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

将内部类成员设为静态是没有用的,因为您一开始就无法访问它们。

想想看,要访问静态成员,您可以使用 className.memberName ,在我们的例子中,它应该是类似 outerclassName.innerclassName.memberName 的东西,现在你明白为什么内部类必须是静态的了......

您可以使用静态嵌套类的静态方法。 例如

public class Outer {

  public static class Inner {

    public static void method() {

    }
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM