Java: Visibilidade do subpacote?


150

Eu tenho dois pacotes no meu projeto: odp.proje odp.proj.test. Existem certos métodos que eu quero que sejam visíveis apenas para as classes nesses dois pacotes. Como posso fazer isso?

EDIT: Se não houver um conceito de subpacote em Java, existe alguma maneira de contornar isso? Eu tenho certos métodos que quero que estejam disponíveis apenas para testadores e outros membros desse pacote. Devo apenas jogar tudo no mesmo pacote? Usar reflexão extensa?




2
Como um aparte, os testes devem sempre testar o comportamento de seus objetos como observável de fora do pacote. O acesso a métodos / classes de escopo de pacote de seus testes me diz que os testes provavelmente estão testando implementações, não comportamentos. Usando uma ferramenta de construção como maven ou gradle, eles facilitarão a execução de seus testes no mesmo caminho de classe, mas não serão incluídos no jar final (uma coisa boa), portanto, não é necessário que eles tenham nomes de pacotes diferentes. No entanto, colocá-los em pacotes separados de qualquer maneira é garantir que você não acesse o escopo privado / padrão e, portanto, teste apenas a API pública.
derekv

3
Isso pode ser verdade se você estiver trabalhando de maneira puramente comportamental e quiser que seus testes façam apenas testes em caixa preta. Mas pode haver casos em que a implementação do comportamento desejado requer uma complexidade ciclomática inevitavelmente alta. Nesse caso, pode ser bom dividir a implementação em partes menores e mais simples (ainda privadas da implementação) e escrever alguns testes de unidade para executar testes de caixa branca nos diferentes caminhos desses pedaços.
James Woods

Respostas:


165

Você não pode. Em Java não existe o conceito de um subpacote, então odp.proje odp.proj.testsão pacotes completamente distintas.


10
Embora eu goste dessa maneira, é confuso que a maioria dos IDEs junte pacotes com o mesmo nome. Obrigado pela clarificação.
JacksOnF1re

Isso não é estritamente preciso: o JLS define subpacotes, embora o único significado de idioma que eles têm é proibir "contra um pacote que tenha um subpacote com o mesmo nome simples que um tipo de nível superior". Acabei de adicionar uma resposta a esta pergunta, explicando isso em detalhes.
M. Justin

59

Os nomes dos seus pacotes sugerem que o aplicativo aqui é para teste de unidade. O padrão típico usado é colocar as classes que você deseja testar e o código do teste de unidade no mesmo pacote (no seu caso odp.proj), mas em diferentes árvores de origem. Então você colocaria suas aulas src/odp/proje seu código de teste test/odp/proj.

Java possui o modificador de acesso "package", que é o modificador de acesso padrão quando nenhum é especificado (por exemplo, você não especifica public, private ou protected). Com o modificador de acesso "package", apenas as classes odp.projterão acesso aos métodos. Mas lembre-se de que, em Java, não é possível confiar nos modificadores de acesso para impor regras de acesso, porque, com reflexão, qualquer acesso é possível. Modificadores de acesso são meramente sugestivos (a menos que um gerenciador de segurança restritivo esteja presente).


11

Essa não é uma relação especial entre odp.proje odp.proj.test- eles são nomeados como aparentemente relacionados.

Se o pacote odp.proj.test estiver simplesmente fornecendo testes, você poderá usar o mesmo nome de pacote ( odp.proj). IDEs como Eclipse e Netbeans criarão pastas separadas ( src/main/java/odp/proje src/test/java/odp/proj) com o mesmo nome de pacote, mas com semântica JUnit.

Observe que esses IDEs irão gerar testes para métodos odp.proje criar a pasta apropriada para os métodos de teste que não existem.


5

Quando faço isso no IntelliJ, minha árvore de fontes fica assim:

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

4

EDIT: Se não houver um conceito de subpacote em Java, existe alguma maneira de contornar isso? Eu tenho certos métodos que quero que estejam disponíveis apenas para testadores e outros membros desse pacote.

Provavelmente depende um pouco de seus motivos para não exibi-los, mas se o único motivo é que você não deseja poluir a interface pública com as coisas destinadas apenas a testes (ou alguma outra coisa interna), eu colocaria os métodos em um interface pública separada e faça com que os consumidores dos métodos "ocultos" usem essa interface. Não impedirá que outras pessoas usem a interface, mas não vejo razão para que você deva.

Para testes de unidade e, se for possível sem reescrever o lote, siga as sugestões para usar o mesmo pacote.


3

Como outros explicaram, não existe um "subpacote" em Java: todos os pacotes são isolados e não herdam nada de seus pais.

Uma maneira fácil de acessar os membros da classe protegidos de outro pacote é estender a classe e substituir os membros.

Por exemplo, para acessar ClassInAno pacote 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(); }
}

faça uma classe nesse pacote que substitua os métodos necessários em ClassInA:

package a.b;

import a.ClassInA;

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

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

Isso permite que você use a classe de substituição no lugar da classe no outro pacote:

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

Observe que isso funciona apenas para membros protegidos, que são visíveis para estender classes (herança), e não para membros do pacote privado que são visíveis apenas para sub / estender classes dentro do mesmo pacote. Espero que isso ajude alguém!


3

A maioria das respostas aqui afirmou que não existe um subpacote em Java, mas isso não é estritamente preciso. Esse termo está presente na Especificação da Linguagem Java já em Java 6 e provavelmente mais atrás (não parece haver uma versão acessível do JLS para versões anteriores do Java). O idioma em torno dos subpacotes não mudou muito no JLS desde o Java 6.

Java 13 JLS :

Os membros de um pacote são seus subpacotes e todos os tipos de classe de nível superior e tipos de interface de nível superior declarados em todas as unidades de compilação do pacote.

Por exemplo, na API da plataforma Java SE:

  • O pacote javatem subpackages awt, applet, io, lang, net, e utilunidades, mas não compilação.
  • O pacote java.awtpossui um subpacote nomeado image, além de várias unidades de compilação que contêm declarações de classe e tipos de interface.

O conceito de subpacote é relevante, pois reforça as restrições de nomenclatura entre pacotes e classes / interfaces:

Um pacote não pode conter dois membros com o mesmo nome ou resultados de erro em tempo de compilação.

aqui estão alguns exemplos:

  • Como o pacote java.awtpossui um subpacote image, ele não pode (e não contém) uma declaração de uma classe ou tipo de interface nomeado image.
  • Se houver um pacote nomeado mousee um tipo de membro Buttonnesse pacote (que pode ser chamado de mouse.Button), não poderá haver nenhum pacote com o nome completo mouse.Buttonou mouse.Button.Click.
  • Se com.nighthacks.java.jagé o nome completo de um tipo, não pode haver nenhum pacote cujo nome completo seja com.nighthacks.java.jagou com.nighthacks.java.jag.scrabble.

No entanto, essa restrição de nomenclatura é o único significado concedido aos subpacotes pelo idioma:

A estrutura hierárquica de nomenclatura para pacotes deve ser conveniente para organizar pacotes relacionados de maneira convencional, mas não tem significado em si mesma além da proibição de um pacote com um subpacote com o mesmo nome simples que um tipo de nível superior declarado nesse pacote .

Por exemplo, não há relacionamento de acesso especial entre um pacote nomeado olivere outro pacote nomeado oliver.twist, ou entre pacotes nomeados evelyn.woode evelyn.waugh. Ou seja, o código em um pacote nomeado oliver.twistnão tem melhor acesso aos tipos declarados no pacote do oliverque o código em qualquer outro pacote.


Nesse contexto, podemos responder à pergunta em si. Como não há explicitamente nenhum relacionamento de acesso especial entre um pacote e seu subpacote, ou entre dois subpacotes diferentes de um pacote pai, não há como, no idioma, tornar um método visível para dois pacotes diferentes da maneira solicitada. Esta é uma decisão de projeto intencional documentada.

O método pode ser tornado público e todos os pacotes (incluindo odp.proje odp.proj.test) poderão acessar os métodos fornecidos, ou o método pode tornar o pacote privado (a visibilidade padrão) e todo o código que precisa acessá-lo diretamente deve incluir o mesmo (sub) pacote que o método

Dito isto, uma prática muito padrão em Java é colocar o código de teste no mesmo pacote que o código-fonte, mas em um local diferente no sistema de arquivos. Por exemplo, na ferramenta de criação do Maven , a convenção seria colocar esses arquivos de origem e teste em src/main/java/odp/proje src/test/java/odp/proj, respectivamente. Quando a ferramenta de construção compila isso, os dois conjuntos de arquivos acabam no odp.projpacote, mas apenas os srcarquivos são incluídos no artefato de produção; os arquivos de teste são usados ​​apenas no momento da construção para verificar os arquivos de produção. Com essa configuração, o código de teste pode acessar livremente qualquer pacote de código privado ou protegido do código que está testando, pois eles estarão no mesmo pacote.

No caso em que você deseja compartilhar o código entre subpacotes ou pacotes irmãos que não é o caso de teste / produção, uma solução que já vi algumas bibliotecas usar é colocar esse código compartilhado como público, mas documentar que ele se destina à biblioteca interna usarem apenas.


0

Sem colocar o modificador de acesso na frente do método, você diz que é pacote privado.
Veja o exemplo a seguir.

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

Com a classe PackageVisibleHelper e mantenha-a privada antes do congelamento de PackageVisibleHelperFactory, podemos chamar o método launchA (by PackageVisibleHelper) em qualquer lugar :)

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); 
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.