Java: visibilité des sous-packages?

150

J'ai deux packages dans mon projet: odp.projet odp.proj.test. Il y a certaines méthodes que je souhaite voir uniquement pour les classes de ces deux packages. Comment puis-je faire ceci?

EDIT: S'il n'y a pas de concept de sous-paquet en Java, y a-t-il un moyen de contourner cela? J'ai certaines méthodes que je veux être disponibles uniquement pour les testeurs et les autres membres de ce package. Dois-je tout jeter dans le même paquet? Utilisez une réflexion approfondie?

Nick Heiner
la source
2
En passant, les tests ne devraient jamais tester le comportement de vos objets que comme observable de l'extérieur du package. L'accès aux méthodes / classes de la portée du package à partir de vos tests me dit que les tests testent probablement des implémentations et non des comportements. En utilisant un outil de construction comme maven ou gradle, ils faciliteront l'exécution de vos tests dans le même chemin de classe mais ne seront pas inclus dans le fichier jar final (une bonne chose), donc pas besoin qu'ils aient des noms de paquetages différents. Pourtant, les mettre de toute façon dans des packages séparés revient à faire en sorte que vous n'accédez pas à la portée privée / par défaut et ne testez donc que l'API publique.
derekv
3
Cela peut être vrai si vous travaillez de manière purement axée sur le comportement et que vous souhaitez que vos tests ne fassent que des tests en boîte noire. Mais il peut y avoir des cas où la mise en œuvre du comportement souhaité nécessite une complexité cyclomatique inévitablement élevée. Dans ce cas, il peut être agréable de diviser l'implémentation en morceaux plus petits et plus simples (toujours privés de l'implémentation) et d'écrire des tests unitaires pour effectuer des tests en boîte blanche sur les différents chemins à travers ces morceaux.
James Woods

Réponses:

165

Vous ne pouvez pas. En Java, il n'y a pas de concept de sous-package, donc odp.projet odp.proj.testsont des packages complètement séparés.

starblue
la source
10
Bien que je l'aime de cette façon, il est déroutant que la plupart des IDE mettent ensemble des packages portant le même nom. Merci pour la clarification.
JacksOnF1re
Ce n'est pas tout à fait exact: le JLS définit les sous-packages, bien que la seule signification de langage qu'ils aient soit d'interdire "qu'un package ait un sous-package avec le même nom simple qu'un type de niveau supérieur". Je viens d'ajouter une réponse à cette question en expliquant cela en détail.
M. Justin
59

Les noms de vos packages indiquent que l'application ici est destinée aux tests unitaires. Le modèle typique utilisé est de mettre les classes que vous souhaitez tester et le code de test unitaire dans le même package (dans votre cas odp.proj) mais dans des arborescences sources différentes. Ainsi, vous mettriez vos classes src/odp/projet votre code de test test/odp/proj.

Java a le modificateur d'accès "package" qui est le modificateur d'accès par défaut quand aucun n'est spécifié (c'est-à-dire que vous ne spécifiez pas public, privé ou protégé). Avec le modificateur d'accès "package", seules les classes de odp.projauront accès aux méthodes. Mais gardez à l'esprit qu'en Java, les modificateurs d'accès ne peuvent pas être utilisés pour appliquer les règles d'accès car avec la réflexion, tout accès est possible. Les modificateurs d'accès sont simplement suggestifs (sauf si un gestionnaire de sécurité restrictif est présent).

Asaph
la source
11

Il n'y a pas de relation spéciale entre odp.projet odp.proj.test- ils sont simplement nommés comme apparemment liés.

Si le package odp.proj.test fournit simplement des tests, vous pouvez utiliser le même nom de package ( odp.proj). Les IDE comme Eclipse et Netbeans créeront des dossiers séparés ( src/main/java/odp/projet src/test/java/odp/proj) avec le même nom de package mais avec la sémantique JUnit.

Notez que ces IDE généreront des tests pour les méthodes dans odp.projet créeront le dossier approprié pour les méthodes de test dans lesquelles il n'existe pas.

peter.murray.rust
la source
5

Quand je fais cela dans IntelliJ, mon arbre source ressemble à ceci:

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

EDIT: S'il n'y a pas de concept de sous-paquet en Java, y a-t-il un moyen de contourner cela? J'ai certaines méthodes que je veux être disponibles uniquement pour les testeurs et les autres membres de ce package.

Cela dépend probablement un peu de vos motivations pour ne pas les afficher, mais si la seule raison est que vous ne voulez pas polluer l'interface publique avec des choses destinées uniquement à des tests (ou une autre chose interne), je mettrais les méthodes dans un une interface publique distincte et que les consommateurs des méthodes «cachées» utilisent cette interface. Cela n'empêchera pas les autres d'utiliser l'interface, mais je ne vois aucune raison pour laquelle vous devriez le faire.

Pour les tests unitaires, et s'il est possible sans réécrire le lot, suivez les suggestions pour utiliser le même package.

Fredrik
la source
3

Comme d'autres l'ont expliqué, il n'existe pas de "sous-paquetage" en Java: tous les paquets sont isolés et n'héritent en rien de leurs parents.

Un moyen simple d'accéder aux membres de classe protégés à partir d'un autre package consiste à étendre la classe et à remplacer les membres.

