Java: Hiển thị gói phụ?


150

Tôi có hai gói trong dự án của mình: odp.projodp.proj.test. Có một số phương thức nhất định mà tôi muốn chỉ hiển thị cho các lớp trong hai gói này. Tôi có thể làm cái này như thế nào?

EDIT: Nếu không có khái niệm về một gói con trong Java, có cách nào khác không? Tôi có một số phương pháp nhất định mà tôi muốn chỉ có sẵn cho người thử nghiệm và các thành viên khác của gói đó. Tôi có nên ném mọi thứ vào cùng một gói không? Sử dụng phản xạ rộng?




2
Bên cạnh đó, các bài kiểm tra chỉ nên kiểm tra hành vi của các đối tượng của bạn như có thể quan sát được từ bên ngoài gói hàng. Truy cập các phương thức / lớp phạm vi gói từ các thử nghiệm của bạn cho tôi biết các thử nghiệm có thể là các triển khai thử nghiệm không phải là hành vi. Sử dụng một công cụ xây dựng như maven hoặc gradle, chúng sẽ giúp các bài kiểm tra của bạn dễ dàng chạy trong cùng một đường dẫn nhưng không được đưa vào bình cuối cùng (một điều tốt), do đó không cần phải có các tên gói khác nhau. Tuy nhiên, đặt chúng trong các gói riêng biệt là để thực thi rằng bạn không truy cập phạm vi riêng tư / mặc định và do đó chỉ kiểm tra api công khai.
derekv

3
Điều này có thể đúng nếu bạn đang làm việc theo cách hoàn toàn dựa trên hành vi và muốn các bài kiểm tra của bạn chỉ thực hiện kiểm tra hộp đen. Nhưng có thể có trường hợp việc thực hiện hành vi mong muốn đòi hỏi độ phức tạp cao không thể tránh khỏi. Trong trường hợp này, có thể tốt hơn khi chia phần triển khai thành các phần nhỏ hơn, đơn giản hơn (vẫn riêng tư để thực hiện) và viết một số bài kiểm tra đơn vị để thực hiện kiểm tra hộp trắng trên các đường dẫn khác nhau thông qua các phần này.
James Woods

Câu trả lời:


165

Bạn không thể. Trong Java không có khái niệm về một gói con, vì vậy odp.projodp.proj.testlà các gói hoàn toàn riêng biệt.


10
Mặc dù tôi thích nó theo cách này, nhưng thật khó hiểu khi hầu hết các IDE đặt các gói có cùng tên. Cảm ơn đã làm rõ.
JacksOnF1re

Điều này không hoàn toàn chính xác: JLS xác định các gói con, mặc dù ý nghĩa ngôn ngữ duy nhất họ có là cấm "đối với gói có gói con có cùng tên đơn giản như loại cấp cao nhất". Tôi vừa thêm một câu trả lời cho câu hỏi này giải thích điều này một cách chi tiết.
M. Justin

59

Tên của các gói của bạn gợi ý rằng ứng dụng ở đây là để thử nghiệm đơn vị. Mẫu điển hình được sử dụng là đặt các lớp bạn muốn kiểm tra và mã kiểm tra đơn vị trong cùng một gói (trong trường hợp của bạn odp.proj) nhưng trong các cây nguồn khác nhau. Vì vậy, bạn sẽ đặt các lớp học src/odp/projcủa bạn và mã kiểm tra của bạn test/odp/proj.

Java có công cụ sửa đổi truy cập "gói" là công cụ sửa đổi truy cập mặc định khi không được chỉ định (nghĩa là bạn không chỉ định công khai, riêng tư hoặc được bảo vệ). Với công cụ sửa đổi truy cập "gói", chỉ các lớp trong odp.projsẽ có quyền truy cập vào các phương thức. Nhưng hãy nhớ rằng trong Java, các công cụ sửa đổi truy cập không thể dựa vào để thực thi các quy tắc truy cập vì với sự phản ánh, mọi truy cập đều có thể. Công cụ sửa đổi truy cập chỉ mang tính gợi ý (trừ khi có trình quản lý bảo mật hạn chế).


