Java: видимість підпаку?


150

У моєму проекті є два пакети: odp.projі odp.proj.test. Є певні методи, за якими я хочу бути видимими лише для класів у цих двох пакетах. Як я можу це зробити?

EDIT: Якщо в Java немає поняття про підпакет, чи існує спосіб цього? У мене є певні методи, які я хочу бути доступними лише для тестувальників та інших членів цього пакету. Чи варто просто кинути все в один і той же пакет? Використовувати широке роздуми?




2
Крім того, тести повинні коли-небудь перевіряти поведінку ваших об'єктів як помітну поза пакетом. Доступ до методів / класів, що стосуються пакету з ваших тестів, говорить про те, що тести, ймовірно, тестують реалізацію, а не поведінку. Використовуючи такий інструмент для збирання, як Maven або Gradle, вони полегшать запуск ваших тестів на тому ж самому класовому шляху, але не будуть включені в остаточну банку (хороша річ), таким чином, їм не потрібно мати різних назв пакетів. Але все-таки розміщуючи їх в окремих пакетах - це домогтися того, щоб ви не мали доступу до приватного / за замовчуванням, і, таким чином, перевірити лише загальнодоступні api.
дерев

3
Це може бути правдою, якщо ви працюєте в суто поведінці та хочете, щоб ваші тести виконували лише тестування в чорному ящику. Але можуть бути випадки, коли реалізація бажаної поведінки вимагає неминуче високої цикломатичної складності. У цьому випадку може бути непогано розбити реалізацію на менші, простіші фрагменти (все ще приватні для реалізації) і написати кілька тестових одиниць, щоб виконати тестування білого поля на різних шляхах через ці шматки.
Джеймс Вудс

Відповіді:


165

Ви не можете. У Java немає поняття про підпакет, тому є odp.projі odp.proj.testповністю окремими пакетами.


10
Хоча мені це подобається таким чином, це заплутано, що більшість IDE складають пакунки з однаковою назвою разом. Дякуємо за роз’яснення
JacksOnF1re

Це не є точно точним: JLS визначає підпакети, хоча єдине мовне значення, яке вони мають, - забороняти "проти пакету, що має підпакет з таким же простим ім'ям, як тип верхнього рівня". Я щойно додав відповідь на це питання, пояснюючи це детально.
М. Юстін

59

Імена ваших пакетів натякають, що програма тут призначена для тестування одиниць. Типовий зразок - це розміщення класів, які ви хочете перевірити, та одиничний тестовий код у той самий пакет (у вашому випадку odp.proj), але в різні дерева джерел. Таким чином, ви б помістили свої заняття src/odp/projта свій тестовий код test/odp/proj.

У Java є модифікатор доступу "пакет", який є модифікатором доступу за замовчуванням, коли жоден не вказаний (тобто ви не вказуєте загальнодоступні, приватні чи захищені). З модифікатором доступу "пакет" odp.projдоступ до методів матимуть лише класи класів . Але майте на увазі, що в Java не можна покладатися на модифікатори доступу, щоб застосовувати правила доступу, оскільки, відображаючи, можливий будь-який доступ. Модифікатори доступу є лише сугестивними (якщо не присутній обмежувальний менеджер безпеки).


11

Це не має особливого відношення між odp.projі odp.proj.test- їх просто називають як очевидно спорідненими.

Якщо пакет odp.proj.test просто забезпечує тести, то ви можете використовувати те саме ім'я пакету ( odp.proj). IDE, як Eclipse та Netbeans, створюватимуть окремі папки ( src/main/java/odp/projі src/test/java/odp/proj) з однаковим іменем пакета, але з семантикою JUnit.

Зауважте, що ці IDE будуть генерувати тести для методів у odp.projта створювати відповідну папку для методів тестування, яких не існує.


5

Коли я це роблю в IntelliJ, моє дерево джерела виглядає так:

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

4

EDIT: Якщо в Java немає поняття про підпакет, чи існує спосіб цього? У мене є певні методи, які я хочу бути доступними лише для тестувальників та інших членів цього пакету.

Це, мабуть, трохи залежить від ваших мотивів не показувати їх, але якщо єдиною причиною є те, що ви не хочете забруднювати публічний інтерфейс речами, призначеними лише для тестування (чи якоюсь іншою внутрішньою річчю), я б поставив методи в відокремлений публічний інтерфейс і споживачі «прихованих» методів використовують цей інтерфейс. Це не завадить іншим користуватися інтерфейсом, але я не бачу причин, чому слід.

Для одиничних тестів, і якщо це можливо без переписування партії, дотримуйтесь пропозицій використовувати той самий пакет.


3

Як пояснили інші, у Java не існує такого поняття, як "підпакет": усі пакети є ізольованими і нічого не успадковують від батьків.

Простий спосіб доступу до захищених членів класу з іншого пакету - це розширення класу та перекриття членів.

Наприклад, для доступу ClassInAв пакет 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(); }
}

