【问题标题】:Java class instantiation clarificationJava类实例化说明
【发布时间】:2015-01-16 19:16:42
【问题描述】:

给定两个 .java 文件:

// Car.java
class Car {
    static int counter = 0; // Class field
    Car () { counter++;}
}

// Cars.java
public class Cars{
    public static void main(String[] args){
        System.out.println(Car.counter);  // Does this instantiate a Car?
    }
}

我正在学习 Java,我只是想在这里准确(迂腐?)。

编译,然后执行java Cars 会产生0 的正确值。由于 Car 还没有被实例化(或者已经实例化了?),你会说这段代码会发生什么?我的意思是,我可以看到 Cars.class 使用了 Car.class,但我无法形成正确的句子来描述它为什么起作用。对于新手,您如何描述使类字段“栩栩如生”的概念?

【问题讨论】:

  • 也许我应该澄清我的问题。好的,所以我知道不会实例化 Car 对象。我进一步了解了类字段和实例字段之间的区别。我不清楚的是,由于没有实例化对象,汽车是如何“出现”以返回 0 的值的?也许我只是想太多了。
  • counter 是一个类变量。您不需要对象来访问 类变量

标签: java class field instantiation


【解决方案1】:

不,不会。 Car.counter 用于获取Car 中对counter 的引用。

关键字static 表示字段counter 是属于整个类的东西,而不是单独的对象实例。因此,counter 字段对于Car 的所有实例具有相同的值。

【讨论】:

  • 值得一提的是static修饰符:)
  • @JorgeCampos,感谢您的提示,我对此进行了详细说明;)
  • 这是一个很好的答案,虽然有点偏离我的问题的中心。谢谢尼尔斯!
【解决方案2】:

我假设您有两个文件 Car.java 和 Cars.java,对应于您给定的代码。 现在,当您编译 Cars.java 时,它还会自动创建 Car.class 以及 Cars.class 事实上,这个 Car.class 是运行 Cars.class 程序所必需的。如果你删除前者,然后尝试运行 Cars 程序,你会得到以下异常:

Exception in thread "main" java.lang.NoClassDefFoundError: Car
        at Cars.main(Cars.java:3)
Caused by: java.lang.ClassNotFoundException: Car
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at java.net.URLClassLoader.findClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
        at java.lang.ClassLoader.loadClass(Unknown Source)
        ... 1 more

注意这里的异常来源

java.lang.ClassLoader.loadClass

Java 类加载器是 JRE 的一部分,可将 Java 类动态加载到 JVM 中。当一个类被初始化时,所有的静态变量实例都放在堆上。由于您的 Cars.class 程序引用了 Car.class 程序的静态变量,因此它也将其加载到 JVM 中。

请注意,加载与实例化完全不同。短语“实例化一个类”意味着创建一个类的“实例”。为了实例化一个类,我们使用 new 运算符:

Car myCar = new Car();

new 运算符通过为新对象分配内存并返回对该内存的引用来实例化一个类。这里也将动态分配实例变量的内存(根据需要)。请注意,每个实例都将被分配一个新的内存空间。但是,它们仍将共享相同的静态分配变量。

因此,在您的程序中,您并没有实例化 Car 类,但您仍将其加载到 JVM 中。

【讨论】:

  • 宾果游戏!!这个解释巩固了我的理解。谢谢!!
【解决方案3】:

来自Oracle Docs

有时,您希望拥有所有对象共有的变量。这是通过static 修饰符完成的。在声明中具有static 修饰符的字段称为静态字段类变量。它们与类相关联,而不是与任何对象相关联。类的每个实例共享一个类变量,该变量位于内存中的一个固定位置。任何对象都可以更改类变量的值,但也可以在不创建类实例的情况下操作类变量。

所以,回答你的问题。 NO - 访问静态字段时,您实例化一个类。您实际实例化对象的方式是调用它的constructor。使用关键字new调用构造函数,构造函数名称与类名相同。

Car c = new Car(); // instantiation via constructor

为了描述你的情况:

Cars 类使用来自Car 类的类变量。类变量与类关联,并在类的每个实例之间共享。

【讨论】:

    【解决方案4】:

    您正在递增 Car consyructor 中的计数器。 这意味着仅当您使用“new Car”创建 Car 的新实例时,计数器才会增加。该代码不会实例化任何方式计数器为 0 的 Car。

    【讨论】:

      【解决方案5】:

      简单地说:

      您可以将类视为对象的描述。类的实例是对象,它们遵循类文件中给出的描述规则。

      但描述本身也可以被视为遵循描述规则的对象。这些对象是 Class 类的实例 - 描述描述的描述 ;-) :

      System.out.println(Car.class instanceof Class); // true
      System.out.println(Class.class instanceof Class); // true
      

      因此您可以看到类具有表示形式(例如 Car.class),并且表示形式可以具有字段和方法 - static 的那些。

      简单地说;-)

      【讨论】:

        【解决方案6】:

        static 关键字表示整个类的单个副本,它不属于该类的任何特定实例。当您的类第一次加载到 JVM 时,类中的静态字段由类初始化器初始化。

        【讨论】: