Tại sao Java cấm các trường tĩnh trong các lớp bên trong?


85
class OuterClass {
 class InnerClass {
  static int i = 100; // compile error
  static void f() { } // compile error
 }
} 

Mặc dù không thể truy cập trường tĩnh bằng OuterClass.InnerClass.i, nhưng nếu tôi muốn ghi lại nội dung nào đó phải là tĩnh, ví dụ như số lượng đối tượng InnerClass được tạo, sẽ rất hữu ích nếu đặt trường đó ở trạng thái tĩnh. Vậy tại sao Java lại cấm các trường / phương thức tĩnh trong các lớp bên trong?

CHỈNH SỬA: Tôi biết cách làm cho trình biên dịch hài lòng với lớp lồng nhau tĩnh (hoặc lớp bên trong tĩnh), nhưng điều tôi muốn biết là tại sao java cấm các trường / phương thức tĩnh bên trong các lớp bên trong (hoặc lớp bên trong bình thường) khỏi cả thiết kế ngôn ngữ và các khía cạnh thực hiện, nếu ai đó biết thêm về nó.


3
Ví dụ yêu thích của tôi là có một Logger chỉ cho lớp bên trong. Nó không thể tĩnh như tất cả các Trình ghi nhật ký khác.
Piotr Findeisen

Câu trả lời:


32

Ý tưởng đằng sau các lớp bên trong là hoạt động trong ngữ cảnh của cá thể bao quanh. Bằng cách nào đó, việc cho phép các biến và phương thức tĩnh lại mâu thuẫn với động cơ này?

8.1.2 Các lớp bên trong và các trường hợp bao bọc

Một lớp bên trong là một lớp lồng nhau không được khai báo tĩnh một cách rõ ràng hoặc ngầm định. Các lớp bên trong có thể không khai báo các bộ khởi tạo tĩnh (§8.7) hoặc các giao diện thành viên. Các lớp bên trong không được khai báo các thành viên tĩnh, trừ khi chúng là các trường hằng số thời gian biên dịch (§15.28).


18
có lẽ nó chỉ được quyết định như vậy
Gregory Pakosz

3
bạn không thể khởi tạo nội không tĩnh mà không có tham chiếu cha, nhưng bạn vẫn có thể khởi tạo nó.
skaffman

Nếu ClassLoader giữ một bộ nhớ đệm cho biết "Lớp X đã được khởi tạo", thì logic của chúng không thể được sử dụng để khởi tạo nhiều phiên bản của Lớp [đối tượng đại diện] X (đó là điều cần thiết khi các đối tượng Lớp phải được khởi tạo dưới dạng các lớp bên trong bên trong một số đối tượng riêng biệt).
Erwin Smout

@skaffman Nó vẫn không có ý nghĩa. Các thuộc tính tĩnh của lớp bên trong sẽ chỉ được khởi tạo một lần, vậy vấn đề sẽ là gì? Hiện tại, tôi có một bản đồ băm tĩnh và tôi có khoảng 4 phương thức chỉ thao tác với bản đồ này, khiến việc nhóm mọi thứ trong một lớp bên trong trở nên lý tưởng hơn. Tuy nhiên, hashmap tĩnh bây giờ sẽ phải sống bên ngoài, và có thể là những thứ khác liên quan, điều này thật ngu ngốc. Vấn đề với việc khởi tạo thuộc tính tĩnh là gì?
mmm,

54

điều tôi muốn biết là tại sao java cấm các trường / phương thức tĩnh bên trong các lớp bên trong

Bởi vì các lớp bên trong đó là các lớp bên trong "thể hiện". Có nghĩa là, chúng giống như một thuộc tính thể hiện của đối tượng bao quanh.

Vì chúng là các lớp "cá thể", không có ý nghĩa gì khi cho phép staticcác tính năng, vì staticcó nghĩa là hoạt động mà không có cá thể ngay từ đầu.

Nó giống như bạn cố gắng tạo một thuộc tính static / instance cùng một lúc.

Lấy ví dụ sau:

class Employee {
    public String name;
}

Nếu bạn tạo hai trường hợp nhân viên:

Employee a = new Employee(); 
a.name = "Oscar";

Employee b = new Employee();
b.name = "jcyang";

Rõ ràng là tại sao mỗi cái lại có giá trị tài sản riêng name, đúng không?

Điều tương tự cũng xảy ra với lớp bên trong; mỗi cá thể lớp bên trong độc lập với cá thể lớp bên trong khác.

Vì vậy, nếu bạn cố gắng tạo một counterthuộc tính lớp, không có cách nào để chia sẻ giá trị đó trên hai trường hợp khác nhau.

class Employee {
    public String name;
    class InnerData {
        static count; // ??? count of which ? a or b? 
     }
}

Khi bạn tạo phiên bản abtrong ví dụ trên, giá trị đúng cho biến static sẽ là countbao nhiêu? Không thể xác định nó, vì sự tồn tại của InnerDatalớp phụ thuộc hoàn toàn vào từng đối tượng bao quanh.