складіть клас із цього пакету, який перекриває потрібні вам методи ClassInA:

package a.b;

import a.ClassInA;

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

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

Це дозволяє використовувати переважаючий клас замість класу в іншому пакеті:

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()));
    }
}

Зауважте, що це працює лише для захищених членів, які видимі для розширення класів (успадкування), а не приватних членів пакета, які видно лише підкладам / розширенням класів у межах одного пакету. Сподіваємось, це комусь допомагає!


3

Більшість відповідей тут зазначають, що в Java немає такого поняття, як підпакет, але це не є строго точною. Цей термін був включений у специфікацію мови Java аж до Java 6, і, ймовірно, ще більше назад (здається, що для попередніх версій Java не існує вільнодоступної версії JLS). Мова навколо підпакетів мало змінилася в JLS після Java 6.

Java 13 JLS :

Членами пакету є його підпакети та всі типи класів верхнього рівня та типи інтерфейсів верхнього рівня, оголошені у всіх блоках компіляції пакету.

Наприклад, в API SE SE Platform:

  • Пакет javaмає підпакети awt, applet, io, lang, net, і util, але немає одиниці компіляції.
  • У пакеті java.awtє підпакет з назвою image, а також ряд компіляційних одиниць, що містять декларації класів та типів інтерфейсу.

Концепція субпакетів є актуальною, як і примушує називати обмеження між пакетами та класами / інтерфейсами:

Пакет може не містити двох однотипних членів або результатів помилки під час компіляції.

Ось кілька прикладів:

  • Оскільки пакет java.awtмає підпакет image, він не може (і не містить) декларації класу або типу інтерфейсу з назвою image.
  • Якщо в цьому пакеті є названий пакет mouseі тип учасника Button(який потім може називатися mouse.Button), то не може бути жодного пакета з повністю кваліфікованим ім'ям mouse.Buttonабо mouse.Button.Click.
  • Якщо com.nighthacks.java.jagце повністю кваліфіковане ім'я типу, то не може бути жодного пакету, повністю кваліфікованим ім'ям якого є com.nighthacks.java.jagабо com.nighthacks.java.jag.scrabble.

Однак це обмеження іменування є єдиним значенням, яке надається мовою субпакетів:

Ієрархічна структура імен для пакетів повинна бути зручною для організації пов'язаних пакетів звичайним чином, але сама по собі не має значення, крім заборони щодо пакету, що має підпакет з таким же простим ім'ям, як тип верхнього рівня, оголошений у цьому пакеті .

Наприклад, немає спеціального зв’язку доступу між іменованим пакетом oliverта іншим названим пакетом oliver.twistабо між пакетами, названими evelyn.woodта evelyn.waugh. Тобто код у названому пакеті oliver.twistне має кращого доступу до типів, оголошених у пакеті, oliverніж код у будь-якому іншому пакеті.


За допомогою цього контексту ми можемо відповісти на саме запитання. Оскільки явно немає спеціального зв’язку доступу між пакетом та його підпакетом, або між двома різними підпакетів батьківського пакету, немає можливості в межах мови зробити метод видимим для двох різних пакетів у запитуваному порядку. Це документоване, навмисне дизайнерське рішення.

Або метод може бути опублікований, і всі пакети (включаючи odp.projі odp.proj.test) матимуть доступ до заданих методів, або метод може бути зроблений приватним пакетом (видимість за замовчуванням), і весь код, до якого потрібно безпосередньо отримати доступ, повинен бути введений той же (під) пакет, що і метод.

Однак, дуже стандартною практикою в Java є тест-код ставити в той же пакет, що і вихідний код, але в іншому місці файлової системи. Наприклад, в Maven інструменту для зборки, угода була б помістити ці вихідні і тестові файли в src/main/java/odp/projі src/test/java/odp/proj, відповідно. Коли інструмент збірки збирає це, обидва набори файлів закінчуються в odp.projпакеті, але лише srcфайли включаються у виробничий артефакт; тестові файли використовуються лише під час збирання для перевірки виробничих файлів. За допомогою цієї установки тестовий код може вільно отримувати доступ до будь-якого приватного або захищеного коду пакета коду, який він тестує, оскільки вони будуть знаходитися в одному пакеті.

У випадку, коли ви хочете ділитися кодом у підпакеті чи пакетів братів, що не є тестовим / виробничим випадком, одне рішення, яке я бачив у деяких бібліотеках, - це розмістити цей спільний код як загальнодоступний, але документ, що він призначений для внутрішньої бібліотеки використовувати тільки.


0

Не ставлячи модифікатор доступу перед методом, ви говорите, що він пакет приватний.
Подивіться на наступний приклад.

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

За допомогою класу PackageVisibleHelper і зберігаючи його приватним перед замороженим PackageVisibleHelperFactory, ми можемо викликати метод startA (від PackageVisibleHelper) у будь-якому місці :)

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); 
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.