Why Can You Instantiate a Class within its Definition?
A coworker (who is very new to Java) stopped in today and asked what seemed like a very simple question. Unfortunately, I did an absolutely horrible job of trying to explain it to him. He had a book that had a little code that 开发者_C百科looked like this:
class XCopy {
public static void main(String[] args) {
XCopy x = new XCopy(); // 1
x.doIt();
}
public void doIt() {
// Some code...
}
}
He was confused on line 1. What he wanted to know was why a new instance of XCopy could be created within the definition of the class XCopy. He thought this would have given some sort of a forward referencing error. After all, we hadn't yet finished declaring what the class XCopy was, so how could we create one?
I certainly know that this is valid code but, when I tried to explain it to him, I found myself stumbling over the answer and I'm afraid I left him more confused than when he started. I'd like to hear some other explanations of why this works.
Any thoughts? Why can you instantiate an instance of a class within the definition of the class, itself?
You are defining the class, all its fields and methods etc at compile time. The instance is not created until runtime. So there is no contradiction, the class is fully defined by the time you reach line #1.
As others point out, because the main
method is static
you will reach line #1 without having instantiated an object, but you can do so without issue. I use this pattern all the time for one-class experiments.
Because code is compiled first, and executed later. All the compiler needs to know to validate that line is that a class named XCopy exists, and that it has a no-argument constructor. It doesn't need to know everything about the class.
A Class is just a blue print that describes what each and every instance of the class will look like and behave like. Depending on the visibility of the class and its constructors, code in the same class, in the same package, or complete strangers may create instances.
It is for example common to provide a factory method in classes where the constructor should not be public:
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();
}
}
It's not a forward reference in the C/C++ sense. Your main method is referring to the class as a type inside its own context. You aren't "ahead" of anything.
The fact that main is static isn't germane, because it'll still work even for non-static methods:
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
}
One difference is the compilation and linking. C/C++ have separate compilation and linking steps. Java has a class loader. I think compiling to byte code and loading as needed at runtime using the class loader is a subtle difference between Java and C/C++ that explains why the forward reference idea isn't needed, but I'm not certain.
Because the main method is static. And by static, it means that the method doesn't belong to any particular instance of the class. That is, it can be accessed without creating an instance.
Thus, in order to invoke the doIt
method which is non-static, one must create an instance of the class which holds it.
The same reason you can call a method on line 42 that isn't defined until line 78? Java isn't a scripting language, so things don't have to be declared before they are used (this is even true of some scripting languages, actually). Class definitions are considered as a whole at compile time.
You can even instantiate an object of a class in its own constructor:
public class Test {
Test a;
Test() {
a = new Test();
}
public static void main(String[] args) {
System.out.println(new Test());
}
}
This produces... wait for it... a java.lang.StackOverflowError
.
If your coworker is coming from a C or pascal programming background this question is abolutely logical. In a C program methods have to be declared above the line where they are first used. As its not always practical to order functions in this order, there are forward declarations that just give the methods name, return type and parameters, without defining the function body:
// forward declaration
void doSomething(void);
void doSomethingElse(void) {
doSomething();
}
// function definition
void doSomething(void) {
...
}
This was done to simplify the creation of a parser and to allow faster parsing since fewer passes over the source are needed. In Java however, identifiers are allowed to be used before their point of definition. Therefor parsing has to happen in several phases. After a syntax tree corresponding to the source code is build, this tree is traversed to determine all definitions of classes or methods. The method bodies are processed at a later stage, when all information about the names in scope are known.
So by the point that the method body of your main method is processed the compiler knows of the default constructor of your class and its doIt method and can generate the correct bytecode to call exactly this method.
The jvm loads the class whenever it is first "mentioned". Then there is nothing to prevent instantiation - it is already loaded
A class is loaded into memory by classloader and is initialized if any of the following happens.
1) an Instance of class is created using either new() keyword or using reflection using class.forName(), which may throw ClassNotFoundException in Java.
2) an static method of Class is invoked.
3) an static field of Class is assigned.
4) an static field of class is used which is not a constant variable.
5) if Class is a top level class and an assert statement lexically nested within class is executed.
so by line 1, class is both loaded and initialized and hence there is no problem in instantiating an instance of class in itself.
But if your code is like this,
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.
Read more from https://javarevisited.blogspot.in/2012/07/when-class-loading-initialization-java-example.html
精彩评论