Java 为什么可以在类的定义中实例化类?

Java 为什么可以在类的定义中实例化类?,java,Java,一位同事(对Java非常陌生)今天来了,问了一个看起来很简单的问题。不幸的是,我做了一件非常糟糕的事,试图向他解释这件事。他有一本书有一个小代码,看起来像这样: class XCopy { public static void main(String[] args) { XCopy x = new XCopy(); // 1 x.doIt(); } public void doIt() { // Some code...

一位同事(对Java非常陌生)今天来了,问了一个看起来很简单的问题。不幸的是,我做了一件非常糟糕的事,试图向他解释这件事。他有一本书有一个小代码,看起来像这样:

class XCopy {

    public static void main(String[] args) {
        XCopy x = new XCopy(); // 1
        x.doIt();
    }

    public void doIt() {
        // Some code...
    }
}
他在第1行被搞糊涂了。他想知道的是为什么可以在类XCopy的定义中创建一个新的XCopy实例。他认为这会产生某种正向参考错误。毕竟,我们还没有完成XCopy类的声明,那么我们如何创建一个呢

我当然知道这是一个有效的密码,但当我试图向他解释时,我发现自己在答案上结结巴巴,恐怕我让他比他开始时更加困惑。我想听听其他的解释,解释一下为什么会这样


有什么想法吗?为什么可以在类本身的定义中实例化类的实例?

因为主方法是静态的。通过静态,这意味着该方法不属于该类的任何特定实例。也就是说,无需创建实例即可访问它


因此,为了调用非静态的
doIt
方法,必须创建保存它的类的实例。

只要第一次“提到”这个类,jvm就会加载它。那么就没有什么可以阻止实例化了-它已经被加载了

它不是C/C++意义上的前向引用。您的主要方法是将类引用为其自身上下文中的类型。你没有“领先”任何东西

main是静态的这一事实与此无关,因为即使对于非静态方法,它仍然有效:

public class Foo
{
   private String x;

   public Foo(String x) { this.x = x; }
   public Foo(Foo f) { this.x = f.x; }  // copy constructor; still compiles fine, even without static
}

一个区别是编译和链接。C/C++有单独的编译和链接步骤。Java有一个类加载器。我认为编译成字节码并在运行时使用类加载器根据需要加载是Java和C/C++之间的一个细微差别,这解释了为什么不需要前向引用思想,但我不确定。

您在编译时定义了类及其所有字段和方法等。直到运行时才创建实例。所以这并不矛盾,当你到达第1行时,这个类就被完全定义了


正如其他人所指出的,由于
main
方法是
static
,您将在没有实例化对象的情况下到达第1行,但您可以这样做而不会产生问题。我在一次课堂实验中一直使用这种模式。

因为代码是先编译,然后执行的。编译器要验证该行,只需知道名为XCopy的类是否存在,以及它是否具有无参数构造函数。它不需要知道关于类的所有信息。

同样的原因,您可以在第42行调用直到第78行才定义的方法?Java不是一种脚本语言,所以在使用它们之前不必声明(事实上,某些脚本语言也是如此)。类定义在编译时被视为一个整体

您甚至可以在自己的构造函数中实例化类的对象:

public class Test {
    Test a;

    Test() {
        a = new Test();
    }

    public static void main(String[] args) {
        System.out.println(new Test());
    }
}

这就产生了。。。等等。。。
java.lang.StackOverflowerError

类只是一个蓝图,它描述了类的每个实例的外观和行为。根据类及其构造函数的可见性,同一类、同一包或完全陌生的代码可能会创建实例

例如,在构造函数不应为公共的类中提供工厂方法是很常见的:

public class Foo {
    // only I get to create new instances
    private Foo() {
    }

    // but you can get instances through this factory method
    public static Foo createFoo() {
        return new Foo();
    }
}

如果你的同事来自C或pascal编程背景,那么这个问题是非常合乎逻辑的。在C程序中,方法必须在第一次使用它们的行上方声明。由于按此顺序对函数进行排序并不总是切实可行的,因此有一些方法只给出方法名称、返回类型和参数,而不定义函数体:

// forward declaration
void doSomething(void);

void doSomethingElse(void) {
    doSomething();
}

// function definition
void doSomething(void) {
    ...
}
这样做是为了简化解析器的创建并允许更快的解析,因为需要更少的源代码传递。然而,在Java中,标识符允许在其定义点之前使用。因此,解析必须分几个阶段进行。构建与源代码对应的语法树后,将遍历该树以确定类或方法的所有定义。方法体将在稍后的阶段处理,此时有关范围中名称的所有信息都已知


因此,在处理主方法的方法体时,编译器知道类的默认构造函数及其doIt方法,并且可以生成正确的字节码来准确调用此方法。

类加载器将类加载到内存中,并在发生以下任何情况时初始化该类

1) 类的实例是使用new()关键字或使用class.forName()反射创建的,这可能会在Java中引发ClassNotFoundException

2) 调用类的静态方法

3) 已指定类的静态字段

4) 使用类的静态字段,该字段不是常量变量

5) 若类是顶级类,则执行词汇嵌套在类中的assert语句

所以在第1行中,类被加载并初始化,因此在实例化类本身的实例时没有问题

但是如果你的代码是这样的

class Test {
    Test test2 = new Test();
    public static void main(String[] args) {
        Test test1 = new Test();     
    }
}

the above code will result in stackoverflow exception.



class Test {
    public static void main(String[] args) {
        Test test = new Test();     
    }
}

In the above code creating an instance won't call main again. 
Remember, main is a static method, not tied to any particular instance.



class Test {
    static Test test2 = new Test();
    public static void main(String[] args) {
        Test test1 = new Test();     
    }
}

This code will also run fine.

阅读更多相关内容。类也可以在非静态方法中创建自身的实例。没有内在的理由不能让一个对象创建另一个相同类型的对象。比如,就在昨天,我为一个对象编写了一个函数,它表示一个我称为“midnight”的日期/时间,并返回同一类的另一个实例,该类与调用对象的日期相同,但时间回滚到最近的午夜,即MyDate(“2011-01-01 08:30:00”)。midnight()eff