【问题标题】:Java : Class inheriting selfJava:继承自我的类
【发布时间】:2011-03-24 10:47:21
【问题描述】:

我知道这毫无意义:我只是觉得这很有趣,我想进一步了解当您创建一个继承自身的类时会发生什么,从而导致堆栈溢出崩溃。令人惊奇的是,Java 允许您从一开始就创建这样的构造。

我只是在猜测,但 JVM 是否将自身置于无限循环中试图在实例化类之前解析它,或者它实际上是在无休止地实例化该类的多个副本?

我应该更具体一些;我正在使用内部类从封闭类派生。

 public class Outside {
    private int outsideValue;

    public class Inside extends Outside {
        private int insideValue;
        public Inside(int val) {
            insideValue = val;
        }
    }

    public Outside() {
        Inside o = new Inside(0);
    }
}

public class Main {
    public static void main(String args[]) {
        Outside o = new Outside();
    }
}

【问题讨论】:

  • @TurtleToes 那么,当你尝试这样做时实际发生了什么?
  • 什么意思?我无法获得任何循环继承,您使用的是哪个版本? Inherit.java:1:涉及First的循环继承
  • 尝试继承正在声明的类会导致编译错误(Windows/Cygwin 上的 jdk 1.6)。您使用的是什么版本的 Java?
  • 使用内部类继承外部类
  • javac 不是创建类文件的唯一方法。你也可以编辑一个来获得一个虚假的类。

标签: java oop inheritance


【解决方案1】:

请记住,由于Inside 扩展了Outside,它有一个隐式调用super(),这是Outside 的构造函数(它又调用Inside 的构造函数) 就这样。

您发布的代码在概念上与以下程序没有区别

class A {
    B b = new B();
}

class B extends A {
}

public class Test {
    public static void main(String[] args) {
        new A(); // Create an A...
                 //   ... which creates a B
                 //   ... which extends A thus implicitly creates an A
                 //   ... which creates a B
                 //   ...
    }
}

【讨论】:

  • 运行我发布的代码,你会得到一个堆栈溢出错误
  • 创建另一个类并尝试实例化外部类。
【解决方案2】:

这个问题的最终形式与循环继承和内部类无关。这只是由未绑定的递归构造函数调用引起的无限递归。同样的效果可以用下面这个简单的例子来展示:

public class A {
    public A() {
        new A();
    }
}

请注意,此代码完全有效,因为 Java 对递归调用没有任何限制。

在您的情况下,由于继承,它稍微复杂一些,但如果您回想子类的构造函数隐式调用超类的构造函数,应该清楚这些调用形成无限递归。

【讨论】:

  • 您的示例并未反映一个类扩展另一个类的事实。请参阅我的答案以获取更接近 OP 的示例。
  • 我已经超过 48 小时没有睡觉了,我正在喝我的第五罐红牛……起初我觉得很神奇
【解决方案3】:

在 Eclipse 之类的 IDE 中尝试,它不允许您这样做。即给出这样的错误。

检测到循环:Test 类型无法扩展/实现自身或其自己的成员类型之一

【讨论】:

  • 但那是 IDE 比编译器更聪明(一件好事)。令我惊讶的是,java 编译器完全允许这样的构造。
【解决方案4】:

如果我们再修改一下,您发布的示例可能会出现问题:

public class Outside {

    public class Inside extends Outside {

            public Inside(int val) {
        }

    }

    private Inside i;

    public Outside() {
        i = new Inside();
    }
}

但这与InsideOutside 的内部类这一事实并没有真正的关系,它可能同样发生在单独的顶级类中。

【讨论】:

  • 这里是a real-life example,有人用这个问题构建了一些东西。
  • 我会说这样的例子是由糟糕的设计引起的。我无法理解考虑循环继承的有效 OO 模型。由于它甚至不是真实系统中存在的概念,而且我们的任务是建模和构建此类真实系统的软件表示,因此循环继承没有位置(或不应该有)。
  • 有些面向对象语言允许循环继承——但这意味着循环中的所有类都是同义的。 (Cecil 是一个例子,如果我没记错的话。)这仍然与将继承与内部类混合使用无关。