Đó là lý do tại sao, khi lớp được khai báo là static, nó không cần một cá thể sống nữa, để tự sống. Bây giờ không có phụ thuộc, bạn có thể tự do khai báo một thuộc tính tĩnh.

Tôi nghĩ điều này nghe có vẻ lặp lại nhưng nếu bạn nghĩ về sự khác biệt giữa các thuộc tính instance so với class, nó sẽ có ý nghĩa.


4
Tôi sẽ mua lời giải thích của bạn cho các thuộc tính tĩnh của lớp bên trong, nhưng khi @skaffman chỉ ra trong một nhận xét cho câu trả lời của tôi, còn phương thức tĩnh thì sao? Có vẻ như các phương thức nên được cho phép mà không bắt buộc phải tách chúng khỏi bất kỳ trường hợp nào. Thật vậy, trong java, bạn có thể gọi các phương thức tĩnh trên các cá thể (mặc dù nó được coi là kiểu xấu). BTW: Tôi hỏi một đồng nghiệp để cố gắng biên dịch mã của OP như C # và nó không biên dịch. Vì vậy, C # rõ ràng cho phép điều này chứng tỏ rằng những gì OP muốn làm không vi phạm một số nguyên tắc cơ bản của OO.
Asaph

2
Xảy ra hoàn toàn giống với các phương pháp. Vấn đề ở đây không phải là thuộc tính hoặc các phương pháp là statis hay không, nhưng thực tế các lớp bên trong nó tự là trường hợp "công cụ". Ý tôi là, vấn đề ở đây là một thể hiện của lớp bên trong như vậy không tồn tại cho đến khi lớp bên ngoài được tạo. Do đó, phương pháp nào sẽ được gửi đi sau đó nếu không có gì. Bạn sẽ phát sóng chỉ vì bạn sẽ cần một phiên bản ngay từ đầu.
OscarRyz

1
Về C # ... tốt. Nó không hợp lệ OO chỉ vì C # cho phép nó, tôi không có nghĩa là nó sai, nhưng C # bao gồm một số mô hình để giúp việc phát triển dễ dàng hơn ngay cả với chi phí nhất quán (bạn phải học những điều mới với mỗi bản phát hành .NET) và nó cho phép điều này và những thứ khác trong số những thứ khác. Tôi nghĩ đó là một điều tốt. Nếu cộng đồng cảm thấy một tính năng bổ sung đủ thú vị, C # có thể có nó trong tương lai.
OscarRyz

2
@OscarRyz Tại sao bạn cần các thể hiện của lớp bên trong để sử dụng các phương thức / trường tĩnh của nó ? Và một ví dụ về phương thức static [hữu ích] trong lớp bên trong phương thức private helper.
Leonid Semyonov

1
Với việc sử dụng final, các trường tĩnh được phép trong lớp bên trong trong java. Bạn giải thích thế nào về viễn cảnh này?
Number945

34

InnerClasskhông thể có staticthành viên vì nó thuộc về một thể hiện (of OuterClass). Nếu bạn khai báo InnerClassstatictách nó khỏi phiên bản, mã của bạn sẽ được biên dịch.

class OuterClass {
    static class InnerClass {
        static int i = 100; // no compile error
        static void f() { } // no compile error
    }
}

BTW: Bạn vẫn có thể tạo các phiên bản của InnerClass. statictrong ngữ cảnh này cho phép điều đó xảy ra mà không có trường hợp kèm theo OuterClass.


6
InnerClasskhông không thuộc về OuterClass, trường hợp của nó làm. Bản thân hai giai cấp không có mối quan hệ như vậy. Câu hỏi tại sao bạn không thể có các phương thức tĩnh trong tĩnh InnerClassvẫn còn.
skaffman

9

Trên thực tế, bạn có thể khai báo các trường tĩnh nếu chúng là hằng số và được viết trong thời gian biên dịch.

class OuterClass {
    void foo() {
        class Inner{
            static final int a = 5; // fine
            static final String s = "hello"; // fine
            static final Object o = new Object(); // compile error, because cannot be written during compilation
        }
    }
}

8
  1. lớp Trình tự khởi tạo là một lý do quan trọng.

Vì các lớp bên trong phụ thuộc vào thể hiện của lớp enclosing / Outer, vì vậy lớp Outer cần được khởi tạo trước khi khởi tạo lớp Inner.
Đây là JLS nói về Khởi tạo lớp. Điểm chúng ta cần là, lớp T sẽ được khởi tạo nếu

  • Một trường tĩnh do T khai báo được sử dụng và trường không phải là một biến hằng số.

Vì vậy, nếu lớp bên trong có một truy cập trường tĩnh sẽ gây ra việc khởi tạo lớp bên trong, nhưng điều đó sẽ không đảm bảo rằng lớp bao quanh được khởi tạo.

  1. Nó sẽ vi phạm một số quy tắc cơ bản . bạn có thể bỏ qua phần cuối cùng (đến two cases) để tránh nội dung noob