11

Đây không phải là mối quan hệ đặc biệt giữa odp.projodp.proj.test- chúng chỉ được đặt tên là có liên quan rõ ràng.

Nếu gói odp.proj.test chỉ đơn giản là cung cấp các bài kiểm tra thì bạn có thể sử dụng cùng tên gói ( odp.proj). Các IDE như Eclipse và Netbeans sẽ tạo các thư mục riêng ( src/main/java/odp/projsrc/test/java/odp/proj) có cùng tên gói nhưng với ngữ nghĩa JUnit.

Lưu ý rằng các IDE này sẽ tạo các thử nghiệm cho các phương thức trong odp.projvà tạo thư mục thích hợp cho các phương thức thử nghiệm mà nó không tồn tại.


5

Khi tôi làm điều này trong IntelliJ, cây nguồn của tôi trông như thế này:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here

4

EDIT: Nếu không có khái niệm về một gói con trong Java, có cách nào khác không? Tôi có một số phương pháp nhất định mà tôi muốn chỉ có sẵn cho người thử nghiệm và các thành viên khác của gói đó.

Có lẽ nó phụ thuộc một chút vào động cơ của bạn vì không hiển thị chúng nhưng nếu lý do duy nhất là bạn không muốn làm ô nhiễm giao diện công cộng với những thứ chỉ dành cho thử nghiệm (hoặc một số thứ nội bộ khác) thì tôi sẽ đưa các phương thức vào giao diện công cộng riêng biệt và có người tiêu dùng của các phương thức "ẩn" sử dụng giao diện đó. Nó sẽ không ngăn người khác sử dụng giao diện nhưng tôi thấy không có lý do tại sao bạn nên.

Đối với các bài kiểm tra đơn vị, và nếu có thể mà không cần viết lại lô, hãy làm theo các gợi ý để sử dụng cùng một gói.


3

Như những người khác đã giải thích, không có thứ gọi là "gói con" trong Java: tất cả các gói đều bị cô lập và không được thừa hưởng gì từ cha mẹ của chúng.

Một cách dễ dàng để truy cập các thành viên lớp được bảo vệ từ gói khác là mở rộng lớp và ghi đè các thành viên.

Ví dụ, để truy cập ClassInAtrong gói a.b:

package a;

public class ClassInA{
    private final String data;

    public ClassInA(String data){ this.data = data; }

    public String getData(){ return data; }

    protected byte[] getDataAsBytes(){ return data.getBytes(); }

    protected char[] getDataAsChars(){ return data.toCharArray(); }
}

tạo một lớp trong gói đó ghi đè các phương thức bạn cần trong ClassInA:

package a.b;

import a.ClassInA;

public class ClassInAInB extends ClassInA{
    ClassInAInB(String data){ super(data); }

    @Override
    protected byte[] getDataAsBytes(){ return super.getDataAsBytes(); }
}

Điều đó cho phép bạn sử dụng lớp ghi đè thay cho lớp trong gói khác:

package a.b;

import java.util.Arrays;

import a.ClassInA;

public class Driver{
    public static void main(String[] args){
        ClassInA classInA = new ClassInA("string");
        System.out.println(classInA.getData());
        // Will fail: getDataAsBytes() has protected access in a.ClassInA
        System.out.println(Arrays.toString(classInA.getDataAsBytes()));

        ClassInAInB classInAInB = new ClassInAInB("string");
        System.out.println(classInAInB.getData());
        // Works: getDataAsBytes() is now accessible
        System.out.println(Arrays.toString(classInAInB.getDataAsBytes()));
    }
}

