Java: Khai báo nhiều lớp trong một tệp


238

Trong Java, bạn có thể định nghĩa nhiều lớp cấp cao nhất trong một tệp, với điều kiện là nhiều nhất một trong số này là công khai (xem JLS §7.6 ). Xem dưới đây ví dụ.

  1. Có một tên gọn gàng cho kỹ thuật này (tương tự như inner, nested, anonymous)?

  2. JLS cho biết hệ thống có thể thực thi các hạn chế mà các lớp thứ cấp này không thể referred to by code in other compilation units of the package, ví dụ, chúng không thể được coi là gói riêng. Đó thực sự là một cái gì đó thay đổi giữa các triển khai Java?

ví dụ: PublicClass.java:

package com.example.multiple;

public class PublicClass {
    PrivateImpl impl = new PrivateImpl();
}

class PrivateImpl {
    int implementationData;
}

11
+1 Câu hỏi hay. Tôi chưa bao giờ thực sự suy nghĩ nhiều về vấn đề này, vì gần như không bao giờ cần phải làm điều này.
Michael Myers

12
lưu ý rằng đây là một tính năng di tích; sẽ không bao giờ có thể nếu java có các lớp lồng nhau ngay từ đầu.
Kevin Bourrillion

Câu trả lời:


120

Tên gợi ý của tôi cho kỹ thuật này (bao gồm nhiều lớp cấp cao nhất trong một tệp nguồn) sẽ là "mớ hỗn độn". Nghiêm túc mà nói, tôi không nghĩ đó là một ý tưởng hay - thay vào đó tôi sẽ sử dụng một loại lồng nhau trong tình huống này. Sau đó, thật dễ dàng để dự đoán tập tin nguồn nào. Tôi không tin rằng có một thuật ngữ chính thức cho phương pháp này.

Về việc điều này có thực sự thay đổi giữa các lần triển khai hay không - tôi rất nghi ngờ điều đó, nhưng nếu bạn tránh thực hiện nó ngay từ đầu, bạn sẽ không bao giờ cần phải quan tâm :)


71
Tôi không phải là người đánh giá thấp, nhưng thực tế câu trả lời này là một thứ có thể được gọi là "quy chuẩn" (nghĩa là "bạn nên" thay vì "trên thực tế ... tuy nhiên ...") là lý do rất có thể cho nó nhận được một downvote tôi nghĩ. Nó không thực sự trả lời bất kỳ câu hỏi. Giống như đưa ra một ngoại lệ không liên quan thay vì trả lại bất cứ điều gì / đưa ra một ngoại lệ có thông tin về các sự kiện thực tế thay vì ý kiến.
n611x007

6
Tôi đã tìm thấy những gì tôi nghĩ là một ngoại lệ nhỏ đối với đề xuất của @JonSkeet để sử dụng loại lồng nhau (mà tôi có thể đồng ý với nó): nếu lớp chính là chung và tham số loại là lớp thứ hai, lớp thứ hai không thể được lồng Và nếu hai lớp được liên kết chặt chẽ với nhau (như PublicClass và PrivateImpl trong câu hỏi), tôi nghĩ rằng nên đặt PrivateImpl thành một lớp cấp cao nhất trong cùng một tệp.
jfritz42

6
@BoomerRogers: Không, đây chắc chắn không phải là "cơ sở cốt lõi của lập trình dựa trên thành phần". Nếu bạn đang lập trình chống lại một thành phần, tại sao bạn lại quan tâm đến cách mã nguồn được tổ chức? (Cá nhân tôi thích tiêm phụ thuộc hơn là mẫu định vị dịch vụ, nhưng đó là một vấn đề khác.) API riêng biệt và tổ chức mã nguồn trong tâm trí bạn - chúng là những thứ rất khác nhau.
Jon Skeet

1
@JonSkeet Hãy để tôi nói lại: "Câu trả lời" của bạn là một ý kiến ​​cá nhân không liên quan. (tức là các câu trả lời như "lộn xộn" và "tôi nghi ngờ" có rất ít giá trị.) Vì vậy, bài đăng của bạn không trả lời bất kỳ câu hỏi nào trong số 2 câu hỏi được đặt ra. Kiểm tra câu trả lời của đa gen, và bạn sẽ thấy anh ta quản lý để trả lời cả hai.
bvdb

