【问题标题】:How can a class have a member of its own type, isn't this infinite recursion?一个类怎么会有自己类型的成员,这不是无限递归吗?
【发布时间】:2012-04-04 13:29:52
【问题描述】:

假设我定义了一个类,该类的成员具有与其自身相同类型的变量。

public class Abc {
    private Abc p;
}

这确实有效,令我惊讶的是。

为什么我认为它不应该:创建一个Abc 的实例,它包含一个Abc 类型的变量,它包含一个Abc 类型的变量,它包含一个Abc 类型的变量,它.....

显然我错了,谁能告诉我怎么做?

【问题讨论】:

  • 好问题,但请务必回到您之前的问题并接受每个问题的答案(包括这个问题)。
  • 因为在 Java 中,abc 类型的变量不包含 abc 对象。 abc 类型的变量包含对abc 对象的引用。你的推理在 C++ 中是有效的。
  • 但是一个类可以有self类型的静态对象。

标签: java oop class


【解决方案1】:

正如所选答案所述,这没关系,因为变量没有被实例化,而是接受来自 set 方法或构造函数参数的引用。所以你看,它只是一个参考,只是一个地址。

不过,有一种方法可以在不引起噩梦循环的情况下对其进行实例化,那就是将其声明为静态的。由于静态成员不在对象级别,而是在类级别,因此这种类型的递归规则被扭曲了。

所以...

public class Abc
{
    public static Abc p = new Abc ();
}

工作得很好,因为变量 'p' 并没有真正受到 Abc 类的实例化的影响。这几乎相当于在另一个类中创建 'p' 对象。

记住我可以做到这一点...

 public class Abc
 {
     public static Abc p = new Abc ();

     public static void main(String [] args)
     {
         Abc.p;
     }
 }

没有在 main 方法中创建新的 Abc 对象。这就是静力学的工作原理,它们几乎不受对象实例化的影响,而且就您的问题而言,它们是递归规则的例外。

【讨论】:

    【解决方案2】:

    区别在于编译时与运行时检查。

    在第一种情况下(编译时),您声明在这种情况下您将引用Abc 类型的值。编译器在检查语义是否正确时会意识到这一点,并且由于它在编译时就知道类型,所以它认为这没有问题。

    在第二种情况下(运行时),您实际上将为此引用创建一个值来引用。这是您可能让自己陷入麻烦的地方。例如,如果您说以下内容:

    public class Abc {
        private Abc p;
    
        public Abc() {
            p = new Abc();
        }
    }
    

    由于您引用的确切原因,这可能会导致您陷入麻烦(不包含基本情况的递归,并且会不断分配内存,直到您运行 VM 的堆空间不足)。

    但是,您仍然可以执行类似的操作并避免无限递归。通过避免在构造过程中创建值,您将其推迟到调用方法之前。事实上,这是在Java中实现the singleton pattern的常用方法之一。例如:

    public class Abc {
        private Abc p;
    
        private Abc() {  // Private construction. Use singleton method
        }
    
        public static synchronized Abc getInstance() {
            if (p == null)
                p = new Abc();
    
            return p;
        }
    }
    

    这是完全有效的,因为您只创建了一个值的新实例,并且由于运行时已经加载了类,它会知道实例变量的类型是有效的。

    【讨论】:

    • 您的回答真的很有帮助。谢谢道格。
    【解决方案3】:

    这个的基本用途可能是数据结构中的链表。它是链表中的核心概念,是从一个节点到另一个节点的引用。类 Node 由链表的基本元素组成。

    class Node{
        private int value;
        private Node next; //reference, as a pointer to another node, →
    
        Node(){
            this.value=0;
            this.next=null;
        }
        Node(int value){
            this.value=value;
            this.next=null;
        }
        Node(int value,Node next){
            this.value=value;
            this.next=next;
        }
        public int getValue() {
            return this.value;
        }
        public void setValue(int value) {
            this.value=value;
        }
        public Node getNext() {
            return this.next;
        }
        public void setNext(Node next) {
            this.next=next;
        }
    }
    

    我们可以通过引用来连接这些节点。

    class Linkedlist{
        Node head = new Node();
        Node one = new Node();
        Node two = new Node();
    
        // Assign data values 
        one.value = 1;
        two.value = 2;
    
        // Connect nodes 
        one.next = two;
        two.next = NULL;
    
        //Save address of first node in head
        head = one;
    }
    

    Head → 1 next → 2 next → NULL

    因此,这只是链表数据结构中将一个节点连接到另一个节点的引用类型。

    【讨论】:

      【解决方案4】:

      在这里总结一些答案。

      该类包含对其同类之一的引用。现实世界中最好的类比是寻宝。一个线索地点有一些数据和一个线索,它通向另一个你知道重复的线索地点。

      这并不是说一个线索地点有另一个线索地点,你似乎在推断。

      【讨论】:

        【解决方案5】:

        当我们谈论“递归”时,代码的“执行”是我们正在考虑的重点。是的,如果发生“递归”,它就是“动态的”。

        在类型声明中,包含成员的变量,即包含变量的类型,是“静态的”。例如,一个盒子里面可能包含另一个盒子,依此类推。但最后有一个最小的盒子,里面什么都没有。对于一连串的铁路车厢,除了最后一节车厢外,每个车厢都有下一节车厢的成员。

        在程序中,静态数据必须是“有限的”(你没有“无限”的内存空间)。代码的执行可能是“无限的”。但有一个例外。只是成像一圈链条。

        【讨论】:

          【解决方案6】:

          当您想要对一些真实场景进行建模时,您可能必须使用这个概念。例如,想想一棵树的树枝。一棵树的树枝上可能有 n 个树枝。或者从计算机科学背景,想想链表的节点。一个节点将引用它旁边的节点。最后,next 将包含一个 null 来表示列表的结尾。

          所以,这只是一个引用,表明这个类型可能引用它自己的一个。仅此而已。

          【讨论】:

            【解决方案7】:

            您只是声明变量而不是创建它。尝试在声明或构造函数中创建它,让我知道会发生什么:

            public class Abc {
               private Abc p = new Abc(); // have fun!
            
               public static void main(String[] args) {
                  new Abc();
               }
            }
            

            顺便说一句,如果您不在类中创建它,而是在 getter 方法或构造函数参数中接受对它的引用,您的代码就可以正常工作。这就是一些链表的工作原理。

            【讨论】:

            • 任何寻求正确答案的人都应该看看answer from @David Rodríguez - dribe,HovercraftFullOfEels 本人对此表示赞同。
            • 不过,您可以使用静态初始化器在类 Abc 的定义中实例化类 Abc 的静态成员变量。
            • @Hovercraft Full Of Eels:链接列表的好例子。现在我知道为什么有人会按照 OP 的描述声明一个类是有道理的。
            • private static final Abc p = new Abc();在 Abc 类里面? @HovercraftFullOfEels
            • @TanTran 有趣的问题。我得到了同样的错误,但我不认为该行为是规范的一部分,所以它可能取决于实现。更重要的是,虽然我们可以提出这类问题(如果我们写 X 会发生什么?),我们首先需要明确我们真的意思想要实现(在内存、指令等方面)语言只是与编译器的契约,用来表达我们想要的东西。
            猜你喜欢
            • 2015-09-19
            • 2016-06-17
            • 1970-01-01
            • 1970-01-01
            • 2012-01-21
            • 1970-01-01
            • 1970-01-01
            • 2019-06-16
            • 1970-01-01
            相关资源
            最近更新 更多