Tại sao các lớp Java bên ngoài có thể truy cập các thành viên riêng của lớp bên trong?


177

Tôi đã quan sát thấy rằng các lớp bên ngoài có thể truy cập các biến thể hiện riêng tư của các lớp bên trong. Sao có thể như thế được? Đây là một mã mẫu chứng minh tương tự:

class ABC{
    class XYZ{
        private int x=10;
    }

    public static void main(String... args){
        ABC.XYZ xx = new ABC().new XYZ();
        System.out.println("Hello :: "+xx.x); ///Why is this allowed??
    }
}

Tại sao hành vi này được cho phép?


Câu hỏi này làm tôi bối rối khá lâu cho đến khi tôi thấy bình luận ... điều đó giải thích tại sao tôi không thể truy cập xx.x trên máy của mình ..
Wang Sheng

4
Các ý kiến ​​làm tôi bối rối, tôi chạy đoạn mã trên trong Java 8, nó biên dịch và chạy. xx.x có thể được truy cập.
leon

Harish, bạn có thể vui lòng không chấp nhận câu trả lời được chấp nhận (không trả lời câu hỏi bạn đã hỏi) và thay vào đó chấp nhận câu trả lời của Martin Andersson bên dưới, câu trả lời rất kỹ lưỡng không?
tạm

FWIW cú pháp này hoàn toàn khủng khiếp:new ABC().new XYZ()
Josh M.

Câu trả lời:


80

Lớp bên trong chỉ là một cách để phân tách rõ ràng một số chức năng thực sự thuộc về lớp bên ngoài ban đầu. Chúng được dự định sẽ được sử dụng khi bạn có 2 yêu cầu:

  1. Một số chức năng trong lớp bên ngoài của bạn sẽ rõ ràng nhất nếu nó được thực hiện trong một lớp riêng biệt.
  2. Mặc dù nó thuộc một lớp riêng biệt, chức năng này được liên kết rất chặt chẽ với cách mà lớp bên ngoài hoạt động.

Với các yêu cầu này, các lớp bên trong có toàn quyền truy cập vào lớp bên ngoài của chúng. Vì về cơ bản họ là thành viên của lớp bên ngoài, nên có ý nghĩa rằng họ có quyền truy cập vào các phương thức và thuộc tính của lớp bên ngoài - bao gồm cả tư nhân.


217
Câu trả lời này giải thích tại sao các lớp lồng nhau có quyền truy cập vào các thành viên tư nhân của lớp bên ngoài của họ. Nhưng câu hỏi là tại sao lớp bên ngoài có quyền truy cập vào các thành viên tư nhân của các lớp lồng nhau.
Andrew

13
Chỉ cần thêm "và ngược lại" vào Đưa ra các yêu cầu này, các lớp bên trong có toàn quyền truy cập vào lớp bên ngoài của chúng và bây giờ nó trả lời câu hỏi.
anthropomo

13
Đây không phải là câu trả lời chính xác cho vấn đề này, đây là: stackoverflow.com/questions/19747812/ Khăn
Colin Su

4
@anthropomo: Không, không. Cả hai yêu cầu đều hoàn toàn khả thi mà không có lớp bên ngoài nào có quyền truy cập vào các thành viên tư nhân của các lớp bên trong.
HOẶC Mapper

Một ví dụ điển hình trong đó điều này đặc biệt hữu ích là Mẫu xây dựng, stackoverflow.com/a/1953567/1262000 . Lớp cha chỉ cần một hàm tạo có một Builder và truy cập tất cả các biến thành viên của nó. Nếu không, bạn sẽ cần phải có một hàm tạo riêng trong lớp cha với tất cả các biến thành viên riêng.
vikky.rk

62

Nếu bạn muốn ẩn các thành viên riêng của lớp bên trong, bạn có thể xác định Giao diện với các thành viên công cộng và tạo một lớp bên trong ẩn danh thực hiện giao diện này. Ví dụ dưới đây:

class ABC{
    private interface MyInterface{
         void printInt();
    }

    private static MyInterface mMember = new MyInterface(){
        private int x=10;

        public void printInt(){
            System.out.println(String.valueOf(x));
        }
    };

    public static void main(String... args){
        System.out.println("Hello :: "+mMember.x); ///not allowed
        mMember.printInt(); // allowed
    }
}

Đây là một đoạn mã tuyệt vời. Đúng thứ tôi cần. Cảm ơn!
kevinarpe

Vui lòng cung cấp mã có thể chạy. Điều gì làm hài lòng hơn lý do tại sao biến riêng tư không thể được phép truy cập.
androidyue

7
Nhưng sau đó ... lớp bên trong là ẩn danh. Bạn không thể tạo một số phiên bản của lớp bên trong đó hoặc sử dụng lớp bên trong đó cho bất kỳ khai báo biến nào, v.v.
HOẶC Mapper

@OR Mapper đó là lý do tại sao ngay cả khi xcông khai ở đây, nó không được phép như thế mMember.x.
MAC