1
@bvdb: (Và có rất nhiều điều không hay nhưng được thông số kỹ thuật cho phép. Tôi sẽ khuyên mọi người đừng viết public int[] foo(int x)[] { return new int[5][5]; }như vậy, mặc dù điều đó hợp lệ.)
Jon Skeet

130

javac không chủ động cấm điều này, nhưng nó có một hạn chế khá nhiều có nghĩa là bạn sẽ không bao giờ muốn tham khảo một lớp cấp cao nhất từ ​​một tệp khác trừ khi nó có cùng tên với tệp.

Giả sử bạn có hai tệp, Foo.java và Bar.java.

Foo.java chứa:

  • lớp học công cộng Foo

Bar.java chứa:

  • lớp học công cộng
  • lớp Baz

Chúng ta cũng nói rằng tất cả các lớp nằm trong cùng một gói (và các tệp nằm trong cùng một thư mục).

Điều gì xảy ra nếu Foo.java đề cập đến Baz nhưng không phải là Bar và chúng tôi cố gắng biên dịch Foo.java? Việc biên dịch thất bại với một lỗi như thế này:

Foo.java:2: cannot find symbol
symbol  : class Baz
location: class Foo
  private Baz baz;
          ^
1 error

Điều này có ý nghĩa nếu bạn nghĩ về nó. Nếu Foo.java đề cập đến Baz, nhưng không có Baz.java (hoặc Baz. Class), làm thế nào javac có thể biết tệp nguồn nào cần tìm?

Thay vào đó, nếu bạn yêu cầu javac biên dịch Foo.java và Bar.java cùng một lúc hoặc ngay cả khi bạn đã biên dịch Bar.java trước đó (để lại Baz. Class nơi javac có thể tìm thấy nó) thì lỗi này sẽ biến mất. Điều này làm cho quá trình xây dựng của bạn cảm thấy rất không đáng tin cậy và không ổn định, tuy nhiên.

Bởi vì giới hạn thực tế, giống như "không đề cập đến một lớp cấp cao nhất từ ​​một tệp khác trừ khi nó có cùng tên với tệp đó hoặc bạn cũng đang đề cập đến một lớp trong cùng một tệp có tên đó điều tương tự như tệp "rất khó theo dõi, mọi người thường đi theo quy ước đơn giản hơn (mặc dù chặt chẽ hơn) về việc chỉ đưa một lớp cấp cao nhất vào mỗi tệp. Điều này cũng tốt hơn nếu bạn thay đổi suy nghĩ về việc một lớp học có nên công khai hay không.

Đôi khi thực sự có một lý do chính đáng tại sao mọi người làm điều gì đó theo một cách cụ thể.


Maven có làm gì để biên dịch đáng tin cậy không?
Alexanderr Dubinsky

23

Tôi tin rằng bạn chỉ đơn giản gọi PrivateImplnó là gì: a non-public top-level class. Bạn cũng có thể tuyên bố non-public top-level interfaceslà tốt.

ví dụ: các nơi khác trên SO: Lớp cấp cao nhất không công khai so với lớp lồng tĩnh

Về những thay đổi trong hành vi giữa các phiên bản, đã có cuộc thảo luận về điều gì đó "hoạt động hoàn hảo" trong 1.2.2. nhưng đã ngừng hoạt động trong 1.4 trong diễn đàn của Sun: Trình biên dịch Java - không thể khai báo một lớp cấp cao nhất không công khai trong một tệp .


1
Vấn đề duy nhất của tôi với điều này là bạn có thể có một non-public top level classlớp duy nhất trong một tệp, vì vậy nó không giải quyết được tính đa bội.
Michael Brewer-Davis

Tôi hiểu mối quan tâm, nhưng như bạn có thể thấy đây là một thuật ngữ mà những người khác đã sử dụng trong lịch sử. Nếu tôi phải tạo ra thuật ngữ của riêng mình, có lẽ tôi sẽ gọi nó secondary top level types.
đa sinh học

7

Bạn có thể có nhiều lớp như bạn muốn