Lưu ý rằng điều này chỉ hoạt động đối với các thành viên được bảo vệ, có thể nhìn thấy đối với các lớp mở rộng (kế thừa) và không phải là thành viên riêng tư gói chỉ hiển thị cho các lớp phụ / mở rộng trong cùng một gói. Hy vọng điều này sẽ giúp được ai đó!


3

Hầu hết các câu trả lời ở đây đã tuyên bố rằng không có thứ gọi là gói con trong Java, nhưng điều đó không hoàn toàn chính xác. Thuật ngữ này đã có trong Đặc tả ngôn ngữ Java từ thời Java 6 và có thể quay trở lại (dường như không có phiên bản JLS có thể truy cập tự do cho các phiên bản Java trước đó). Ngôn ngữ xung quanh các gói con không thay đổi nhiều trong JLS kể từ Java 6.

Java 13 JLS :

Các thành viên của gói là các gói con của nó và tất cả các loại lớp cấp cao nhất và loại giao diện cấp cao nhất được khai báo trong tất cả các đơn vị biên dịch của gói.

Ví dụ: trong API nền tảng Java SE:

  • Các gói phần mềm javacó các gói con awt, applet, io, lang, net, và utilcác đơn vị, nhưng không có biên dịch.
  • Gói java.awtnày có một gói con được đặt tên image, cũng như một số đơn vị biên dịch có chứa các khai báo của các loại lớp và giao diện.

Khái niệm gói phụ có liên quan, vì nó thực thi các ràng buộc đặt tên giữa các gói và các lớp / giao diện:

Một gói có thể không chứa hai thành viên cùng tên hoặc kết quả lỗi thời gian biên dịch.

Dưới đây là một số ví dụ:

  • Bởi vì gói java.awtcó một gói con image, nó không thể (và không) chứa một khai báo của một lớp hoặc loại giao diện được đặt tên image.
  • Nếu có một gói có tên mousevà một loại thành viên Buttontrong gói đó (sau đó có thể được gọi là mouse.Button), thì không thể có bất kỳ gói nào có tên đủ điều kiện mouse.Buttonhoặc mouse.Button.Click.
  • Nếu com.nighthacks.java.jaglà tên đủ điều kiện của một loại, thì không thể có bất kỳ gói nào có tên đủ điều kiện là com.nighthacks.java.jaghoặc com.nighthacks.java.jag.scrabble.

Tuy nhiên, hạn chế đặt tên này là ý nghĩa duy nhất đủ khả năng để đóng gói theo ngôn ngữ:

Cấu trúc đặt tên phân cấp cho các gói nhằm thuận tiện cho việc tổ chức các gói liên quan theo cách thông thường, nhưng bản thân nó không có ý nghĩa gì ngoài việc cấm gói có gói con có cùng tên đơn giản như loại cấp cao nhất được khai báo trong gói đó .

Ví dụ, không có mối quan hệ truy cập đặc biệt giữa một gói có tên olivervà một gói khác có tên oliver.twist, hoặc giữa các gói có tên evelyn.woodevelyn.waugh. Nghĩa là, mã trong gói có tên oliver.twistkhông có quyền truy cập tốt hơn vào các loại được khai báo trong gói oliverso với mã trong bất kỳ gói nào khác.


Với bối cảnh này, chúng ta có thể tự trả lời câu hỏi. Vì rõ ràng không có mối quan hệ truy cập đặc biệt giữa một gói và gói phụ của nó, hoặc giữa hai gói phụ khác nhau của gói cha, nên không có cách nào trong ngôn ngữ để hiển thị một phương thức cho hai gói khác nhau theo cách được yêu cầu. Đây là một quyết định thiết kế tài liệu, cố ý.

Phương thức có thể được công khai và tất cả các gói (bao gồm odp.projodp.proj.test) sẽ có thể truy cập các phương thức đã cho hoặc phương thức có thể được đặt ở chế độ riêng tư (khả năng hiển thị mặc định) và tất cả các mã cần truy cập trực tiếp vào nó phải được đặt gói (phụ) giống như phương thức.