54

Lớp bên trong là (cho mục đích kiểm soát truy cập) được coi là một phần của lớp chứa. Điều này có nghĩa là truy cập đầy đủ cho tất cả các tư nhân.

Cách thức này được thực hiện là sử dụng các phương thức bảo vệ gói tổng hợp: Lớp bên trong sẽ được biên dịch thành một lớp riêng trong cùng một gói (ABC $ XYZ). JVM không hỗ trợ trực tiếp mức cô lập này, do đó, ở mức ABC $ XYZ sẽ có các phương thức được bảo vệ gói mà lớp bên ngoài sử dụng để đến các phương thức / trường riêng.


17

Có một câu trả lời đúng xuất hiện trên một câu hỏi khác tương tự như sau: Tại sao thành viên riêng của một lớp lồng có thể được truy cập bằng các phương thức của lớp kèm theo?

Nó nói có một định nghĩa về phạm vi riêng tư trên JLS - Xác định khả năng truy cập :

Mặt khác, nếu thành viên hoặc nhà xây dựng được khai báo là riêng tư, thì quyền truy cập được cho phép khi và chỉ khi nó xảy ra trong phần thân của lớp cấp cao nhất (§7.6) kèm theo khai báo của thành viên hoặc nhà xây dựng.


Tôi nghĩ đoạn trích này có thể trả lời độc đáo câu hỏi của tôi.
shen

5

Một trường hợp sử dụng quan trọng IMHO cho các lớp bên trong là mẫu nhà máy. Lớp kèm theo có thể chuẩn bị một thể hiện của lớp bên trong với các hạn chế truy cập và chuyển thể hiện ra thế giới bên ngoài, nơi quyền truy cập riêng sẽ được tôn vinh.

Mâu thuẫn với abyx khi khai báo lớp tĩnh không thay đổi các hạn chế truy cập đối với lớp kèm theo, như được hiển thị bên dưới. Ngoài ra, các hạn chế truy cập giữa các lớp tĩnh trong cùng một lớp kèm theo đang hoạt động. Tôi đã rất ngạc nhiên ...

class MyPrivates {
    static class Inner1 { private int test1 = 2; }
    static class Inner2 { private int test2 = new Inner1().test1; }

    public static void main(String[] args) {
        System.out.println("Inner : "+new Inner2().test2);
    }
}

1
Bình luận đẹp nhưng không có câu trả lời.
ceving

@cerving Đây thực sự là câu trả lời duy nhất cho phép tôi thấy một cách sử dụng thực tế thực tế của quyết định thiết kế kỳ quái này. Câu hỏi đặt ra là tại sao nó lại được quyết định theo cách này và đây là một lý do tuyệt vời - một minh chứng cho sự khác biệt giữa những gì bạn có thể muốn lớp bên ngoài truy cập trong lớp bên trong và những gì bạn muốn các lớp không liên quan khác truy cập.
et_l

3

Hạn chế truy cập được thực hiện trên cơ sở mỗi lớp. Không có cách nào để một phương thức được khai báo trong một lớp không thể truy cập tất cả các thành viên thể hiện / lớp. Điều này có nghĩa là lý do rằng các lớp bên trong cũng có quyền truy cập vào các thành viên của lớp bên ngoài và lớp bên ngoài có quyền truy cập vào các thành viên của lớp bên trong.

Bằng cách đặt một lớp bên trong một lớp khác, bạn đang làm cho nó gắn chặt với việc thực hiện và bất kỳ thứ gì là một phần của việc thực hiện nên có quyền truy cập vào các phần khác.


3

Logic đằng sau các lớp bên trong là nếu bạn tạo một lớp bên trong trong lớp bên ngoài, thì đó là vì họ sẽ cần chia sẻ một vài điều, và do đó, thật hợp lý khi họ có thể linh hoạt hơn các lớp "thông thường".

