Java: видимость подпакета?

150

У меня есть два пакета в моем проекте: odp.projи odp.proj.test. Есть определенные методы, которые я хочу видеть только для классов в этих двух пакетах. Как я могу это сделать?

РЕДАКТИРОВАТЬ: Если в Java нет концепции подпакета, есть ли способ обойти это? У меня есть определенные методы, которые я хочу использовать только для тестировщиков и других участников этого пакета. Должен ли я просто выбросить все в одну и ту же упаковку? Использовать обширные размышления?

Ник Хейнер
источник
2
Кроме того, тесты должны только когда-либо проверять поведение ваших объектов как наблюдаемое снаружи пакета. Доступ к методам / классам области пакета из ваших тестов говорит мне, что тесты, вероятно, тестируют реализации, а не поведения. Используя инструмент сборки, такой как maven или gradle, они облегчат выполнение ваших тестов в одном и том же пути к классам, но не будут включены в конечный файл jar (что хорошо), поэтому им не нужно иметь разные имена пакетов. Тем не менее, в любом случае помещение их в отдельные пакеты означает, что вы не обращаетесь к частной области / области по умолчанию и, таким образом, тестируете только общедоступный API.
Дерекв
3
Это может быть правдой, если вы работаете исключительно на основе поведения и хотите, чтобы ваши тесты выполняли только тестирование черного ящика. Но могут быть случаи, когда реализация желаемого поведения требует неизбежно высокой цикломатической сложности. В этом случае может быть полезно разбить реализацию на более мелкие, более простые куски (все еще частные для реализации) и написать некоторые модульные тесты для выполнения тестирования в виде белого ящика на разных путях через эти куски.
Джеймс Вудс

Ответы:

165

Ты не можешь В Java нет понятия подпакета, а значит odp.projи odp.proj.testявляются совершенно отдельными пакетами.

starblue
источник
10
Хотя мне это нравится, это сбивает с толку, что большинство IDE объединяют пакеты с одинаковыми именами. Спасибо за разъяснение.
JacksOnF1re
Это не совсем точно: JLS определяет подпакеты, хотя единственное значение, которое они имеют для языка, - это запрещать «против пакета, имеющего подпакет с тем же простым именем, что и тип верхнего уровня». Я только что добавил ответ на этот вопрос, объясняя это подробно.
М. Джастин
59

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

В Java есть модификатор доступа «пакет», который является модификатором доступа по умолчанию, когда ничего не указано (т. Е. Вы не указываете открытый, закрытый или защищенный). С модификатором доступа «package» 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и создавать соответствующую папку для методов тестирования, которых не существует.

peter.murray.rust
источник
5

Когда я делаю это в IntelliJ, мое дерево исходных текстов выглядит так:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here
duffymo
источник
4

РЕДАКТИРОВАТЬ: Если в 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()));
    }
}

Обратите внимание, что это работает только для защищенных членов, которые видимы для расширяющих классов (наследование), а не для закрытых для пакета членов, которые видимы только для подчиненных / расширяющих классов в одном и том же пакете. Надеюсь, это кому-нибудь поможет!

ndm13
источник
3

В большинстве ответов здесь говорится, что в Java нет такого понятия, как подпакет, но это не совсем точно. Этот термин был в Спецификации языка Java еще в Java 6, и, вероятно, еще в далеком прошлом (кажется, не существует свободно доступной версии JLS для более ранних версий Java). Язык вокруг подпакетов не сильно изменился в JLS со времени Java 6.

Java 13 JLS :

Членами пакета являются его подпакеты и все типы классов верхнего уровня и типы интерфейсов верхнего уровня, объявленные во всех единицах компиляции пакета.

Например, в API платформы Java SE:

  • Пакет 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 мы можем вызывать метод launchA (с помощью 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); 
    }
}
qxo
источник