2013-08-21 31 views
22

Điều gì làm cho nó có thể tạo ra một thể hiện của lớp bên trong chính lớp đó?Làm thế nào để tạo một thể hiện của lớp bên trong của lớp chính nó hoạt động?

public class My_Class 
{ 

     My_Class new_class= new My_Class(); 
} 

Tôi biết điều đó là có thể và đã tự làm nhưng tôi vẫn không thể tin rằng đây không phải là "người đầu tiên - Gà hay trứng?" loại vấn đề. Tôi có thể vui mừng khi nhận được câu trả lời sẽ làm rõ điều này từ quan điểm lập trình cũng như từ phối cảnh JVM/trình biên dịch. Tôi nghĩ rằng sự hiểu biết này sẽ giúp tôi xóa một số khái niệm cổ chai rất quan trọng của lập trình OO.

Tôi đã nhận được một số câu trả lời nhưng không ai trả lời rõ ràng về mức độ tôi mong đợi.

+2

Nếu bạn không tạo ra 'MyClass 'trong' MyClass' 'constructor (Yay cho đệ quy vô hạn) hoàn toàn không có vấn đề gì khi làm điều đó. Mẫu thiết kế tổng hợp thậm chí còn dựa trên đó. Tôi thực sự không hiểu câu hỏi là gì. Ngoài ra, 'public void class' sẽ không bao giờ biên dịch. –

+0

Tôi biết rằng nó có thể được thực hiện nhưng câu hỏi của tôi là: Không phải là như bạn đang sử dụng chức năng mà không tạo ra các chức năng ở nơi đầu tiên. Làm thế nào bạn có thể giải thích điều này cho ai đó chỉ biết lập trình chức năng? –

+0

Phần nào của quá trình tải và khởi tạo lớp vẫn chưa rõ ràng? – Joni

Trả lời

31

Hoàn toàn không có vấn đề trong việc tạo các phiên bản của một lớp trong chính lớp đó. Vấn đề gà hoặc trứng rõ ràng được giải quyết theo nhiều cách khác nhau trong khi chương trình đang được biên dịch và khi chương trình đang được chạy.

Compile-time

Khi một lớp mà tạo ra một thể hiện của bản thân đã được biên soạn, trình biên dịch thấy rằng lớp có một circular dependency trên chính nó. Sự phụ thuộc này rất dễ giải quyết: trình biên dịch biết rằng lớp đó đã được biên dịch sao cho nó sẽ không cố gắng biên dịch lại nó. Thay vào đó, nó giả vờ rằng lớp đã tồn tại tạo ra mã cho phù hợp.

Run-time

Các vấn đề con gà hay quả trứng lớn nhất với một lớp tạo một đối tượng của chính nó là khi lớp thậm chí không tồn tại được nêu; có nghĩa là, khi lớp đang được nạp. Vấn đề này được giải quyết bằng cách phá vỡ lớp tải thành hai bước: đầu tiên lớp là được xác định và sau đó nó được khởi tạo được khởi tạo.

Xác định phương tiện đăng ký lớp với hệ thống thời gian chạy (JVM hoặc CLR), để biết cấu trúc mà đối tượng của lớp có và mã nào sẽ được chạy khi hàm tạo và phương thức của nó được gọi.

Sau khi lớp đã được xác định, nó được khởi tạo. Điều này được thực hiện bằng cách khởi tạo các thành viên tĩnh và chạy các khối khởi tạo tĩnh và các thứ khác được định nghĩa trong ngôn ngữ cụ thể. Nhớ lại rằng lớp đã được định nghĩa tại thời điểm này, do đó, thời gian chạy biết các đối tượng của lớp trông như thế nào và mã nào sẽ được chạy để tạo chúng. Điều này có nghĩa là không có vấn đề gì để tạo ra các đối tượng của lớp khi khởi tạo nó.

Dưới đây là một ví dụ minh họa cách khởi tạo lớp và instantiation tương tác trong Java:

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

