2012-03-13 76 views
8

Việc gọi một phương thức tĩnh trên một lớp trong Java có kích hoạt các khối initalization tĩnh để thực thi không?Trình khởi tạo tĩnh và phương thức tĩnh Trong Java

Theo kinh nghiệm, tôi muốn nói không. Tôi có một cái gì đó như thế này:

public class Country { 
    static { 
     init(); 
     List<Country> countries = DataSource.read(...); // get from a DAO 
     addCountries(countries); 
    } 

    private static Map<String, Country> allCountries = null; 

    private static void init() { 
     allCountries = new HashMap<String, Country>(); 
    } 

    private static void addCountries(List<Country> countries) { 
     for (Country country : countries) { 
      if ((country.getISO() != null) && (country.getISO().length() > 0)) { 
       allCountries.put(country.getISO(), country); 
      } 
     } 
    } 

    public static Country findByISO(String cc) { 
     return allCountries.get(cc); 
    } 
} 

Trong mã sử dụng lớp học, tôi làm điều gì đó như:

Country country = Country.findByISO("RO"); 

Vấn đề là tôi nhận được một NullPointerException vì bản đồ (allCountries) không được khởi tạo. Nếu tôi thiết lập các điểm ngắt trong khối static tôi có thể thấy bản đồ được điền chính xác, nhưng dường như phương thức tĩnh không có kiến ​​thức về trình khởi tạo đang được thực thi.

Có ai có thể giải thích hành vi này không?


Cập nhật: Tôi đã thêm chi tiết hơn vào mã. Nó vẫn không phải là 1: 1 (có một số bản đồ trong đó và logic hơn), nhưng tôi đã xem xét một cách rõ ràng các khai báo/tham chiếu của allCountries và chúng được liệt kê ở trên.

Bạn có thể xem mã khởi tạo đầy đủ here.

Cập nhật # 2: Tôi đã cố gắng đơn giản hóa mã càng nhiều càng tốt và viết nó nhanh chóng. Mã thực tế có khai báo biến tĩnh sau initializer. Điều đó khiến nó đặt lại tham chiếu, như Jon đã chỉ ra trong câu trả lời dưới đây.

Tôi đã sửa đổi mã trong bài đăng của mình để phản ánh điều này, vì vậy sẽ rõ ràng hơn cho những người tìm thấy câu hỏi. Xin lỗi về sự nhầm lẫn mọi người. Tôi chỉ cố gắng làm cho cuộc sống của mọi người dễ dàng hơn :).

Cảm ơn câu trả lời của bạn!

+2

bạn có thể hiển thị mã mà bạn đã khởi tạo bản đồ không? – Tom

+1

Btw bạn đang thiếu kiểu trả về của phương thức findByISO() trong ví dụ của bạn. –

Trả lời

26

Gọi một phương thức tĩnh trên một lớp trong Java có kích hoạt các khối initalization tĩnh để thực thi không?

Theo kinh nghiệm, tôi muốn nói không.

Bạn đã sai.

Từ JLS section 8.7:

Một initializer tĩnh tuyên bố trong một lớp học được thực hiện khi lớp được khởi tạo (§12.4.2). Cùng với bất kỳ trình khởi tạo trường nào cho các biến lớp (§8.3.2), các trình khởi tạo tĩnh có thể được sử dụng để khởi tạo các biến lớp của lớp.

Section 12.4.1 của JLS khẳng định:

Một lớp học hoặc giao diện kiểu T sẽ được khởi tạo ngay trước khi sự xuất hiện đầu tiên của bất kỳ một trong các cách sau:

  • T là một lớp và một thể hiện của T được tạo ra.

  • T là một lớp và phương thức tĩnh được khai báo bởi T được gọi.

  • Trường tĩnh được khai báo bằng T được gán.

  • Trường tĩnh được khai báo bằng T được sử dụng và trường không phải là biến không đổi (§4.12.4).

  • T là một lớp cấp cao nhất (§7.6), và một câu lệnh khẳng định (§14.10) được lồng vào bên trong T (§8.1.3) được thực thi.

này có thể dễ dàng hiển thị:

class Foo { 
    static int x = 0; 
    static { 
     x = 10; 
    } 

    static int getX() { 
     return x; 
    } 
} 

public class Test { 
    public static void main(String[] args) throws Exception { 
     System.out.println(Foo.getX()); // Prints 10 
    } 
} 

Vấn đề của bạn là trong một số phần của mã mà bạn đã không cho chúng ta. My đoán là bạn đang thực sự khai báo một biến địa phương, như thế này:

static { 
    Map<String, Country> allCountries = new HashMap<String, Country>(); 
    // Add entries to the map 
} 

Đó da biến tĩnh, để lại vô giá trị biến tĩnh.Nếu đây là trường hợp, chỉ cần thay đổi nó vào một phân thay vì tuyên bố:

static { 
    allCountries = new HashMap<String, Country>(); 
    // Add entries to the map 
} 

