在类内部创建一个类实例的可能性是什么?
public class My_Class
{
My_Class new_class= new My_Class();
}
我知道这是可能的,我自己也做过,但我仍然无法相信这不是像“先有鸡还是先有蛋”的问题一样的问题。我很高兴能够从编程角度以及JVM /编译器角度得到一个能够澄清这个问题的答案。我认为理解这一点将有助于我消除面向对象编程中一些非常重要的瓶颈概念。
我已经收到一些答案,但都没有达到我预期的程度。
在类内部创建一个类实例的可能性是什么?
public class My_Class
{
My_Class new_class= new My_Class();
}
我知道这是可能的,我自己也做过,但我仍然无法相信这不是像“先有鸡还是先有蛋”的问题一样的问题。我很高兴能够从编程角度以及JVM /编译器角度得到一个能够澄清这个问题的答案。我认为理解这一点将有助于我消除面向对象编程中一些非常重要的瓶颈概念。
我已经收到一些答案,但都没有达到我预期的程度。
在类本身创建实例没有任何问题。当程序被编译和运行时,明显的鸡生蛋问题有不同的解决方法。
编译时
当一个创建自身实例的类正在被编译时,编译器发现该类对自身存在循环依赖。这个依赖关系很容易解决:编译器知道该类正在被编译,因此它不会再次尝试编译它。相反,它假装该类已经存在并生成相应的代码。
运行时
当一个类创建自己的对象时,最大的鸡生蛋问题是当该类甚至还不存在时;也就是说,当该类正在被加载时。这个问题通过将类加载分为两个步骤来解决:首先定义该类,然后初始化该类。
定义意味着向运行时系统(JVM或CLR)注册该类,以便它知道该类的对象具有哪些结构,并且在调用其构造函数和方法时应运行哪些代码。
一旦类被定义,它就被初始化。这是通过初始化静态成员、运行静态初始化块和其他在特定语言中定义的内容来完成的。请注意,在此时类已经被定义,因此运行时知道类的对象看起来像什么,以及应该运行哪些代码来创建它们。这意味着,在初始化类时创建类的对象没有任何问题。class Test {
static Test instance = new Test();
static int x = 1;
public Test() {
System.out.printf("x=%d\n", x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Test
类的实例并将其分配给instance
。实例创建有两个步骤:
Test()
构造函数来初始化对象。JVM可以做到这一点,因为它已经从类定义阶段获得了构造函数的代码。构造函数打印出x
的当前值,即0
。x
设置为1
。只有现在类才完成加载。请注意,JVM创建了一个类的实例,即使它还没有完全加载。您可以证明这一事实,因为构造函数打印出x
的初始默认值0
。
main
方法来运行程序。 main
方法创建了另一个Test
类的对象 - 程序执行中的第二个对象。再次,构造函数打印出x
的当前值,现在是1
。程序的完整输出如下:x=0
x=1
正如您所看到的,这里没有鸡生蛋的问题:将类加载分为定义和初始化阶段完全避免了这个问题。
那么当一个对象的实例想要创建另一个实例时,怎么办呢?就像下面的代码一样?
class Test {
Test buggy = new Test();
}
buggy
被设置为null
。然后JVM开始初始化对象。为了做到这一点,它必须创建另一个类Test
的对象。就像之前一样,JVM已经知道如何做:它分配内存,将属性设置为null
,并开始初始化新对象...这意味着它必须创建同一类的第三个对象,然后是第四个、第五个等等,直到它耗尽堆栈空间或堆内存。class Chain {
Chain link = null;
public Chain(int length) {
if (length > 1) link = new Chain(length-1);
}
}
public sealed class AutoInvokingEvent
{
private AutoInvokingEvent _statuschanged;
public event EventHandler StatusChanged
{
add
{
_statuschanged.Register(value);
}
remove
{
_statuschanged.Unregister(value);
}
}
private void OnStatusChanged()
{
if (_statuschanged == null) return;
_statuschanged.OnEvent(this, EventArgs.Empty);
}
private AutoInvokingEvent()
{
//basis case what doesn't allocate the event
}
/// <summary>
/// Creates a new instance of the AutoInvokingEvent.
/// </summary>
/// <param name="statusevent">If true, the AutoInvokingEvent will generate events which can be used to inform components of its status.</param>
public AutoInvokingEvent(bool statusevent)
{
if (statusevent) _statuschanged = new AutoInvokingEvent();
}
public void Register(Delegate value)
{
//mess what registers event
OnStatusChanged();
}
public void Unregister(Delegate value)
{
//mess what unregisters event
OnStatusChanged();
}
public void OnEvent(params object[] args)
{
//mess what calls event handlers
}
}
我经常在类内部创建实例的主要原因是,在静态上下文中引用非静态项目时,例如当我为游戏创建框架或其他内容时,我使用主方法来设置框架。当构造函数中有想要设置的东西时(如在下面的示例中,我使我的JFrame不等于null),也可以使用它:
public class Main {
private JFrame frame;
public Main() {
frame = new JFrame("Test");
}
public static void main(String[] args) {
Main m = new Main();
m.frame.setResizable(false);
m.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
m.frame.setLocationRelativeTo(null);
m.frame.setVisible(true);
}
}
在对象内部创建一个对象实例可能会导致 StackOverflowError,因为每次从这个“Test
”类创建一个实例时,你都会创建另一个实例,再创建另一个实例,以此类推... 尽量避免这种做法!
public class Test {
public Test() {
Test ob = new Test();
}
public static void main(String[] args) {
Test alpha = new Test();
}
}
public class My_Class{My_Class new_class = new My_Class();}
,而且我不确定Java是否有所不同,但在C#中,它将是一个异常。 - Habib应该将保存self实例的属性设置为静态的
public class MyClass {
private static MyClass instance;
static {
instance = new MyClass();
}
// some methods
}
MyClass
的构造函数中创建一个MyClass
(无限递归),那么这样做就没有任何问题。组合设计模式甚至是基于此的。我真的不明白问题在哪里。另外,public void class
永远不会编译。 - Pierre-Luc Pineault