Java: Sichtbarkeit von Unterpaketen?


150

Ich habe zwei Pakete in meinem Projekt: odp.projund odp.proj.test. Es gibt bestimmte Methoden, die nur für die Klassen in diesen beiden Paketen sichtbar sein sollen. Wie kann ich das machen?

EDIT: Wenn es in Java kein Konzept für ein Unterpaket gibt, gibt es einen Weg, dies zu umgehen? Ich habe bestimmte Methoden, die ich nur Testern und anderen Mitgliedern dieses Pakets zur Verfügung stellen möchte. Soll ich einfach alles in das gleiche Paket werfen? Verwenden Sie umfangreiche Reflexion?




2
Abgesehen davon sollten Tests immer nur das Verhalten Ihrer Objekte testen, das von außerhalb des Pakets beobachtet werden kann. Wenn Sie über Ihre Tests auf Methoden / Klassen im Paketbereich zugreifen, wird mir mitgeteilt, dass es sich bei den Tests wahrscheinlich um Testimplementierungen handelt, nicht um Verhaltensweisen. Mit einem Build-Tool wie maven oder gradle können Ihre Tests problemlos im selben Klassenpfad ausgeführt werden, sind jedoch nicht im endgültigen Jar enthalten (eine gute Sache), sodass sie keine unterschiedlichen Paketnamen haben müssen. Wenn Sie sie jedoch trotzdem in separate Pakete packen , müssen Sie sicherstellen , dass Sie nicht auf den privaten / Standardbereich zugreifen und somit nur die öffentliche API testen.
Derek

3
Dies kann zutreffen, wenn Sie rein verhaltensorientiert arbeiten und möchten, dass Ihre Tests nur Black-Box-Tests durchführen. Es kann jedoch Fälle geben, in denen die Umsetzung des gewünschten Verhaltens eine unvermeidlich hohe zyklomatische Komplexität erfordert. In diesem Fall kann es hilfreich sein, die Implementierung in kleinere, einfachere Blöcke (die für die Implementierung noch privat sind) aufzuteilen und einige Komponententests zu schreiben, um White-Box-Tests auf den verschiedenen Pfaden durch diese Blöcke durchzuführen.
James Woods

Antworten:


165

Das kannst du nicht. In Java gibt es kein Konzept eines subpackage, so odp.projund odp.proj.testsind völlig getrennte Pakete.


10
Obwohl es mir so gefällt, ist es verwirrend, dass die meisten IDEs Pakete mit demselben Namen zusammenstellen. Danke für die Klarstellung.
JacksOnF1re

Dies ist nicht genau: Das JLS definiert Unterpakete, obwohl die einzige sprachliche Bedeutung darin besteht, "gegen ein Paket mit einem Unterpaket mit demselben einfachen Namen wie ein Typ der obersten Ebene" zu verbieten. Ich habe gerade eine Antwort auf diese Frage hinzugefügt, die dies ausführlich erklärt.
M. Justin

59

Die Namen Ihrer Pakete deuten darauf hin, dass die Anwendung hier für Unit-Tests vorgesehen ist. Das typische Muster besteht darin, die zu testenden Klassen und den Unit-Test-Code in demselben Paket (in Ihrem Fall odp.proj), jedoch in unterschiedlichen Quellbäumen abzulegen . Sie würden also Ihre Klassen src/odp/projund Ihren Testcode eingeben test/odp/proj.

Java verfügt über den Zugriffsmodifikator "package", der der Standardzugriffsmodifikator ist, wenn keiner angegeben ist (dh Sie geben weder public, private noch protected an). Mit dem Zugriffsmodifikator "package" haben nur Klassen in odp.projZugriff auf die Methoden. Beachten Sie jedoch, dass in Java nicht auf die Zugriffsmodifikatoren zurückgegriffen werden kann, um Zugriffsregeln durchzusetzen, da mit Reflexion jeder Zugriff möglich ist. Zugriffsmodifikatoren sind lediglich ein Hinweis (es sei denn, ein restriktiver Sicherheitsmanager ist vorhanden).


11

Dies ist keine besondere Beziehung zwischen odp.projund odp.proj.test- sie werden einfach als scheinbar verwandt bezeichnet.