EDIT: Một điểm đáng chú ý - mặc dù bạn đã có init() như dòng đầu tiên của initializer tĩnh của bạn, nếu bạn thực làm bất cứ điều gì khác trước đó (có thể trong initializers biến khác) trong đó kêu gọi ra lớp khác, và lớp mà các cuộc gọi lại vào lớp Country của bạn, sau đó mã đó sẽ được thực hiện trong khi allCountries vẫn null.

EDIT: OK, bây giờ chúng ta có thể thấy mã thực sự của bạn, tôi đã tìm thấy sự cố. bài mã của bạn có này:

private static Map<String, Country> allCountries; 
static { 
    ... 
} 

Nhưng thực mã của bạn có này:

static { 
    ... 
} 
private static Collection<Country> allCountries = null; 

hai khác biệt quan trọng ở đây:

  • Việc kê khai biến xảy ra sau khởi động tĩnh alizer khối
  • Việc kê khai biến bao gồm một nhiệm vụ rõ ràng để null

Sự kết hợp của những đang rối tung lên bạn: initializers biến là không phải tất cả chạy trước khi khởi tạo tĩnh - khởi xảy ra theo thứ tự văn bản.

Vì vậy, bạn đang điền tập hợp ... và sau đó đặt tham chiếu thành không.

Section 12.4.2 của JLS đảm bảo nó trong bước 9 của khởi:

Tiếp theo, thực hiện một trong hai initializers lớp biến và initializers tĩnh của lớp, hoặc initializers lĩnh vực giao diện, theo thứ tự văn bản, như thể họ là một khối duy nhất.

đang trình diễn:

class Foo { 

    private static String before = "before"; 

    static { 
     before = "in init"; 
     after = "in init"; 
     leftDefault = "in init"; 
    } 

    private static String after = "after"; 
    private static String leftDefault; 

    static void dump() { 
     System.out.println("before = " + before); 
     System.out.println("after = " + after); 
     System.out.println("leftDefault = " + leftDefault); 
    } 
} 

public class Test { 
    public static void main(String[] args) throws Exception { 
     Foo.dump(); 
    } 
} 

Output:

before = in init 
after = after 
leftDefault = in init 

Vì vậy, giải pháp là hoặc để thoát khỏi sự phân công rõ ràng để null, hoặc để di chuyển tờ khai (và do đó khởi tạo) trước bộ khởi tạo tĩnh hoặc (sở thích của tôi) cả hai.

+0

Cảm ơn bạn đã làm rõ. Tôi đã kiểm tra các tham chiếu đến bản đồ và chúng ổn, tôi tham chiếu biến tĩnh, không khai báo một biến cục bộ. Tôi đã đăng thêm mã để cung cấp thông tin chi tiết. –

+0

@AlexCiminian: Vâng, đó chắc chắn không phải là mã thực của bạn - phương thức 'findByISO' không có kiểu trả về. Tôi vẫn chắc chắn vấn đề nằm trong mã của bạn ... mặc dù tôi đã có một ý tưởng khác. Sẽ chỉnh sửa. –

+0

'init()' thực sự là dòng đầu tiên của bộ khởi tạo và nó không gọi ra bất kỳ lớp nào khác có tham chiếu đến Quốc gia. Nó chỉ khởi tạo một số chủ sở hữu bên trong lớp 'Country'. Bạn có thể xem mã đầy đủ trong liên kết hastebin mà tôi đã đăng trong bản chỉnh sửa của tôi (ở cuối bài đăng). –

2

Trình khởi chạy tĩnh sẽ được gọi khi lớp được tải, thường là khi nó được 'đề cập' lần đầu tiên. Vì vậy, gọi một phương thức tĩnh sẽ thực sự kích hoạt bộ khởi tạo nếu đây là lần đầu tiên lớp được tham chiếu.

Bạn có chắc chắn ngoại lệ con trỏ null là từ allcountries.get() và không phải từ số không Country được trả về bởi get()? Nói cách khác, bạn có chắc chắn là đối tượng nào không?

+0

Vâng, tôi chắc chắn ngoại lệ được kích hoạt do bản đồ rỗng. –

2

Về mặt lý thuyết, khối tĩnh sẽ được thực thi bởi trình nạp lớp thời gian sẽ tải lớp đó.

Country country = Country.findByISO("RO"); 
^ 

Trong mã của bạn, nó được khởi tạo lần đầu tiên bạn đề cập đến lớp Quốc gia (có thể là dòng ở trên).

Tôi chạy này:

public class Country { 
    private static Map<String, Country> allCountries; 
    static { 
     allCountries = new HashMap<String, Country>(); 
     allCountries.put("RO", new Country()); 
    } 

    public static Country findByISO(String cc) { 
     return allCountries.get(cc); 
    } 
} 

với điều này:

public class Start 
{ 
    public static void main(String[] args){ 
     Country country = Country.findByISO("RO"); 
     System.out.println(country); 
    } 
} 

và tất cả mọi thứ đã làm việc một cách chính xác. Bạn có thể đăng dấu vết ngăn xếp của lỗi không?

Tôi sẽ nói rằng vấn đề nằm trong thực tế là khối tĩnh được khai báo trước trường thực tế.

0

Bạn có allCountries = new HashMap(); trong khối khởi tạo tĩnh của mình không? Khối khởi tạo tĩnh thực sự là called khi class initialization.

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