Java: visibilità del pacchetto secondario?


150

Ho due pacchetti nel mio progetto: odp.proje odp.proj.test. Ci sono alcuni metodi che voglio essere visibili solo alle classi in questi due pacchetti. Come posso fare questo?

EDIT: Se non esiste il concetto di un pacchetto secondario in Java, c'è un modo per aggirare questo? Ho alcuni metodi che voglio essere disponibili solo per tester e altri membri di quel pacchetto. Devo semplicemente gettare tutto nello stesso pacchetto? Usa una riflessione approfondita?




2
A parte questo, i test dovrebbero sempre e solo testare il comportamento dei tuoi oggetti come osservabile dall'esterno del pacchetto. L'accesso ai metodi / classi dell'ambito del pacchetto dai test mi dice che i test stanno probabilmente testando implementazioni e comportamenti. Usando uno strumento di compilazione come Maven o Gradle, semplificheranno l'esecuzione dei test nello stesso percorso di classe ma non saranno inclusi nel jar finale (una buona cosa), quindi non è necessario che abbiano nomi di pacchetti diversi. Tuttavia, metterli in pacchetti separati significa imporre di non accedere all'ambito privato / predefinito e quindi testare solo l'API pubblica.
derekv,

3
Questo può essere vero se stai lavorando in modo meramente orientato al comportamento e desideri che i tuoi test eseguano solo test in scatola nera. Ma ci possono essere casi in cui l'implementazione del comportamento desiderato richiede una complessità ciclomatica inevitabilmente elevata. In questo caso può essere utile suddividere l'implementazione in blocchi più piccoli e più semplici (ancora privati ​​dell'implementazione) e scrivere alcuni test unitari per eseguire test in white box sui diversi percorsi attraverso questi blocchi.
James Woods,

Risposte:


165

Non puoi. In Java non esiste il concetto di un sottopacchetto, così odp.proje odp.proj.testsono pacchetti completamente separati.


10
Anche se mi piace in questo modo, è confuso che la maggior parte degli IDE metta insieme pacchetti con lo stesso nome. Grazie per il chiarimento.
JacksOnF1re

Ciò non è strettamente preciso: JLS definisce i pacchetti secondari, anche se l' unico significato linguistico che hanno è vietare "contro un pacchetto con un pacchetto secondario con lo stesso nome semplice di un tipo di livello superiore". Ho appena aggiunto una risposta a questa domanda spiegando questo in dettaglio.
M. Justin,

59

I nomi dei pacchetti suggeriscono che l'applicazione qui è per test unitari. Lo schema tipico utilizzato è quello di mettere le classi che si desidera testare e il codice di test unitario nello stesso pacchetto (nel proprio caso odp.proj) ma in alberi di origine diversi. Quindi inseriresti le tue classi src/odp/proje il tuo codice di test test/odp/proj.

Java ha il modificatore di accesso "pacchetto" che è il modificatore di accesso predefinito quando non ne viene specificato nessuno (ovvero non si specifica pubblico, privato o protetto). Con il modificatore di accesso "pacchetto", solo le classi in odp.projavranno accesso ai metodi. Ma tieni presente che in Java non è possibile fare affidamento sui modificatori di accesso per far rispettare le regole di accesso perché con la riflessione è possibile qualsiasi accesso. I modificatori di accesso sono semplicemente indicativi (a meno che non sia presente un gestore della sicurezza restrittivo).


11

Questa non è una relazione speciale tra odp.proje odp.proj.test- capita solo di essere nominata come apparentemente correlata.

Se il pacchetto odp.proj.test fornisce semplicemente test, è possibile utilizzare lo stesso nome pacchetto ( odp.proj). IDE come Eclipse e Netbeans creeranno cartelle separate ( src/main/java/odp/proje src/test/java/odp/proj) con lo stesso nome di pacchetto ma con semantica JUnit.

Si noti che questi IDE genereranno test per i metodi odp.proje creeranno la cartella appropriata per i metodi di test inesistenti.


5

Quando lo faccio in IntelliJ, il mio albero dei sorgenti appare così:

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

4

EDIT: Se non esiste il concetto di un pacchetto secondario in Java, c'è un modo per aggirare questo? Ho alcuni metodi che voglio essere disponibili solo per tester e altri membri di quel pacchetto.

Probabilmente dipende un po 'dai tuoi motivi per non visualizzarli, ma se l'unica ragione è che non vuoi inquinare l'interfaccia pubblica con le cose intese solo per i test (o qualche altra cosa interna) metterei i metodi in un interfaccia pubblica separata e fare in modo che i consumatori dei metodi "nascosti" utilizzino tale interfaccia. Non impedirà ad altri di utilizzare l'interfaccia, ma non vedo alcun motivo per cui tu debba farlo.

Per i test unitari e se è possibile senza riscrivere il lotto, seguire i suggerimenti per utilizzare lo stesso pacchetto.


3

Come altri hanno spiegato, non esiste un "pacchetto secondario" in Java: tutti i pacchetti sono isolati e non ereditano nulla dai loro genitori.

Un modo semplice per accedere ai membri della classe protetti da un altro pacchetto è estendere la classe e sovrascrivere i membri.

Ad esempio, per accedere ClassInAal pacchetto 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 classe in quel pacchetto che sovrascrive i metodi di cui hai bisogno ClassInA:

package a.b;

import a.ClassInA;

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

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

Ciò consente di utilizzare la classe di sostituzione al posto della classe nell'altro pacchetto:

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

Si noti che questo funziona solo per i membri protetti, che sono visibili alle classi estendenti (ereditarietà) e non ai membri privati ​​del pacchetto che sono visibili solo alle classi secondarie / estendibili all'interno dello stesso pacchetto. Spero che questo aiuti qualcuno!


3

La maggior parte delle risposte qui ha affermato che non esiste un pacchetto secondario in Java, ma ciò non è strettamente accurato. Questo termine è stato nelle specifiche del linguaggio Java fin da Java 6, e probabilmente anche più indietro (non sembra esserci una versione liberamente accessibile di JLS per le versioni precedenti di Java). La lingua attorno ai pacchetti secondari non è cambiata molto in JLS da Java 6.

Java 13 JLS :

I membri di un pacchetto sono i suoi pacchetti secondari e tutti i tipi di classe di livello superiore e i tipi di interfaccia di livello superiore dichiarati in tutte le unità di compilazione del pacchetto.

Ad esempio, nell'API della piattaforma Java SE:

  • Il pacchetto javaha sottopackage awt, applet, io, lang, net, e utille unità, ma non di compilazione.
  • Il pacchetto java.awtha un pacchetto secondario denominato image, oltre a un numero di unità di compilazione contenenti dichiarazioni di classe e tipi di interfaccia.

Il concetto di subpackage è rilevante, così come impone vincoli di denominazione tra pacchetti e classi / interfacce:

Un pacchetto non può contenere due membri con lo stesso nome o un errore di compilazione risulta.

Ecco alcuni esempi:

  • Poiché il pacchetto java.awtha un pacchetto secondario image, non può (e non contiene) una dichiarazione di una classe o di un tipo di interfaccia denominato image.
  • Se esiste un pacchetto denominato mousee un tipo di membro Buttonin quel pacchetto (che quindi potrebbe essere indicato come mouse.Button), allora non può esserci alcun pacchetto con il nome completo mouse.Buttono mouse.Button.Click.
  • Se com.nighthacks.java.jagè il nome completo di un tipo, non può esserci alcun pacchetto il cui nome completo sia com.nighthacks.java.jago com.nighthacks.java.jag.scrabble.

Tuttavia, questa restrizione di denominazione è l' unico significato dato ai pacchetti secondari dalla lingua:

La struttura gerarchica di denominazione per i pacchetti è utile per organizzare i pacchetti correlati in modo convenzionale, ma non ha alcun significato in sé oltre al divieto di un pacchetto con un pacchetto secondario con lo stesso nome semplice di un tipo di livello superiore dichiarato in quel pacchetto .

Ad esempio, non esiste una relazione di accesso speciale tra un pacchetto denominato olivere un altro pacchetto denominato oliver.twisto tra pacchetti denominati evelyn.woode evelyn.waugh. Cioè, il codice in un pacchetto denominato oliver.twistnon ha un migliore accesso ai tipi dichiarati all'interno del pacchetto oliverrispetto al codice in qualsiasi altro pacchetto.


Con questo contesto, possiamo rispondere alla domanda stessa. Poiché non esiste esplicitamente una relazione di accesso speciale tra un pacchetto e il suo pacchetto secondario o tra due pacchetti secondari diversi di un pacchetto padre, non esiste alcun modo all'interno della lingua per rendere visibile un metodo a due pacchetti diversi nel modo richiesto. Questa è una decisione di progettazione documentata e intenzionale.

Il metodo può essere reso pubblico e tutti i pacchetti (inclusi odp.proje odp.proj.test) saranno in grado di accedere ai metodi indicati, oppure il metodo potrebbe essere reso pacchetto privato (la visibilità predefinita) e deve essere inserito tutto il codice che deve accedervi direttamente lo stesso (sotto) pacchetto del metodo.

Detto questo, una pratica molto standard in Java è quella di inserire il codice di test nello stesso pacchetto del codice sorgente, ma in una posizione diversa sul file system. Ad esempio, nello strumento di compilazione Maven , la convenzione sarebbe quella di inserire questi file di origine e di test in src/main/java/odp/proje src/test/java/odp/proj, rispettivamente. Quando lo strumento di compilazione lo compila, entrambi i set di file finiscono nel odp.projpacchetto, ma solo i srcfile sono inclusi nell'artefatto di produzione; i file di test vengono utilizzati solo al momento della creazione per verificare i file di produzione. Con questa configurazione, il codice di test può accedere liberamente a qualsiasi pacchetto di codice privato o protetto del codice che sta testando, poiché si troveranno nello stesso pacchetto.

Nel caso in cui si desideri condividere il codice tra subpackage o pacchetti fratelli che non sono il caso di test / produzione, una soluzione che ho visto usare alcune librerie è quella di mettere quel codice condiviso come pubblico, ma documentare che è destinato alla libreria interna utilizzare solo.


0

Senza mettere il modificatore di accesso davanti al metodo dici che è un pacchetto privato.
Guarda il seguente esempio.

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

Con la classe PackageVisibleHelper e mantienila privata prima che PackageVisibleHelperFactory sia congelato, possiamo invocare il metodo launchA (di PackageVisibleHelper) ovunque :)

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); 
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.