Java: ¿visibilidad de subpaquete?

150

Tengo dos paquetes en mi proyecto: odp.proje odp.proj.test. Hay ciertos métodos que quiero que sean visibles solo para las clases en estos dos paquetes. ¿Cómo puedo hacer esto?

EDITAR: Si no hay un concepto de subpaquete en Java, ¿hay alguna forma de evitarlo? Tengo ciertos métodos que quiero que estén disponibles solo para los evaluadores y otros miembros de ese paquete. ¿Debo simplemente tirar todo en el mismo paquete? ¿Usar una extensa reflexión?

Nick Heiner
fuente
2
Por otro lado, las pruebas solo deberían probar el comportamiento de sus objetos como observables desde fuera del paquete. Acceder a los métodos / clases de ámbito de paquete desde sus pruebas me dice que las pruebas probablemente prueben implementaciones, no comportamientos. Usando una herramienta de construcción como maven o gradle, facilitarán que sus pruebas se ejecuten en el mismo classpath pero no se incluyan en el jar final (algo bueno), por lo que no es necesario que tengan nombres de paquetes diferentes. Sin embargo, ponerlos en paquetes separados de todos modos es garantizar que no acceda al ámbito privado / predeterminado y, por lo tanto, pruebe solo la API pública.
derekv
3
Esto puede ser cierto si está trabajando de manera puramente basada en el comportamiento y desea que sus pruebas solo realicen pruebas de caja negra. Pero puede haber casos en los que la implementación del comportamiento deseado requiera una complejidad ciclomática inevitablemente alta. En este caso, puede ser agradable dividir la implementación en fragmentos más pequeños y simples (aún privados para la implementación) y escribir algunas pruebas unitarias para realizar pruebas de caja blanca en las diferentes rutas a través de estos fragmentos.
James Woods, el

Respuestas:

165

No puedes En Java no existe el concepto de un sub-paquete, por lo que odp.projy odp.proj.testson paquetes completamente separadas.

starblue
fuente
10
Aunque me gusta de esta manera, es confuso que la mayoría de los IDE junten paquetes con el mismo nombre. Gracias por la aclaración.
JacksOnF1re
Esto no es estrictamente preciso: el JLS define subpaquetes, aunque la única importancia del lenguaje que tienen es prohibir "contra un paquete que tenga un subpaquete con el mismo nombre simple que un tipo de nivel superior". Acabo de agregar una respuesta a esta pregunta explicando esto en detalle.
M. Justin
59

Los nombres de sus paquetes sugieren que la aplicación aquí es para pruebas unitarias. El patrón típico utilizado es colocar las clases que desea probar y el código de prueba de la unidad en el mismo paquete (en su caso odp.proj) pero en diferentes árboles de origen. Así que pondrías tus clases src/odp/projy tu código de prueba test/odp/proj.

Java tiene el modificador de acceso "paquete", que es el modificador de acceso predeterminado cuando no se especifica ninguno (es decir, no se especifica público, privado o protegido). Con el modificador de acceso "paquete", solo las clases odp.projtendrán acceso a los métodos. Pero tenga en cuenta que en Java, no se puede confiar en los modificadores de acceso para hacer cumplir las reglas de acceso porque con la reflexión, cualquier acceso es posible. Los modificadores de acceso son meramente sugerentes (a menos que esté presente un administrador de seguridad restrictivo).

Asaph
fuente
11

Esta no es una relación especial entre odp.projy odp.proj.test, simplemente se les nombra como aparentemente relacionados.

Si el paquete odp.proj.test simplemente proporciona pruebas, puede usar el mismo nombre de paquete ( odp.proj). IDEs como Eclipse y Netbeans crearán carpetas separadas ( src/main/java/odp/projy src/test/java/odp/proj) con el mismo nombre de paquete pero con semántica JUnit.

Tenga en cuenta que estos IDE generarán pruebas para los métodos odp.projy crearán la carpeta adecuada para los métodos de prueba en los que no existe.

peter.murray.rust
fuente
5

Cuando hago esto en IntelliJ, mi árbol fuente se ve así:

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

EDITAR: Si no hay un concepto de subpaquete en Java, ¿hay alguna forma de evitarlo? Tengo ciertos métodos que quiero que estén disponibles solo para los evaluadores y otros miembros de ese paquete.

Probablemente depende un poco de tus motivos para no mostrarlos, pero si la única razón es que no quieres contaminar la interfaz pública con las cosas destinadas solo para pruebas (o alguna otra cosa interna), pondría los métodos en un separe la interfaz pública y haga que los consumidores de los métodos "ocultos" usen esa interfaz. No impedirá que otros usen la interfaz, pero no veo ninguna razón por la que deba hacerlo.

Para pruebas unitarias, y si es posible sin reescribir el lote, siga las sugerencias para usar el mismo paquete.

Fredrik
fuente
3

Como han explicado otros, no existe un "subpaquete" en Java: todos los paquetes están aislados y no heredan nada de sus padres.

Una manera fácil de acceder a los miembros de la clase protegida desde otro paquete es extender la clase y anular los miembros.

Por ejemplo, para acceder ClassInAen paquete 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(); }
}