Hãy bước qua bao JVM sẽ chạy chương trình này. Đầu tiên JVM tải lớp Test.Điều này có nghĩa rằng lớp là lần đầu tiên định nghĩa, do đó JVM biết rằng

  1. một lớp được gọi là Test tồn tại và rằng nó có một phương pháp main và một constructor, và rằng
  2. lớp Test có hai tĩnh các biến số, được gọi là x và một biến khác được gọi là instance
  3. bố cục đối tượng của lớp Test là gì. Nói cách khác: đối tượng trông như thế nào; những gì thuộc tính nó có. Trong trường hợp này, Test không có bất kỳ thuộc tính cá thể nào.

Bây giờ lớp được xác định, nó là được khởi tạo. Trước hết, giá trị mặc định 0 hoặc null được gán cho mọi thuộc tính tĩnh. Điều này đặt x thành 0. Sau đó, JVM thực thi các khởi tạo trường tĩnh trong thứ tự mã nguồn. Có hai:

  1. Tạo một thể hiện của lớp Test và gán cho instance. Có hai bước để tạo ví dụ:
    1. Bộ nhớ đầu tiên được phân bổ cho đối tượng. JVM có thể làm điều này vì nó đã biết bố cục đối tượng từ giai đoạn định nghĩa lớp.
    2. Nhà xây dựng Test() được gọi để khởi tạo đối tượng. JVM có thể làm điều này vì nó đã có mã cho hàm tạo từ giai đoạn định nghĩa lớp. Hàm khởi tạo in ra giá trị hiện tại của x, là 0.
  2. Đặt biến tĩnh x to 1.

Chỉ bây giờ lớp đã tải xong. Lưu ý rằng JVM đã tạo ra một cá thể của lớp, mặc dù nó chưa được nạp đầy đủ. Bạn có bằng chứng về thực tế này vì hàm tạo đã in giá trị mặc định ban đầu 0 cho x.

Bây giờ JVM đã tải lớp này, nó gọi phương thức main để chạy chương trình. Phương thức main tạo một đối tượng khác của lớp Test - thứ hai trong quá trình thực hiện chương trình. Một lần nữa, hàm tạo sẽ in ra giá trị hiện tại của x, hiện là 1. Sản lượng đầy đủ của chương trình là:

x=0 
x=1 

Như bạn có thể thấy không có vấn đề con gà hay quả trứng: việc tách lớp tải vào định nghĩa và khởi tạo giai đoạn tránh những vấn đề hoàn toàn.

Điều gì xảy ra khi một cá thể của đối tượng muốn tạo một cá thể khác, như trong mã bên dưới?

class Test { 
    Test buggy = new Test(); 
} 

Khi bạn tạo đối tượng của lớp này, một lần nữa không có vấn đề cố hữu. JVM biết cách đối tượng nên được đặt ra trong bộ nhớ để nó có thể cấp phát bộ nhớ cho nó. Nó đặt tất cả các thuộc tính về giá trị mặc định của chúng, vì vậy buggy được đặt thành null. Sau đó, JVM bắt đầu khởi tạo đối tượng.Để làm điều này, nó phải tạo một đối tượng khác của lớp Test. Giống như trước đây, JVM đã biết cách làm điều đó: nó phân bổ bộ nhớ, đặt thuộc tính là null và bắt đầu khởi tạo đối tượng mới ... có nghĩa là nó phải tạo đối tượng thứ ba của cùng một lớp, và sau đó là thứ tư, thứ năm, v.v. cho đến khi nó hết dung lượng bộ nhớ hoặc bộ nhớ heap.

Không có vấn đề khái niệm nào ở đây bạn nghĩ: đây chỉ là trường hợp phổ biến của một đệ quy vô hạn trong một chương trình được viết kém. Các đệ quy có thể được kiểm soát ví dụ bằng cách sử dụng một truy cập; các nhà xây dựng của lớp này sử dụng đệ quy để thực hiện một chuỗi các đối tượng:

class Chain { 
    Chain link = null; 
    public Chain(int length) { 
     if (length > 1) link = new Chain(length-1); 
    } 
} 
+0

Điều đó có nghĩa là các biểu thức bên trong định nghĩa lớp tạo ra cá thể của lớp riêng của nó không được thực thi/biên dịch trước các biểu thức khác không? Bạn đã nói, lớp đã được định nghĩa tại thời điểm này và điều đó là đúng, nhưng làm thế nào để cá thể đó có thể truy cập vào phương thức không được định nghĩa cho đến điểm đó. Bạn có thể vui lòng xây dựng câu trả lời của bạn ít để giải thích điều này cho ai đó đã luôn sử dụng ngôn ngữ diễn giải (có nghĩa là mã thực hiện từng dòng) –

+0

Tất cả các phương thức, hàm tạo và các thành viên khác được xác định khi lớp được xác định trước khi bất kỳ mã nào trong lớp được thực hiện. – Joni

+0

Xin vui lòng xóa sự nhầm lẫn của tôi ở đây, Không phải là trường hợp của lớp học của lớp riêng cũng là thành viên của lớp trong mã của tôi ở trên? Tôi đang cố gắng xóa bỏ suy nghĩ của mình, Joni. –

-1

Tạo một thể hiện của một đối tượng bên trong đối tượng có thể dẫn đến một StackOverflowError vì mỗi thời gian mà bạn tạo ra một thể hiện từ lớp này "Test" bạn sẽ tạo một thể hiện khác và một thể hiện khác, v.v .. cố gắng tránh thực hành này!

public class Test { 

    public Test() { 
     Test ob = new Test();  
    } 

    public static void main(String[] args) { 
     Test alpha = new Test(); 
    } 
} 
+0

Vui lòng định dạng đoạn mã của bạn. – leppie

+0

Điều đó chỉ đúng nếu nó được thực hiện trong hàm tạo, cho cùng một hàm tạo và vô điều kiện, vì nó dẫn đến đệ quy vô hạn. Nếu nó được thực hiện bên ngoài constructor (nói, trong phương thức 'Clone') thì điều này không áp dụng. Nếu nó được thực hiện có điều kiện, thì đó là đệ quy, nhưng không phải đệ quy vô hạn. – Servy

+0

@Servy, khởi tạo đối tượng lớp * cùng * tại thời điểm khai báo cũng sẽ gây ra đệ quy vô hạn và ngoại lệ stackoverflow. Nó không chỉ là nhà xây dựng. Mã này * (giống như câu hỏi) * sẽ cung cấp cho Stack overflow exception khi chạy 'public class My_Class {My_Class new_class = new My_Class();}', cộng với tôi không chắc liệu java có hoạt động khác không, nhưng trong C#, nó sẽ là một ngoại lệ. – Habib

1

Các câu trả lời khác hầu hết đều đề cập đến câu hỏi. Nếu nó giúp bọc một bộ não xung quanh nó, làm thế nào về một ví dụ?

Vấn đề về trứng và trứng được giải quyết như bất kỳ vấn đề đệ quy nào: trường hợp cơ bản không tiếp tục sản xuất nhiều công việc/trường hợp/bất cứ điều gì.

Hãy tưởng tượng bạn đã đặt cùng một lớp để tự động xử lý lời gọi sự kiện theo chủ đề chéo khi cần. Liên quan nhiều đến WinForms luồng. Sau đó, bạn muốn lớp để lộ một sự kiện xảy ra bất cứ khi nào một cái gì đó đăng ký hoặc hủy đăng ký với trình xử lý, và tự nhiên nó cũng nên xử lý lời gọi qua luồng.

Bạn có thể viết mã xử lý hai lần, một lần cho chính sự kiện và một lần cho sự kiện trạng thái hoặc viết một lần và sử dụng lại.

Phần lớn lớp học đã được cắt bớt vì nó không thực sự liên quan đến cuộc thảo luận.

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 
    } 

} 
2

Điều chính tôi luôn thấy khi tạo tài liệu không tĩnh trong ngữ cảnh tĩnh, chẳng hạn như khi tôi tạo khung cho trò chơi hoặc bất cứ điều gì, tôi sử dụng phương pháp chính để thực sự thiết lập khung. Bạn cũng có thể sử dụng nó cho khi có một cái gì đó trong một constructor mà bạn muốn thiết lập (như sau, tôi làm cho JFrame của tôi không bằng 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); 
    } 
} 
Các vấn đề liên quan