Wenn das Paket odp.proj.test lediglich Tests bereitstellt, können Sie denselben Paketnamen ( odp.proj) verwenden. IDEs wie Eclipse und Netbeans erstellen separate Ordner ( src/main/java/odp/projund src/test/java/odp/proj) mit demselben Paketnamen, jedoch mit JUnit-Semantik.

Beachten Sie, dass diese IDEs Tests für Methoden in generieren odp.projund den entsprechenden Ordner für die Testmethoden erstellen, die nicht vorhanden sind.


5

Wenn ich dies in IntelliJ mache, sieht mein Quellbaum folgendermaßen aus:

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

4

EDIT: Wenn es in Java kein Konzept für ein Unterpaket gibt, gibt es einen Weg, dies zu umgehen? Ich habe bestimmte Methoden, die ich nur Testern und anderen Mitgliedern dieses Pakets zur Verfügung stellen möchte.

Es hängt wahrscheinlich ein wenig von Ihren Motiven ab, sie nicht anzuzeigen, aber wenn der einzige Grund darin besteht, dass Sie die öffentliche Schnittstelle nicht mit Dingen verschmutzen möchten, die nur zum Testen (oder einer anderen internen Sache) bestimmt sind, würde ich die Methoden in a einfügen separate öffentliche Schnittstelle und lassen Sie die Konsumenten der "versteckten" Methoden diese Schnittstelle verwenden. Es wird andere nicht davon abhalten, die Schnittstelle zu verwenden, aber ich sehe keinen Grund, warum Sie sollten.

Befolgen Sie für Unit-Tests und wenn es möglich ist, das Los nicht neu zu schreiben, die Vorschläge, dasselbe Paket zu verwenden.


3

Wie andere erklärt haben, gibt es in Java kein "Unterpaket": Alle Pakete sind isoliert und erben nichts von ihren Eltern.

Eine einfache Möglichkeit, von einem anderen Paket aus auf geschützte Klassenmitglieder zuzugreifen, besteht darin, die Klasse zu erweitern und die Mitglieder zu überschreiben.

Zum Beispiel, um ClassInAim Paket zuzugreifen 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(); }
}

Erstellen Sie in diesem Paket eine Klasse, die die folgenden Methoden überschreibt ClassInA:

package a.b;

import a.ClassInA;

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

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

Auf diese Weise können Sie die überschreibende Klasse anstelle der Klasse im anderen Paket verwenden:

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

Beachten Sie, dass dies nur für geschützte Mitglieder funktioniert, die für Erweiterungsklassen sichtbar sind (Vererbung), und nicht für paketprivate Mitglieder, die nur für Unter- / Erweiterungsklassen innerhalb desselben Pakets sichtbar sind. Hoffentlich hilft das jemandem!


3

Die meisten Antworten hier haben angegeben, dass es in Java kein Unterpaket gibt, aber das ist nicht genau. Dieser Begriff wurde bereits in Java 6 und wahrscheinlich weiter hinten in der Java-Sprachspezifikation verwendet (es scheint keine frei zugängliche Version des JLS für frühere Java-Versionen zu geben). Die Sprache um Unterpakete hat sich im JLS seit Java 6 nicht wesentlich geändert.

Java 13 JLS :

Die Mitglieder eines Pakets sind seine Unterpakete und alle Klassentypen der obersten Ebene und Schnittstellentypen der obersten Ebene, die in allen Kompilierungseinheiten des Pakets deklariert sind.

Zum Beispiel in der Java SE Platform API:

  • Das Paket javahat Subpackages awt, applet, io, lang, net, und util, aber keine Übersetzungseinheiten.
  • Das Paket java.awtenthält ein Unterpaket mit dem Namen imagesowie eine Reihe von Kompilierungseinheiten, die Deklarationen von Klassen- und Schnittstellentypen enthalten.

Das Unterpaketkonzept ist relevant, ebenso wie die Durchsetzung von Namensbeschränkungen zwischen Paketen und Klassen / Schnittstellen:

Ein Paket enthält möglicherweise nicht zwei Mitglieder mit demselben Namen, oder es tritt ein Fehler beim Kompilieren auf.