【解决方案5】:

java 编译器在尝试进入循环继承链时不会进入无限循环。毕竟,每条继承链都是一个最终有限的图(从计算上讲,节点和边的数量非常少。)更准确地说,从子类 A 到(最终)超类 Z 的继承图必须是一条线(而不是其他方式,不过),编译器可以很容易地确定它是否是一条线。

程序不需要太多的时间来确定这样一个小图是否是循环的,或者它是否是一条线,这是编译器所做的。因此编译器不会进入无限循环,并且 JVM 永远不会用完堆栈空间,因为 1)编译器既不会在 JVM 上运行,也不会 2)JVM 可以执行(因为没有任何东西可以编译并且编译器永远不会调用在这种情况下,JVM 无论如何。)

我不知道有任何语言允许这种循环继承图(但我已经 11 年只做 Java 了,所以我对 Java 以外的任何东西的记忆都是糊状的。)此外,我看不到使用这种构造(在建模或现实生活中)。不过,理论上可能很有趣。

编辑

好的,我运行了您的代码,确实会导致堆栈溢出。你是对的。我将不得不坐下来真正研究一下,以了解编译器为什么允许这样的构造。

很好的发现!!!!

【讨论】:

  • 谢谢...我只是坐在这里试图破坏东西,它坏了
  • @luis.espinal,该程序没有什么特别之处。堆栈溢出是由于两个相互递归的构造函数造成的。看我的回答。
  • 我想通了...起初让我措手不及...不过,我不敢相信它会让您做出如此明目张胆的循环引用
  • @aioobe - 不,不,我知道这两个构造函数是相互递归的(顺便说一句,很好的答案。)我必须思考的是为什么编译器会让这样的构造存在。不应该有理由(在有效模型上)继承链是循环的或内部成员扩展其包含类型。这真的不是很好的建模,有问题的语义。我很惊讶编译器完全允许它。不过感谢您的回复。
  • 语义很清楚:它是一个无限递归,它将永远在具有无限内存的机器上运行。实际上,如果编译器或 JVM 足够聪明,它会将其编译为具有等效语义的while (true) {}。您可能已经意识到,禁止这个 Java 程序的理由与禁止 while(true) {}-program 的理由一样少。
【解决方案6】:

扩展自身会产生循环继承错误(java 不允许)。您的代码示例确实可以编译并且是有效的。


由于Vladimir Ivanov's 的坚持,我将修复我的编辑。

由于以下原因,您的代码会抛出 StackOverflowError

Inside o = new Inside(0);

由于Inside 扩展OutsideInside 首先隐式调用super() 方法(因为您自己没有调用它)。 Outside() 构造函数初始化Inside o 并再次循环运行,直到堆栈已满并溢出(堆堆栈内有太多InsideOutside)。

希望这对弗拉基米尔·伊万诺夫特别有帮助。

【讨论】:

  • 这与循环继承无关(-1)
  • @Vladimir Ivanov,OP 后来添加了代码,因此我添加了第二句。
  • 是的。这不是我的愿望。仅供参考,在编辑答案之前,不允许您更改投票。
【解决方案7】:

您可以通过以下方式获得答案:

Class.forName("MyClass");

这样它会被解析但不会被实例化。因此,您可以检查分辨率本身是否会导致崩溃。

我猜这取决于你使用的 JVM。

【讨论】:

    【解决方案8】:

    当我尝试编译时:

    class A extends A {
    }
    

    我明白了:

    $ javac A.java
    A.java:1: cyclic inheritance involving A
    class A extends A {
    ^
    1 error
    

    所以Java 不允许你做这种事情。欲知详情,java version "1.6.0_24"

    【讨论】:

    • 这不是关于循环继承(-1)。这是关于封闭类的内部类继承。
    • @Vladimir 我的答案是在向问题添加代码之前写的。没有代码,就无法知道 op 谈论的是内部类(“你创建了一个继承自身的类”,这正是我所做的)。
    • @Krtel,如果再次编辑答案,我会收回我的反对票。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-27
    • 2012-02-25
    相关资源
    最近更新 更多