Một điều về , khi một số là nó sẽ hoạt động giống như một lớp học bình thường về mọi mặt và nó được kết hợp với lớp Outer.static nested classnested classstatic

Nhưng khái niệm Inner class/ là nó sẽ được liên kết với lớp ngoài / bao quanh. Xin lưu ý rằng liên kết với trường hợp không phải lớp. Bây giờ việc liên kết với instance rõ ràng có nghĩa là ( từ khái niệm biến instance ) nó sẽ tồn tại bên trong một instance và sẽ khác nhau giữa các instance. non-static nested classinstance

Bây giờ, khi chúng tôi tạo một cái gì đó tĩnh, chúng tôi mong đợi nó sẽ được khởi tạo khi lớp đang được tải và sẽ được chia sẻ giữa tất cả các phiên bản. Nhưng vì không phải là tĩnh, ngay cả bản thân các lớp bên trong ( bạn chắc chắn có thể quên phiên bản của lớp bên trong bây giờ ) không được chia sẻ với tất cả các phiên bản của lớp bên ngoài / bao quanh ( ít nhất là về mặt khái niệm ), thì làm sao chúng ta có thể mong đợi rằng một số biến của lớp bên trong sẽ được chia sẻ giữa tất cả các thể hiện của lớp bên trong.

Vì vậy, nếu Java cho phép chúng ta sử dụng biến tĩnh bên trong không phải lớp lồng nhau tĩnh. sẽ có hai trường hợp .

  • Nếu nó được chia sẻ với tất cả thể hiện của lớp bên trong, nó sẽ vi phạm khái niệm về context of instance(biến thể hiện). Đó là KHÔNG.
  • Nếu nó không được chia sẻ với tất cả các instance, nó sẽ vi phạm khái niệm tĩnh. Một lần nữa KHÔNG.

5

Đây là động lực mà tôi thấy phù hợp nhất cho "giới hạn" này: Bạn có thể triển khai hành vi của một trường tĩnh của một lớp bên trong như một trường thể hiện của đối tượng bên ngoài; Vì vậy, bạn không cần các trường / phương thức tĩnh . Hành vi mà tôi muốn nói là tất cả các thể hiện lớp bên trong của một số đối tượng dùng chung một trường (hoặc phương thức).

Vì vậy, giả sử bạn muốn đếm tất cả các cá thể của lớp bên trong, bạn sẽ làm như sau:

public class Outer{
    int nofInner; //this will count the inner class 
                  //instances of this (Outer)object
                  //(you know, they "belong" to an object)
    static int totalNofInner; //this will count all 
                              //inner class instances of all Outer objects
    class Inner {
        public Inner(){
            nofInner++;
            totalNofInner++;
        }
    }
}

2
Nhưng câu hỏi đặt ra là lý do cho phép các trường tĩnh khi chúng được khai báo finallà gì?
Solace

nếu bạn nhìn vào [ stackoverflow.com/a/1954119/1532220] (câu trả lời của OscarRyzs ở trên): Động lực của anh ấy là một giá trị không thể được liên kết với biến. Tất nhiên, nếu biến là cuối cùng, bạn có thể khá dễ dàng biết giá trị nào cần gán (bạn phải biết).
ianos

2

Nói cách đơn giản, các lớp bên trong không tĩnh là biến thể hiện cho lớp bên ngoài và chúng chỉ được tạo khi một lớp bên ngoài được tạo và một đối tượng lớp bên ngoài được tạo tại thời điểm chạy trong khi các biến tĩnh được tạo vào thời gian tải lớp. Vì vậy, lớp bên trong không tĩnh là thứ thời gian chạy, đó là lý do tại sao static không phải là một phần của lớp bên trong không tĩnh.

LƯU Ý: coi các lớp bên trong luôn giống như một biến đối với lớp bên ngoài, chúng có thể là tĩnh hoặc không tĩnh như bất kỳ biến nào khác.


Nhưng lớp bên trong có thể có static finalhằng số.
Mưa


1

Bởi vì nó sẽ gây ra sự mơ hồ trong ý nghĩa của "tĩnh".

Các lớp bên trong không thể khai báo các thành viên tĩnh ngoài các hằng số thời gian biên dịch. Sẽ có sự mơ hồ về ý nghĩa của "tĩnh". Nó có nghĩa là chỉ có một phiên bản trong máy ảo? Hay chỉ một thể hiện cho mỗi đối tượng bên ngoài? Các nhà thiết kế ngôn ngữ quyết định không giải quyết vấn đề này.

Lấy từ "Core Java SE 9 cho người thiếu kiên nhẫn" của Cay S. Horstmann. Trang 90 Chương 2.6.3


-1

Tôi đoán đó là sự nhất quán. Mặc dù dường như không có bất kỳ giới hạn kỹ thuật nào đối với nó, nhưng bạn sẽ không thể truy cập các thành viên tĩnh của lớp nội bộ từ bên ngoài, tức là OuterClass.InnerClass.ivì bước giữa không phải là tĩnh.


Nhưng lớp bên trong có thể có static finalhằng số.
Mưa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.