crea una clase en ese paquete que anule los métodos que necesitas en ClassInA:

package a.b;

import a.ClassInA;

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

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

Eso le permite usar la clase superior en lugar de la clase en el otro paquete:

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

Tenga en cuenta que esto solo funciona para miembros protegidos, que son visibles para las clases extendidas (herencia), y no para miembros privados de paquetes que son visibles solo para las clases sub / extendidas dentro del mismo paquete. ¡Ojalá esto ayude a alguien!

ndm13
fuente
3

La mayoría de las respuestas aquí han declarado que no hay tal cosa como un subpaquete en Java, pero eso no es estrictamente exacto. Este término ha estado en la especificación del lenguaje Java desde Java 6, y probablemente más atrás (no parece haber una versión de JLS de libre acceso para versiones anteriores de Java). El lenguaje alrededor de los subpaquetes no ha cambiado mucho en el JLS desde Java 6.

Java 13 JLS :

Los miembros de un paquete son sus subpaquetes y todos los tipos de clase de nivel superior y los tipos de interfaz de nivel superior declarados en todas las unidades de compilación del paquete.

Por ejemplo, en la API de la plataforma Java SE:

  • El paquete javatiene sub-paquetes awt, applet, io, lang, net, y utilunidades, pero no hay compilación.
  • El paquete java.awttiene un subpaquete denominado image, así como varias unidades de compilación que contienen declaraciones de clase y tipos de interfaz.

El concepto de subpaquete es relevante, ya que impone restricciones de nomenclatura entre paquetes y clases / interfaces:

Un paquete no puede contener dos miembros con el mismo nombre o un resultado de error en tiempo de compilación.

Aquí hay unos ejemplos:

  • Debido a que el paquete java.awttiene un subpaquete image, no puede (y no contiene) una declaración de una clase o tipo de interfaz llamado image.
  • Si hay un paquete con nombre mousey un tipo de miembro Buttonen ese paquete (que luego podría denominarse mouse.Button), entonces no puede haber ningún paquete con el nombre completo mouse.Buttono mouse.Button.Click.
  • Si com.nighthacks.java.jages el nombre completo de un tipo, entonces no puede haber ningún paquete cuyo nombre completo sea com.nighthacks.java.jago com.nighthacks.java.jag.scrabble.

Sin embargo, esta restricción de nomenclatura es el único significado que el lenguaje otorga a los subpaquetes:

La estructura de nomenclatura jerárquica para paquetes está pensada para ser conveniente para organizar paquetes relacionados de una manera convencional, pero no tiene importancia en sí misma aparte de la prohibición de que un paquete tenga un subpaquete con el mismo nombre simple que un tipo de nivel superior declarado en ese paquete .

Por ejemplo, no hay una relación de acceso especial entre un paquete llamado olivery otro paquete llamado oliver.twist, o entre paquetes nombrados evelyn.woody evelyn.waugh. Es decir, el código en un paquete llamado oliver.twistno tiene mejor acceso a los tipos declarados dentro del paquete oliverque el código en cualquier otro paquete.


Con este contexto, podemos responder la pregunta en sí. Dado que no existe una relación de acceso especial entre un paquete y su subpaquete, o entre dos subpaquetes diferentes de un paquete padre, no hay forma dentro del lenguaje de hacer que un método sea visible para dos paquetes diferentes de la manera solicitada. Esta es una decisión de diseño intencional documentada.

O el método puede hacerse público y todos los paquetes (incluidos odp.projy odp.proj.test) podrán acceder a los métodos dados, o el método podría hacerse privado (la visibilidad predeterminada), y todo el código que necesita acceder directamente a él debe incluirse el mismo (sub) paquete que el método.

Dicho esto, una práctica muy estándar en Java es colocar el código de prueba en el mismo paquete que el código fuente, pero en una ubicación diferente en el sistema de archivos. Por ejemplo, en la herramienta de compilación Maven , la convención sería colocar estos archivos fuente y de prueba en , src/main/java/odp/projy src/test/java/odp/projrespectivamente. Cuando la herramienta de compilación compila esto, ambos conjuntos de archivos terminan en el odp.projpaquete, pero solo los srcarchivos se incluyen en el artefacto de producción; los archivos de prueba solo se usan en el momento de la compilación para verificar los archivos de producción. Con esta configuración, el código de prueba puede acceder libremente a cualquier código privado o protegido del paquete del código que está probando, ya que estarán en el mismo paquete.

En el caso en que desee compartir código entre subpaquetes o paquetes hermanos que no sea el caso de prueba / producción, una solución que he visto que algunas bibliotecas usan es poner ese código compartido como público, pero documentar que está destinado a la biblioteca interna Usar unicamente.

M. Justin
fuente
0

Sin poner el modificador de acceso delante del método, dices que es un paquete privado.
mira el siguiente ejemplo.

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
    }
}
Alberto Zaccagni
fuente
0

Con la clase PackageVisibleHelper, y mantenerla privada antes de que PackageVisibleHelperFactory congelado, podemos invocar el método launchA (por PackageVisibleHelper) en cualquier 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); 
    }
}
qxo
fuente