【问题标题】:Do static variables and methods belong to java.lang.Class object of that class?静态变量和方法是否属于该类的 java.lang.Class 对象?
【发布时间】:2017-03-19 00:30:28
【问题描述】:

我已经阅读了很多关于这个主题的问题,其中许多人说静态内容(static methodsvariables)属于 java.lang.Class 对象(也称为永久代中的类级对象)和这背后的原因是

  1. 在Java规范中,我们可以发现“静态内容属于类的某种内部结构,它为所有对象所共有,存在于对象之外”。

如 Java 语言规范第 8.3.1.1 节所述

如果一个字段被声明为静态的,那么就只存在一个 字段,无论该类有多少实例(可能为零) 最终可能会被创建。静态字段,有时称为类 变量,在类初始化时体现(第 12.4 节)。

  1. java.lang.Class类的对象代表类的内部结构,它只有一个化身,对所有对象都是通用的。

  2. java.lang.Class 对象在加载和初始化类时在 Perm Gen 中创建。

  3. java.lang.Class 称为类级对象,静态变量称为类变量,因为静态变量是类级对象的状态。

  4. 在同步静态方法时,我们需要获取类级对象的锁,该对象又是java.lang.Class 实例。

如第 8.4.3.6 节所述:

对于类(静态)方法,与该类关联的监视器 使用方法类的对象。

从以上几点,我们可以断定静态内容属于java.lang.Class实例,但是为什么在任何规范中都没有写清楚呢?

还有如果class A { static int b; } 为什么b 不能通过A.class.b 访问?

那么如何证明静态变量和方法属于Class 对象?

如果静态内容不属于 Class 对象,那么它究竟属于哪里?为什么很多答案、博客和教程都提到它?

【问题讨论】:

  • 更多的是它们只有一个实例,它们不“属于”类,因为它们不属于任何一个对象

标签: java class oop static


【解决方案1】:

Java 语言规范的§8.3.1.1 声明如下:

如果一个字段被声明为static,那么无论最终创建多少个类的实例(可能为零),都只存在该字段的一个化身。 static 字段,有时称为类变量,在类初始化时体现(第 12.4 节)。

§8.4.3.6 说:

对于类(静态)方法,使用与方法类的Class 对象关联的监视器。

它们都没有指定应包含static 字段的对象。

正如你所说:

静态内容属于某种内部结构,所有对象共有的类,存在于对象之外”。

这包括Class 的实例。这些实例描述了部分内部结构,但不包含它们。

因此,很遗憾,您要证明的陈述是错误的。

【讨论】:

  • 这就是我问这个问题的原因,在任何规范(JVMS、JLS)中都没有证据证明它,但看起来静态内容属于类级别对象。例如,如果静态内容不属于类级对象,那么为什么我们需要对 Class 对象进行锁定以使静态方法同步?
  • 正如您从 JLS 中引用的“静态字段,有时称为类变量,在类初始化时体现(第 12.4 节)。”,类将被初始化意味着类级对象的 java.lang.Class 被创建。
  • 不,他们不是。静态方法的synchronization之所以使用Class对象,只是为了方便约定。对于每个加载的类只有一个实例的任何其他对象都将适合。幸运的是,JVM 已经有了这种对象——Class 对象。
  • 类的生命周期有两个不同的阶段:1) 类加载 2) 类初始化。 Class 实例在第一阶段创建,而静态字段在第二阶段初始化。
  • 肯定会首先创建对象(在类加载阶段),静态变量初始化将在稍后完成(在初始化阶段),但初始化将使用在类加载期间创建的对象完成类对象。
【解决方案2】:

您似乎将看似不同的问题作为一个问题提出。这可能是您困惑的主要原因。此外,在处理语言规范时,我们需要在名称方面保持精确,以便最大限度地减少混淆。

静态内容是网站组织中使用的术语。当您引用 静态内容 时,您似乎暗示了 Java 类的静态字段和方法(统称为 静态成员)。让我们使用术语静态成员来清楚。

从以上几点,我们可以断定静态内容属于java.lang.Class实例,但为什么在任何规范的任何地方都没有写清楚?

§15.11 尝试使用表达式Primary . Identifier 指定通过PrimaryName标识符 访问静态成员。

在最简单的情况下,规范说:Primary 的类型必须是引用类型 T,否则会发生编译时错误。这意味着静态成员将通过定义成员的类型的 name 来访问。

如果class A { static int b; } 为什么不能通过 A.class.b 访问 b?

出于与上述相同的原因,A.class 不是定义静态字段b 的引用type。类型是A,因此b 只能作为A.b 访问。

作为 Java 程序员,我们要感谢 Java 的设计者,让我们可以像 A.b 一样访问像 Ab 这样的静态成员,不是吗?

一个稍长一点的解释必须处理 Java 的反射能力,特别是类 java.lang.Class这是 Java 中唯一以类为实例的类。是的,java.lang.Class 的实例是类。因此,java.lang.Stringjava.lang.Class实例,就像字符串String s = "foo";java.lang.String 类的实例一样。程序:

public class Main {
  public static void main(String[] args) {
    String s = "foo";
    System.out.println(String.class instanceof Class);  // statically
    System.out.println(Class.class.isInstance(String.class)); // dynamically
    System.out.println(s instanceof String); // statically
    System.out.println(String.class.isInstance(s)); // dynamically
  }
}

打印:

true
true
true
true

一个问题可能仍然存在,那就是A.class 到底是什么? spec defines it 作为类文字

类字面量计算为由当前实例的类的定义类加载器(第 12.2 节)定义的命名类型(或 void)的 Class 对象。

所以,任何可以在 String.class 上调用的方法(请记住,String.class 是这里的接收者)是在 其类上定义的方法,即类 @987654345 @。因此,您可以像调用str.length() 一样执行String.class.isInstance(str),其中strString 的一个实例(而length() 是在String 类上定义的所谓实例方法;实例方法和类方法的区别在这里无关紧要)。

那么如何证明静态变量和方法属于 Class 对象呢?

从上面的 1) 开始,即它们只能通过声明它们的类型名称(即 classinterface)访问。这里有一个小皱纹。静态成员也可以通过定义它们的类型的实例来访问,下面的示例可能会让您感到惊讶。这个程序是做什么的:

public class Main {
  static int foo = 22;

  public static void main(String[] args) {
      Main m = null;
      System.out.println(m.foo); // ** Don't Do This **
  }
}

不,它不会抛出 NullPointerException。通过定义静态成员的类的实例,允许(尽管不鼓励)访问静态成员。由于javac 不像其他人那样理解 Java 语言规范,它会做规范想要做的事情。

但请注意,如果您使用定义类型或该类型的实例,它将获取完全相同的静态字段值并在运行时调用完全相同的静态方法。这足以证明静态成员确实是定义它们的类型(或类)的特征。

【讨论】:

  • Kedar 感谢您的回答,我知道我们可以从实例访问静态成员,并且我知道您提到的其他内容,即 String.class 实际上是 java.lang.Class 的对象 但以上所有解释都没有给我任何关于为什么静态成员不属于类级别对象的线索
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-13
  • 2013-10-10
  • 2021-06-08
相关资源
最近更新 更多