Hier sind einige Beispiele:

  • Da das Paket java.awtein Unterpaket enthält image, kann (und darf) es keine Deklaration einer Klasse oder eines Schnittstellentyps mit dem Namen enthalten image.
  • Wenn in diesem Paket ein Paket mit dem Namen mouseund einem Mitgliedstyp Buttonenthalten ist (der dann als bezeichnet werden kann mouse.Button), kann es kein Paket mit dem vollständig qualifizierten Namen mouse.Buttonoder geben mouse.Button.Click.
  • Wenn com.nighthacks.java.jages sich um den vollständig qualifizierten Namen eines Typs handelt, kann es kein Paket geben, dessen vollständig qualifizierter Name entweder com.nighthacks.java.jagoder ist com.nighthacks.java.jag.scrabble.

Diese Namensbeschränkung ist jedoch die einzige Bedeutung, die Unterpakete durch die Sprache erhalten:

Die hierarchische Namensstruktur für Pakete soll praktisch sein, um verwandte Pakete auf herkömmliche Weise zu organisieren, hat jedoch an sich keine andere Bedeutung als das Verbot, dass ein Paket ein Unterpaket mit demselben einfachen Namen wie ein in diesem Paket deklarierter Typ der obersten Ebene aufweist .

Beispielsweise gibt es keine spezielle Zugriffsbeziehung zwischen einem Paket mit dem Namen oliverund einem anderen Paket mit dem Namen oliver.twistoder zwischen den Paketen mit dem Namen evelyn.woodund evelyn.waugh. Das heißt, der Code in einem Paket mit dem Namen oliver.twisthat keinen besseren Zugriff auf die im Paket deklarierten Typen oliverals der Code in einem anderen Paket.


In diesem Zusammenhang können wir die Frage selbst beantworten. Da es explizit keine spezielle Zugriffsbeziehung zwischen einem Paket und seinem Unterpaket oder zwischen zwei verschiedenen Unterpaketen eines übergeordneten Pakets gibt, gibt es innerhalb der Sprache keine Möglichkeit, eine Methode für zwei verschiedene Pakete auf die angeforderte Weise sichtbar zu machen. Dies ist eine dokumentierte, absichtliche Entwurfsentscheidung.

Entweder kann die Methode veröffentlicht werden und alle Pakete (einschließlich odp.projund odp.proj.test) können auf die angegebenen Methoden zugreifen, oder die Methode kann als Paket privat gemacht werden (Standardsichtbarkeit), und der gesamte Code, der für den direkten Zugriff darauf erforderlich ist, muss eingegeben werden das gleiche (Unter-) Paket wie die Methode.

In Java ist es jedoch sehr üblich, den Testcode im selben Paket wie den Quellcode abzulegen, jedoch an einem anderen Ort im Dateisystem. Im Maven- Build-Tool besteht die Konvention beispielsweise darin, diese Quell- und Testdateien in src/main/java/odp/projbzw. abzulegen src/test/java/odp/proj. Wenn das Build-Tool dies kompiliert, landen beide Dateigruppen im odp.projPaket, aber nur die srcDateien sind im Produktionsartefakt enthalten. Die Testdateien werden nur zur Erstellungszeit verwendet, um die Produktionsdateien zu überprüfen. Mit diesem Setup kann der Testcode frei auf jeden privaten oder geschützten Paketcode des getesteten Codes zugreifen, da er sich im selben Paket befindet.

In dem Fall, in dem Sie Code-Freigabe für Unterpakete oder Geschwisterpakete wünschen, der nicht der Test- / Produktionsfall ist, besteht eine Lösung, die einige Bibliotheken verwendet haben, darin, diesen freigegebenen Code als öffentlich zu kennzeichnen, aber zu dokumentieren, dass er für die interne Bibliothek bestimmt ist nur benutzen.


0

Ohne den Zugriffsmodifikator vor die Methode zu stellen, sagen Sie, dass es sich um ein privates Paket handelt.
Schauen Sie sich das folgende Beispiel an.

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

Mit der PackageVisibleHelper-Klasse, die vor dem Einfrieren von PackageVisibleHelperFactory privat bleibt, können wir die Methode launchA (von PackageVisibleHelper) überall aufrufen :)

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); 
    }
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.