public class Fun {
    Fun() {
        System.out.println("Fun constructor");
    }
    void fun() {
        System.out.println("Fun mathod");
    }
    public static void main(String[] args) {
        Fun fu = new Fun();
        fu.fun();
        Fen fe = new Fen();
        fe.fen();
        Fin fi = new Fin();
        fi.fin();
        Fon fo = new Fon();
        fo.fon();
        Fan fa = new Fan();
        fa.fan();
        fa.run();
    }
}

class Fen {
    Fen() {
        System.out.println("fen construuctor");

    }
    void fen() {
        System.out.println("Fen method");
    }
}

class Fin {
    void fin() {
        System.out.println("Fin method");
    }
}

class Fon {
    void fon() {
        System.out.println("Fon method");
    } 
}

class Fan {
    void fan() {
        System.out.println("Fan method");
    }
    public void run() {
        System.out.println("run");
    }
}

1
@Nenotlep Khi bạn thực hiện "cải thiện định dạng", thì vui lòng lưu ý rằng nó không gây rối với chính mã, như xóa dấu gạch chéo ngược.
Tom

3
Điều đó không trả lời câu hỏi.
ᴠɪɴᴄᴇɴᴛ

4

1. Có một tên gọn gàng cho kỹ thuật này (tương tự như bên trong, lồng nhau, ẩn danh)?

Bản demo đơn nhiều lớp.

2. JLS cho biết hệ thống có thể thực thi hạn chế rằng các lớp thứ cấp này không thể được gọi bằng mã trong các đơn vị biên dịch khác của gói, ví dụ, chúng không thể được coi là gói riêng. Đó thực sự là một cái gì đó thay đổi giữa các triển khai Java?

Tôi không biết bất kỳ hạn chế nào không có hạn chế đó - tất cả các trình biên dịch dựa trên tệp sẽ không cho phép bạn tham khảo các lớp mã nguồn trong các tệp không được đặt tên giống như tên lớp. (nếu bạn biên dịch một tệp nhiều lớp và đặt các lớp trên đường dẫn lớp, thì bất kỳ trình biên dịch nào cũng sẽ tìm thấy chúng)


1

Theo ấn bản Java hiệu quả thứ 2 (Mục 13):

"Nếu chỉ một lớp cấp cao nhất (hoặc giao diện) riêng tư chỉ được sử dụng bởi một lớp, hãy xem xét biến lớp cấp cao nhất thành lớp lồng riêng của lớp duy nhất sử dụng nó (Mục 22). Điều này làm giảm khả năng truy cập của nó từ tất cả các lớp trong gói của nó cho một lớp sử dụng nó. Nhưng điều quan trọng hơn nhiều là giảm khả năng truy cập của một lớp công khai vô cớ so với một lớp cấp cao nhất riêng tư gói: ... "

Lớp lồng nhau có thể là tĩnh hoặc không tĩnh dựa trên việc lớp thành viên có cần truy cập vào thể hiện kèm theo hay không (Mục 22).


OP không hỏi về các lớp lồng nhau.
charmoniumQ

0

Có, bạn có thể, với các thành viên tĩnh công khai trên một lớp công khai bên ngoài, như vậy:

public class Foo {

    public static class FooChild extends Z {
        String foo;
    }

    public static class ZeeChild extends Z {

    }

}

và một tệp khác tham chiếu ở trên:

public class Bar {

    public static void main(String[] args){

        Foo.FooChild f = new Foo.FooChild();
        System.out.println(f);

    }
}

đặt chúng trong cùng một thư mục Biên dịch với:

javac folder/*.java

và chạy với:

 java -cp folder Bar

Ví dụ đó không trả lời câu hỏi. Bạn đang đưa ra một ví dụ về các lớp tĩnh lồng nhau, không giống như có hai lớp cấp cao nhất được định nghĩa trong cùng một tệp.
Pedro García Medina

0

Chỉ cần FYI, nếu bạn đang sử dụng Java 11+, có một ngoại lệ cho quy tắc này: nếu bạn chạy tệp java trực tiếp ( không cần biên dịch ). Trong chế độ này, không có hạn chế đối với một lớp chung cho mỗi tệp. Tuy nhiên, lớp với mainphương thức phải là lớp đầu tiên trong tệp.


-5

Không. Bạn không thể. Nhưng nó rất có thể trong Scala:

class Foo {val bar = "a"}
class Bar {val foo = "b"}
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.