Điều đó nói rằng, một thực tiễn rất chuẩn trong Java là đặt mã kiểm tra vào cùng gói với mã nguồn, nhưng ở một vị trí khác trên hệ thống tệp. Ví dụ, trong công cụ xây dựng Maven , quy ước sẽ là đặt các tệp nguồn và kiểm tra này vào src/main/java/odp/projsrc/test/java/odp/proj, tương ứng. Khi công cụ xây dựng biên dịch cái này, cả hai bộ tệp kết thúc trong odp.projgói, nhưng chỉ các srctệp được bao gồm trong tạo phẩm sản xuất; các tệp kiểm tra chỉ được sử dụng tại thời điểm xây dựng để xác minh các tệp sản xuất. Với thiết lập này, mã kiểm tra có thể tự do truy cập bất kỳ gói riêng tư hoặc mã được bảo vệ nào của mã mà nó đang kiểm tra, vì chúng sẽ nằm trong cùng một gói.

Trong trường hợp bạn muốn chia sẻ mã trên các gói con hoặc gói anh chị em không phải là trường hợp thử nghiệm / sản xuất, một giải pháp tôi đã thấy một số thư viện sử dụng là đặt mã chia sẻ đó thành công khai, nhưng tài liệu đó là dành cho thư viện nội bộ chỉ sử dụng.


0

Không đặt công cụ sửa đổi truy cập trước phương thức bạn nói rằng đó là gói riêng tư.
Nhìn vào ví dụ sau.

package odp.proj;
public class A
{
    void launchA() { }
}

package odp.proj.test;
public class B
{
    void launchB() { }
}

public class Test
{
    public void test()
    {
        A a = new A();
        a.launchA()    // cannot call launchA because it is not visible
    }
}

0

Với lớp PackageVisibleHelper và giữ riêng tư trước khi GóiVisibleHelperFactory bị đóng băng, chúng ta có thể gọi phương thức launchA (bởi PackageVisibleHelper) ở bất cứ đâu :)

package odp.proj;
public class A
 {
    void launchA() { }
}

public class PackageVisibleHelper {

    private final PackageVisibleHelperFactory factory;

    public PackageVisibleHelper(PackageVisibleHelperFactory factory) {
        super();
        this.factory = factory;
    }

    public void launchA(A a) {
        if (factory == PackageVisibleHelperFactory.INSTNACNE && !factory.isSampleHelper(this)) {
            throw new IllegalAccessError("wrong PackageVisibleHelper ");
        }
        a.launchA();
    }
}


public class PackageVisibleHelperFactory {

    public static final PackageVisibleHelperFactory INSTNACNE = new PackageVisibleHelperFactory();

    private static final PackageVisibleHelper HELPER = new PackageVisibleHelper(INSTNACNE);

    private PackageVisibleHelperFactory() {
        super();
    }

    private boolean frozened;

    public PackageVisibleHelper getHelperBeforeFrozen() {
        if (frozened) {
            throw new IllegalAccessError("please invoke before frozen!");
        }
        return HELPER;
    }

    public void frozen() {
        frozened = true;
    }

    public boolean isSampleHelper(PackageVisibleHelper helper) {
        return HELPER.equals(helper);
    }
}
package odp.proj.test;

import odp.proj.A;
import odp.proj.PackageVisibleHelper;
import odp.proj.PackageVisibleHelperFactory;

public class Test {

    public static void main(String[] args) {

        final PackageVisibleHelper helper = PackageVisibleHelperFactory.INSTNACNE.getHelperBeforeFrozen();
        PackageVisibleHelperFactory.INSTNACNE.frozen();


        A a = new A();
        helper.launchA(a);

        // illegal access       
        new PackageVisibleHelper(PackageVisibleHelperFactory.INSTNACNE).launchA(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.