2011-03-03 81 views
23

一位同事(对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类的声明,所以我们如何创建它?

我当然知道这是有效的代码,但是当我试图向他解释时,我发现自己在答案上磕磕绊绊,恐怕我让他比开始时更困惑。我想听听其他解释为什么这会起作用。

有什么想法?为什么你可以在类的定义中实例化一个类的实例?

回答

32

您正在编译时定义类,它的所有字段和方法等。直到运行时才会创建实例。所以不存在矛盾,班级完全由你到达#1线的时间确定。

正如其他人指出,这是因为main方法是static你会到达线#1,而不必实例化一个对象,但你可以没有问题这样做。我一直都在使用这种模式进行一次实验。

+1

什么是'一流实验'? – Andru 2017-03-06 17:29:03

+2

@Andru就在你正在试验语言的时候,你不想创建一个完整的程序,所以你只需要把所有东西放到一个类中,包括主要方法 – Eames 2017-04-17 13:51:09

+0

@Mathew我只是想重新检查我的知识,即一个类首先在内存中加载,然后调用static main,对吧? – light 2017-07-31 14:48:31

1

因为主要方法是静态的。而通过静态,这意味着该方法不属于该类的任何特定实例。也就是说,可以在不创建实例的情况下访问它。

因此,为了调用非静态的doIt方法,必须创建一个持有它的类的实例。

1

无论何时第一次“提及”,jvm都会加载该类。然后没有什么可以阻止实例化 - 它已经被加载

+0

并不真正相关。类也可以在非静态方法中创建自己的实例。没有内在的原因让你不能有一个对象创建另一个相同类型的对象。就像昨天我为一个对象写了一个函数,该对象代表我称为“午夜”的日期/时间,并且返回与调用对象具有相同日期的同一类的另一个实例,但时间回滚到最近的时间午夜,即MyDate(“2011-01-01 08:30:00”)。午夜()有效返回MyDate(“2011-01-01 00:00:00”)。 – Jay 2011-03-03 22:49:31

+0

@jay是的,我一定很困。现在只保留我的答案的相关部分 – Bozho 2011-03-04 01:13:29

5

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

事实上,主要是静态的并不密切,因为它仍然对非静态方法甚至工作:

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++之间的细微差别,这就解释了为什么不需要前向参考思想,但我不确定。

12

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

3

您可以调用第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.StackOverflowError

4

一个类只是一个蓝图,描述该类的每个实例将看起来像和行为。根据类和其构造函数的可见性,同一个类,相同包或完全陌生人中的代码可能会创建实例。

它例如常见的类提供一个工厂方法,其中的构造不应该是公开的:

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

好简单的现实生活中的例子!是我正在寻找的! – Andru 2017-03-06 17:30:18

3

如果你的同事是从C或PASCAL编程背景的这个问题是abolutely逻辑来了。在C程序中,方法必须在首次使用的行的上方进行声明。由于它并不总是可行的命令顺序功能外,还有,只是给这些方法的名称,返回类型和参数,没有定义函数体:

// forward declaration 
void doSomething(void); 

void doSomethingElse(void) { 
    doSomething(); 
} 

// function definition 
void doSomething(void) { 
    ... 
} 

这样做是为了简化解析器的创建并允许更快的解析,因为需要更少的来源通过。但是,在Java中,标识符允许在定义点之前使用。因此解析必须分几个阶段进行。在构建与源代码相对应的语法树之后,遍历该树以确定类或方法的所有定义。方法体在稍后的阶段被处理,当所有有关范围名称的信息都是已知的。

因此,通过处理主要方法的方法体,编译器知道您的类的默认构造函数和它的doIt方法,并且可以生成正确的字节码来精确调用此方法。

+0

毫无疑问,当Java设计人员在编译器上工作时,与C设计人员相比,有更多的组织小组。 :) [康威法则](https://en.wikipedia.org/wiki/Conway%27s_law) – 2017-08-01 19:21:56

-1

一个类被classloader加载到内存中,并且如果发生以下任何一种情况,它将被初始化。

1)使用new()关键字或使用class.forName()使用反射来创建类的实例,该类可能会抛出Java中的ClassNotFoundException。 2)调用Class的静态方法。

3)分配一个静态类的字段。 4)使用不是常量变量的类的静态字段。 5)如果Class是顶级类,并且执行在词类中嵌套的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. 

了解更多从https://javarevisited.blogspot.in/2012/07/when-class-loading-initialization-java-example.html

相关问题