简体   繁体   English

一个类怎么会有自己类型的成员,这不是无限递归吗?

[英]How can a class have a member of its own type, isn't this infinite recursion?

Say I define a class that has as a member a variable of the same type as itself.假设我定义了一个类,该类的成员是一个与自身类型相同的变量。

public class Abc {
    private Abc p;
}

This actually works, much to my surprise.这确实有效,令我惊讶。

Why I think it shouldn't: creating an instance of Abc , it contains a variable of type Abc , which contains a variable of type Abc , which contains a variable of type Abc , which .....为什么我认为它不应该:创建实例Abc ,它包含了类型的变量Abc ,其中包含类型的变量Abc ,其中包含类型的变量Abc ,这.....

Obviously I'm wrong, could someone enlighten me as to how?显然我错了,有人可以启发我吗?

You're only declaring the variable and not creating it.您只是声明变量而不是创建变量。 Try creating it at declaration or in the constructor and let me know what happens:尝试在声明或构造函数中创建它,让我知道会发生什么:

public class Abc {
   private Abc p = new Abc(); // have fun!

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

Incidentally, if you don't create it in the class, but rather accept a reference to it in a getter method or a constructor parameter, your code will work just fine.顺便说一句,如果您不在类中创建它,而是在 getter 方法或构造函数参数中接受对它的引用,则您的代码将正常工作。 This is how some linked lists work.这就是一些链表的工作方式。

The difference lies in compile-time vs run-time checks.区别在于编译时与运行时检查。

In the first case (compile time), you are declaring that you will have a reference to a value of type Abc in this instance.在第一种情况下(编译时),您声明您将在此实例中引用类型为Abc的值。 The compiler will be aware of this when it checks for proper semantics, and since it knows the type upon compile time, it sees no issue with this.编译器在检查正确的语义时会意识到这一点,并且因为它在编译时知道类型,所以它认为这没有问题。

In the second case (run time), you will actually create a value for this reference to refer to.在第二种情况下(运行时),您实际上将为此引用创建一个值以供参考。 This is where you could potentially get yourself into trouble.这是您可能会陷入麻烦的地方。 For example, if you said the following:例如,如果您说以下内容:

public class Abc {
    private Abc p;

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

This could lead you into trouble for the exact reason you cited (recursion that contains no base case and will continually allocate memory until you've run the VM out of heap space).由于您引用的确切原因,这可能会导致您陷入困境(递归不包含基本情况,并且将不断分配内存,直到您运行 VM 的堆空间不足为止)。

However, you can still do something similar to this and avoid the infinite recursion.但是,您仍然可以执行类似的操作并避免无限递归。 By avoiding creating the value during construction, you put it off until a method is called for.通过避免在构造过程中创建值,您可以将其推迟到调用方法之前。 In fact, it's one of the common ways to implement the singleton pattern in Java.事实上,它是在 Java 中实现单例模式的常用方法之一。 For example:例如:

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;
    }
}

This is perfectly valid because you only create one new instance of the value, and since run-time will have loaded the class already, it will know the type of the instance variable is valid.这是完全有效的,因为您只创建了该值的一个新实例,并且由于运行时已经加载了该类,它将知道实例变量的类型是有效的。

When you want to model some real-world scenarios, you might have to use this notion.当您想对一些真实场景进行建模时,您可能必须使用这个概念。 For example, think of a Tree's branch.例如,想想一棵树的分支。 A tree's branch might have n number of branches on it.一棵树的树枝上可能有 n 个树枝。 Or from computer science background, think of a linked list's Node.或者从计算机科学背景,想想链表的节点。 A node will have reference to the node next to it.一个节点将引用它旁边的节点。 At the end, the next will contain a null to indicate end of the list.最后, next 将包含一个 null 以指示列表的结尾。

So, this is only a reference, indicating that this type might refer to one of it's own.所以,这只是一个引用,表明这个类型可能引用它自己的一个。 Nothing more.而已。

As the chosen answer states, it's okay because the variable is not being instantiated, but rather it is accepting a reference from a set method or constructor argument.正如所选答案所述,这没关系,因为变量没有被实例化,而是接受来自 set 方法或构造函数参数的引用。 So you see, it's just a reference, just an address.所以你看,它只是一个参考,只是一个地址。

However though, there is a way of instantiating it without causing the nightmare loop, and that's by declaring it as static.但是,有一种方法可以在不引起噩梦循环的情况下实例化它,那就是将其声明为静态。 This sort of bends the recursion rule because a static member is not on an object level but on a class level.这种类型弯曲了递归规则,因为静态成员不在对象级别,而是在类级别。

So...所以...

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

Works just fine because the variable 'p' is not really affected by the instantiation of the Abc class.工作得很好,因为变量 'p' 并没有真正受到Abc类的实例化的影响。 It's almost the equivalent of creating the 'p' object in another class.这几乎相当于在另一个类中创建“p”对象。

Remember that I can do this...请记住,我可以做到这一点......

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

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

Without creating a new object of Abc in the main method.无需在 main 方法中创建 Abc 的新对象。 That's how statics work, they're virtually unaffected by instantion of objects, and in the case of your question, they are the exception to the recursion rule.这就是静态的工作原理,它们几乎不受对象实例化的影响,在您的问题中,它们是递归规则的例外。

Summarizing some of the answers here.在这里总结一些答案。

The class contains reference to one of its own kind.The best analogy in real world would be of a treasure hunt.该类包含对同类的引用。现实世界中最好的类比是寻宝。 A Clue place has some data and a clue in it which leads to another clue place which again you know repeats.线索地点有一些数据和线索,可以通向另一个线索地点,你知道它会再次重复。

It is not saying that a clue place has another clue place in it, the kind of which you are seeming to infer.这并不是说一个线索地点有另一个线索地点,你似乎推断出的那种。

The basic use of this may be the Linked list in the data structure.这个的基本用途可能是数据结构中的链表。 It is a core concept in the Linked list, a reference from one node to the another node.它是链表中的一个核心概念,从一个节点到另一个节点的引用。 The class Node consists of the basic element for Linked list. 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;
    }
}

And we can connect these node by using the reference.我们可以使用引用连接这些节点。

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 Head → 1 next → 2 next → NULL

Therefore, this is only a reference type connecting one node to the another node in the data structure of the linked list.因此,这只是链表数据结构中将一个节点连接到另一个节点的引用类型。

While we are talking about "recursion", the "execution" of code is the point we are considering.当我们在谈论“递归”时,代码的“执行”是我们正在考虑的重点。 Yes, it is "dynamic" if a "recursion" is occurring.是的,如果发生“递归”,则它是“动态的”。

In type declaration, a variable that contains a member, which is the type of containing variable, is "static".在类型声明中,包含成员的变量,即包含变量的类型,是“静态的”。 For example, a box may contain another box inside it, and so on.例如,一个盒子里面可能包含另一个盒子,依此类推。 But there is finally a smallest box which doesn't contain anything.但最终有一个最小的盒子,里面什么都没有。 For a chain of railway carriages, every carriage has a member to next carriage except the last carriage.对于一列火车车厢,除了最后一节车厢外,每节车厢都有一个下一节车厢的成员。

In program, the static data must be "finite"(You don't have "infinite" space of memory).在程序中,静态数据必须是“有限的”(您没有“无限”的内存空间)。 The execution of code may be "infinite".代码的执行可能是“无限”的。 But there is an exception.但有一个例外。 Just imaging a circle of chain.只是成像一圈链。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM