2009-07-27 24 views
6

Tôi có mã nàyTại sao thứ tự các khai báo lại quan trọng đối với các trình khởi tạo tĩnh?

private static Set<String> myField; 

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); 
} 

và hoạt động. Nhưng khi tôi lật thứ tự, tôi nhận được tham chiếu về phía trước bất hợp pháp lỗi.

static { 
    myField = new HashSet<String>(); 
    myField.add("test"); // illegal forward reference 
} 

private static Set<String> myField; 

Tôi hơi bị sốc, tôi không ngờ rằng điều này giống như Java. :)

Điều gì xảy ra ở đây? Tại sao thứ tự khai báo lại quan trọng? Tại sao công việc được giao nhưng không phải là phương thức gọi?

Trả lời

10

Trước hết, hãy thảo luận xem "tham chiếu chuyển tiếp" là gì và tại sao nó là xấu. Một tham chiếu chuyển tiếp là một tham chiếu đến một biến chưa được khởi tạo và nó không chỉ giới hạn ở các bộ phận inital tĩnh. Đây là những điều xấu vì đơn giản, nếu được cho phép, chúng sẽ cho chúng ta những kết quả bất ngờ. Hãy xem mã bit này:

public class ForwardRef { 
    int a = b; // <--- Illegal forward reference 
    int b = 10; 
} 

Điều gì sẽ xảy ra khi lớp học này được khởi tạo? Khi một lớp được khởi tạo, các khởi tạo được thực thi theo thứ tự từ đầu đến lần gặp cuối cùng. Vì vậy, bạn mong muốn dòng

a = b; 

để thực hiện trước:

b = 10; 

Để tránh những loại vấn đề này, các nhà thiết kế Java hoàn toàn không được phép sử dụng như tài liệu tham khảo về phía trước.

EDIT

hành vi này được xác định bởi section 8.3.2.3 of Java Language Specifications:

Việc kê khai của một thành viên cần phải xuất hiện trước khi nó được sử dụng chỉ khi các thành viên là một thể hiện (tương ứng tĩnh) lĩnh vực của một lớp hoặc giao diện C và tất cả các điều kiện sau đây giữ:

  • Cách sử dụng xảy ra trong một trường hợp ly tĩnh) initializer biến của C hoặc trong một thể hiện (tương ứng tĩnh) initializer của C.

  • Cách sử dụng không nằm ở phía bên tay trái của bài tập.

  • C là lớp hoặc giao diện trong cùng bao quanh việc sử dụng.

Lỗi biên dịch xảy ra nếu một trong ba yêu cầu trên không được đáp ứng.

+0

OK, tôi hiểu. Nhưng sau khi gán myField * ban đầu * được khởi tạo. Tại sao tôi vẫn không thể gọi phương thức thêm? –

+0

Nếu ba yêu cầu đó không có, tôi có thể tạo một tham chiếu ngầm định bằng cách sử dụng các biến cục bộ trong bộ khởi tạo, đúng không? Đó có phải là lý do cho những hạn chế này không? –

+0

THLS JLS cho biết: "... Những hạn chế này được thiết kế để bắt, tại thời gian biên dịch, thông tư hoặc theo cách khác không đúng định dạng ...." –

1

Trong Java, tất cả trình khởi tạo, tĩnh hoặc cách khác, được đánh giá theo thứ tự xuất hiện trong định nghĩa lớp.

+2

Nhưng tôi nghĩ câu hỏi là * tại sao *, phải không? –

+0

Tại sao điều đó cấm gọi phương thức cộng nhưng không phải là nhiệm vụ? –

0

Tôi nghĩ lời gọi phương thức có vấn đề vì trình biên dịch không thể xác định phương thức add() nào để sử dụng mà không có loại tham chiếu cho myField.

Khi chạy, phương pháp được sử dụng sẽ được xác định theo loại đối tượng, nhưng trình biên dịch chỉ biết về loại tham chiếu.

+0

Tôi không nghĩ đó là vấn đề, bởi vì với các phương thức ảo trình biên dịch hầu như không bao giờ có thể ràng buộc phương thức tại thời gian biên dịch. Đó là nơi VMT được sử dụng: http://en.wikipedia.org/wiki/Virtual_method_table –

1

Xem các quy tắc cho tham chiếu chuyển tiếp trong the JLS. Bạn không thể sử dụng tiếp tài liệu tham khảo nếu:

  • Việc sử dụng xảy ra trong một thể hiện (tương ứng tĩnh) initializer biến của C hoặc trong một thể hiện (tương ứng tĩnh) initializer của C.
  • Việc sử dụng không phải là mặt trái bên của một nhiệm vụ.
  • Cách sử dụng là thông qua một tên đơn giản.
  • C là lớp hoặc giao diện trong cùng bao quanh việc sử dụng.

Vì tất cả những điều này giữ cho ví dụ của bạn, tham chiếu chuyển tiếp là bất hợp pháp.

2

thử điều này:

class YourClass { 
    static { 
     myField = new HashSet<String>(); 
     YourClass.myField.add("test"); 
    } 

    private static Set<String> myField; 
} 

cần biên dịch mà không có lỗi theo JLS ...
(không thực sự giúp đỡ, hoặc?)

+0

Tôi chưa thử nghiệm điều này, nhưng điều này vẫn vi phạm "quy tắc bên trái" trong JLS. Xem câu trả lời của tôi dưới đây cho lý do tại sao. – rtperson

+0

Tôi đã thử nghiệm nó và nó hoạt động. Bây giờ tôi không hiểu bất cứ điều gì nữa ... –

+1

@DR: đây là một tính năng trình biên dịch. Phản ánh có thể được sử dụng để phá vỡ nhiều kiểm tra trình biên dịch, ví dụ: gọi một phương thức riêng từ bên ngoài lớp. –

1

Để xây dựng trên DFA của câu trả lời:

Tôi nghĩ rằng những gì vấp ngã bạn là quy tắc "bên trái" ở điểm đạn thứ hai trong JLS 8.2.3.2. Trong khởi tạo của bạn, myField nằm ở phía bên tay trái. Trong cuộc gọi của bạn để thêm, nó ở phía bên tay phải. Mã ở đây hoàn toàn là:

boolean result = myField.add('test') 

Bạn không đánh giá kết quả, nhưng trình biên dịch vẫn hoạt động như thể nó ở đó. Đó là lý do tại sao khởi tạo của bạn chuyển trong khi cuộc gọi của bạn để thêm() không thành công.

Đối với lý do tại sao điều này là như vậy, tôi không có ý tưởng. Nó cũng có thể được cho sự tiện lợi của các nhà phát triển JVM, cho tất cả tôi biết.

+0

Nếu đó là trường hợp, không nên làm việc một phương pháp void? –

+0

Vâng, không, cho cùng một lý do chính xác. Một cuộc gọi trả về void vẫn ở phía bên tay phải, vì bạn không được gán vào nó. Bạn đã chỉ nói với trình biên dịch rằng hàm không có bất kỳ đầu ra nào, nhưng điều đó không thay đổi rằng nó là một hàm. – rtperson

Các vấn đề liên quan