Nếu trong trường hợp của bạn, các lớp có thể nhìn thấy hoạt động bên trong của nhau - điều đó có nghĩa là lớp bên trong đơn giản có thể được tạo thành một lớp thông thường, bạn có thể khai báo lớp bên trong là static class XYZ. Sử dụng staticsẽ có nghĩa là họ sẽ không chia sẻ trạng thái (và, ví dụ, new ABC().new XYZ()sẽ không hoạt động và bạn sẽ cần sử dụng new ABC.XYZ().
Nhưng, nếu đó là trường hợp, bạn nên suy nghĩ về việc có XYZnên thực sự là một lớp bên trong và có lẽ nó xứng đáng với chính nó Đôi khi, thật hợp lý khi tạo một lớp bên trong tĩnh (ví dụ: nếu bạn cần một lớp nhỏ thực hiện giao diện mà lớp bên ngoài của bạn đang sử dụng và điều đó sẽ không hữu ích ở bất kỳ nơi nào khác). Nhưng khoảng một nửa thời gian nó đã được thực hiện một lớp bên ngoài.


2
Lớp bên ngoài cũng có thể truy cập các thành viên riêng của các lớp bên trong tĩnh , vì vậy điều này không liên quan gì đến tĩnh . Bạn nói "thật vô nghĩa khi các lớp có thể nhìn thấy hoạt động bên trong của nhau", nhưng điều đó không nhất thiết phải như vậy - điều gì sẽ hợp lý khi lớp bên trong nhìn thấy hoạt động bên trong của lớp bên ngoài, nhưng không phải là phó ngược lại?
HOẶC Mapper

3

Thilo thêm một câu trả lời hay cho câu hỏi đầu tiên của bạn "Làm thế nào điều này có thể?". Tôi muốn giải thích một chút về câu hỏi thứ hai: Tại sao hành vi này được cho phép?

Đối với người mới bắt đầu, chúng ta hãy hoàn toàn rõ ràng rằng hành vi này không giới hạn ở các lớp bên trong, mà theo định nghĩa là các kiểu lồng nhau không tĩnh. Hành vi này được cho phép đối với tất cả các loại lồng nhau, bao gồm các enum và giao diện lồng nhau phải tĩnh và không thể có một thể hiện kèm theo. Về cơ bản, mô hình là một sự đơn giản hóa theo tuyên bố sau: Mã lồng nhau có toàn quyền truy cập vào mã kèm theo - và ngược lại.

Vậy, tại sao sau đó? Tôi nghĩ rằng một ví dụ minh họa điểm tốt hơn.

Hãy nghĩ về cơ thể và bộ não của bạn. Nếu bạn tiêm heroin vào cánh tay, não của bạn sẽ cao lên. Nếu vùng amygdala trong não của bạn thấy những gì anh ta tin là mối đe dọa đối với sự an toàn cá nhân của bạn, ví dụ như một con ong bắp cày, anh ta sẽ khiến cơ thể bạn quay ngược lại và chạy lên đồi mà bạn không "nghĩ" về điều đó.

Vì vậy, bộ não là một phần nội tại của cơ thể - và thật kỳ lạ, cách khác cũng vậy. Sử dụng kiểm soát truy cập giữa các thực thể có liên quan chặt chẽ như vậy sẽ từ bỏ yêu cầu về mối quan hệ của họ. Nếu bạn cần kiểm soát truy cập, thì bạn cần tách các lớp thành nhiều đơn vị thực sự khác biệt. Cho đến lúc đó, họ là cùng một đơn vị. Một ví dụ lái xe cho các nghiên cứu tiếp theo sẽ là xem xét cách Java Iteratorthường được triển khai.

Truy cập không giới hạn từ mã kèm theo đến mã lồng nhau, phần lớn, khá vô dụng khi thêm các sửa đổi truy cập vào các trường và phương thức của loại lồng nhau. Làm như vậy là thêm lộn xộn và có thể cung cấp một cảm giác an toàn sai lầm cho những người mới sử dụng ngôn ngữ lập trình Java.


1
Đây thực sự sẽ là câu trả lời được chấp nhận. Vì vậy, rõ ràng và kỹ lưỡng. Thay vào đó, câu trả lời được chấp nhận thậm chí không giải quyết câu hỏi.
tạm

IMO điều này vẫn không trả lời câu hỏi là tại sao chúng ta không thể dễ dàng thêm các trường riêng vào một lớp bên trong, mà lớp bên ngoài không thể truy cập trực tiếp. Trừ khi tôi sai, điều này làm hỏng một trong những trường hợp chính cho các lớp bên trong - tạo ra các kiểu "giống như cấu trúc" tồn tại trong thời gian ngắn. FWIW C # vui vẻ hỗ trợ điều này: repl.it/repls/VengiouslyCheeryInverse
Josh M.

-1

Lớp bên trong được coi là một thuộc tính của lớp ngoài. Do đó, bất kể biến đối tượng của lớp bên trong là riêng tư hay không, lớp ngoài có thể truy cập mà không gặp vấn đề gì giống như truy cập các thuộc tính riêng tư (biến) khác của nó.

class Outer{

private int a;

class Inner{
private int b=0;
}

void outMethod(){
a = new Inner().b;
}
}

-2

Bởi vì main()phương thức của bạn nằm trong ABClớp, có thể truy cập lớp bên trong của chính nó.


2
Câu hỏi không phải là liệu các thành viên của ABClớp có thể truy cập các lớp được lồng trong ABClớp hay không, mà tại sao họ có thể truy cập các thành viên riêng của các lớp được lồng trong ABClớp trong Java.
HOẶC Mapper

Tôi trả lời câu hỏi cùng ngày nó được hỏi. Ai đó đã chỉnh sửa câu hỏi 2 năm sau, và downvote đến 3 năm sau. Tôi khá chắc chắn rằng bất cứ ai chỉnh sửa câu hỏi đã thay đổi từ ngữ của câu hỏi hoàn toàn quá nhiều.
aberrant80
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.