Par exemple, pour accéder ClassInAau package 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(); }
}

créez une classe dans ce package qui remplace les méthodes dont vous avez besoin dans ClassInA:

package a.b;

import a.ClassInA;

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

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

Cela vous permet d'utiliser la classe de substitution à la place de la classe dans l'autre package:

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

Notez que cela ne fonctionne que pour les membres protégés, qui sont visibles pour les classes étendues (héritage), et non pour les membres privés de package qui ne sont visibles que pour les sous-classes / extensions dans le même package. Espérons que cela aide quelqu'un!

ndm13
la source
3

La plupart des réponses ici ont déclaré qu'il n'existe pas de sous-paquetage en Java, mais ce n'est pas strictement exact. Ce terme a été dans la spécification du langage Java aussi loin que Java 6, et probablement plus loin (il ne semble pas y avoir de version librement accessible du JLS pour les versions antérieures de Java). Le langage autour des sous-packages n'a pas beaucoup changé dans le JLS depuis Java 6.

Java 13 JLS :

Les membres d'un package sont ses sous-packages et tous les types de classe de niveau supérieur et les types d'interface de niveau supérieur déclarés dans toutes les unités de compilation du package.

Par exemple, dans l'API Java SE Platform:

  • Le paquet javaa sous - paquets awt, applet, io, lang, netet util, mais aucune unité de compilation.
  • Le paquet java.awta un sous-paquet nommé image, ainsi qu'un certain nombre d'unités de compilation contenant des déclarations de types de classe et d'interface.

Le concept de sous-package est pertinent, tout comme il applique des contraintes de dénomination entre les packages et les classes / interfaces:

Un package ne peut pas contenir deux membres du même nom, ou une erreur de compilation en résulte.

Voici quelques exemples:

  • Étant donné que le package java.awta un sous-package image, il ne peut pas (et ne contient pas) de déclaration d'une classe ou d'un type d'interface nommé image.
  • S'il y a un package nommé mouseet un type de membre Buttondans ce package (qui pourrait alors être appelé mouse.Button), il ne peut pas y avoir de package avec le nom complet mouse.Buttonou mouse.Button.Click.
  • Si com.nighthacks.java.jagest le nom complet d'un type, alors il ne peut y avoir aucun package dont le nom complet est soit com.nighthacks.java.jagou com.nighthacks.java.jag.scrabble.

Cependant, cette restriction de dénomination est la seule signification accordée aux sous-packages par le langage:

La structure de dénomination hiérarchique pour les packages est destinée à être pratique pour organiser les packages liés de manière conventionnelle, mais n'a aucune signification en soi autre que l'interdiction pour un package d'avoir un sous-package avec le même nom simple qu'un type de niveau supérieur déclaré dans ce package .

Par exemple, il n'y a pas de relation d'accès spéciale entre un package nommé oliveret un autre package nommé oliver.twist, ou entre les packages nommés evelyn.woodet evelyn.waugh. Autrement dit, le code d'un package nommé oliver.twistn'a pas de meilleur accès aux types déclarés dans le package oliverque le code de tout autre package.


Dans ce contexte, nous pouvons répondre à la question elle-même. Puisqu'il n'y a explicitement aucune relation d'accès spéciale entre un package et son sous-package, ou entre deux sous-packages différents d'un package parent, il n'y a aucun moyen dans le langage de rendre une méthode visible à deux packages différents de la manière demandée. Il s'agit d'une décision de conception documentée et intentionnelle.

Soit la méthode peut être rendue publique et tous les packages (y compris odp.projet odp.proj.test) pourront accéder aux méthodes données, soit la méthode peut être rendue package private (la visibilité par défaut), et tout le code qui doit y accéder directement doit le mettre le même (sous) package que la méthode.

Cela dit, une pratique très courante en Java consiste à placer le code de test dans le même package que le code source, mais à un emplacement différent sur le système de fichiers. Par exemple, dans l' outil de construction Maven , la convention serait de placer ces fichiers source et test dans src/main/java/odp/projet src/test/java/odp/proj, respectivement. Lorsque l'outil de construction compile cela, les deux ensembles de fichiers se retrouvent dans le odp.projpackage, mais seuls les srcfichiers sont inclus dans l'artefact de production; les fichiers de test ne sont utilisés qu'au moment de la construction pour vérifier les fichiers de production. Avec cette configuration, le code de test peut accéder librement à n'importe quel code privé ou protégé du code qu'il teste, car ils seront dans le même package.

Dans le cas où vous souhaitez un partage de code entre des sous-packages ou des packages frères qui ne sont pas le cas de test / production, une solution que j'ai vue dans certaines bibliothèques consiste à rendre ce code partagé public, mais à documenter qu'il est destiné à la bibliothèque interne utiliser seulement.

M. Justin
la source
0

Sans mettre le modificateur d'accès devant la méthode, vous dites qu'il s'agit d'un package privé.
Regardez l'exemple suivant.

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
la source
0

Avec la classe PackageVisibleHelper, et gardez-la privée avant PackageVisibleHelperFactory gelé, nous pouvons appeler la méthode launchA (par PackageVisibleHelper) n'importe où :)

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
la source