Java:サブパッケージの可視性?


150

プロジェクトに2つのパッケージがあります:odp.projodp.proj.test。これらの2つのパッケージのクラスだけに表示したい特定のメソッドがあります。これどうやってするの?

編集: Javaにサブパッケージの概念がない場合、これを回避する方法はありますか?テスターやそのパッケージの他のメンバーだけが使用できるようにする特定のメソッドがあります。私はすべてを同じパッケージに入れるべきですか?広範な反射を使用しますか?




2
余談ですが、テストはオブジェクトの動作をパッケージの外部から観察できるものとしてのみテストする必要があります。テストからパッケージスコープのメソッド/クラスにアクセスすると、テストが動作ではなく実装をテストしていることがわかります。mavenやgradleなどのビルドツールを使用すると、テストを同じクラスパスで簡単に実行できますが、最終的なjarには含まれないため(良いことです)、異なるパッケージ名を付ける必要はありません。それでも、それらを別々のパッケージに入れることは、private / defaultスコープにアクセスせず、public apiのみをテストすることを強制することです
derekv 2015

3
これは、純粋に動作主導の方法で作業していて、テストでブラックボックステストのみを実行する場合に当てはまることがあります。ただし、目的の動作を実装するには、やむを得ず高いサイクロマティックな複雑さが必要になる場合があります。この場合、実装をより小さく単純なチャンク(まだ実装に対してプライベート)に分割し、これらのチャンクを通るさまざまなパスでホワイトボックステストを実行するためのユニットテストを作成すると便利です。
James Woods

回答:


165

できません。Javaにはサブパッケージの概念がないためodp.projodp.proj.test完全に別のパッケージです。


10
私はこの方法が好きですが、ほとんどのIDEが同じ名前のパッケージをまとめるのは混乱しています。説明をありがとう。
JacksOnF1re 2016年

これは厳密には正確ではありません。JLSはサブパッケージを定義しますが、それらが持つ唯一の言語の重要性は、「トップレベルタイプと同じ単純な名前のサブパッケージを持つパッケージに対して」禁止することです。これを詳細に説明するこの質問への回答を追加しました。
M.ジャスティン

59

パッケージの名前は、ここでのアプリケーションが単体テスト用であることを示唆しています。使用される典型的なパターンは、テストしたいクラスとユニットテストコードを同じパッケージ(あなたの場合odp.proj)に、異なるソースツリーに置くことです。したがって、クラスはsrc/odp/projに、テストコードはに配置しtest/odp/projます。

Javaには「パッケージ」アクセス修飾子があります。これは、何も指定されていない場合(つまり、パブリック、プライベート、または保護を指定していない場合)のデフォルトのアクセス修飾子です。「パッケージ」アクセス修飾子odp.projを使用すると、のクラスのみがメソッドにアクセスできます。ただし、Javaでは、リフレクションを使用して任意のアクセスが可能であるため、アクセス修飾子を使用してアクセスルールを適用することはできません。アクセス修飾子は単に示唆的なものです(制限的なセキュリティマネージャーが存在しない限り)。


11

これは、間に特別な関係はありませんodp.projし、odp.proj.test彼らは明らかに関連して命名することが起こります- 。

odp.proj.testパッケージが単にテストを提供している場合は、同じパッケージ名(odp.proj)を使用できます。EclipseやNetbeansのようなIDE は、同じパッケージ名でJUnitセマンティクスを持つ別個のフォルダー(src/main/java/odp/projおよびsrc/test/java/odp/proj)を作成します。

これらのIDEはメソッドのテストを生成odp.projし、存在しないテストメソッド用の適切なフォルダーを作成することに注意してください。


5

IntelliJでこれを行うと、ソースツリーは次のようになります。

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

4

編集:Javaにサブパッケージの概念がない場合、これを回避する方法はありますか?テスターやそのパッケージの他のメンバーだけが使用できるようにする特定のメソッドがあります。

おそらくそれらを表示しない動機に少し依存しますが、唯一の理由が、テストのみを目的としたもの(またはその他の内部的なもの)でパブリックインターフェイスを汚染したくない場合は、メソッドを別個のパブリックインターフェイスを使用して、「非表示」メソッドのコンシューマーにそのインターフェイスを使用させる。他の人がインターフェースを使用するのを止めることはありませんが、あなたがそうすべき理由は私にはわかりません。

単体テストの場合、ロットを書き直さなくても可能であれば、提案に従って同じパッケージを使用してください。


3

他の人が説明したように、Javaには「サブパッケージ」などはありません。すべてのパッケージは分離され、親から何も継承しません。

別のパッケージから保護されたクラスメンバーにアクセスする簡単な方法は、クラスを拡張してメンバーをオーバーライドすることです。

たとえばClassInA、パッケージでアクセスするには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(); }
}

そのパッケージに、必要なメソッドをオーバーライドするクラスを作成しますClassInA

package a.b;

import a.ClassInA;

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

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

これにより、他のパッケージのクラスの代わりにオーバーライドクラスを使用できます。

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

これは、拡張クラス(継承)に表示される保護されたメンバーに対してのみ機能し、同じパッケージ内のサブ/拡張クラスにのみ表示されるパッケージプライベートメンバーには機能しないことに注意してください。うまくいけば、これは誰かを助けるでしょう!


3

ここでの回答のほとんどは、Javaにはサブパッケージなどがないと述べていますが、厳密には正確ではありません。この用語は、Java言語仕様に含まれており、Java 6までさかのぼります(おそらく以前のバージョンのJava用のJLSの自由にアクセスできるバージョンはないようです)。サブパッケージを取り巻く言語は、Java 6以降のJLSではあまり変わっていません。

Java 13 JLS

パッケージのメンバーは、そのサブパッケージと、パッケージのすべてのコンパイルユニットで宣言されているすべてのトップレベルのクラスタイプとトップレベルのインターフェイスタイプです。

たとえば、Java SEプラットフォームAPIでは次のようになります。

  • パッケージには、javaサブパッケージがあるawtappletiolangnet、とutil、ないコンパイル単位を。
  • パッケージにjava.awtは、という名前のサブパッケージとimage、クラスおよびインターフェイスタイプの宣言を含むいくつかのコンパイル単位があります。

サブパッケージの概念は、パッケージとクラス/インターフェース間の命名の制約を適用するので、関連しています。

パッケージには同じ名前の2つのメンバーが含まれていない場合があり、そうでない場合はコンパイル時エラーが発生します。

ここではいくつかの例を示します。

  • パッケージjava.awtにはサブパッケージがあるため、imageという名前のクラスまたはインターフェイス型の宣言を含めることはできません(含めません)image
  • 名前が付けられたパッケージとそのパッケージ内のmouseメンバータイプButton(その後、と呼ばれるmouse.Button場合があります)がある場合、完全修飾名mouse.Buttonまたはのパッケージは存在できませんmouse.Button.Click
  • 場合com.nighthacks.java.jag型の完全修飾名がある場合、その完全修飾名のいずれかである任意のパッケージがあることはできませんcom.nighthacks.java.jagcom.nighthacks.java.jag.scrabble

ただし、この命名の制限は、言語によってサブパッケージに与えられる唯一の重要性です。

パッケージの階層的な命名構造は、従来の方法で関連するパッケージを整理するのに便利であることを意図していますが、そのパッケージで宣言された最上位タイプと同じ単純な名前のサブパッケージを持つパッケージに対する禁止以外、それ自体には意味がありません。

たとえば、という名前のパッケージの間には特別なアクセスの関係が存在しないoliverとという名前の別のパッケージoliver.twist、または指定したパッケージの間evelyn.woodとはevelyn.waugh。つまり、named oliver.twistパッケージのoliverコードは、他のパッケージのコードよりも、パッケージ内で宣言された型にアクセスできません。


この文脈で、私たちは質問自体に答えることができます。パッケージとそのサブパッケージの間、または親パッケージの2つの異なるサブパッケージの間に特別なアクセス関係がないので、要求された方法でメソッドを2つの異なるパッケージから見えるようにする方法はありません。これは文書化された、意図的な設計上の決定です。

メソッドをパブリックにして、すべてのパッケージ( odp.projおよびodp.proj.test)が特定のメソッドにアクセスできるようにするか、メソッドをパッケージプライベート(デフォルトの可視性)にして、直接アクセスする必要のあるすべてのコードを入力する必要があります。メソッドと同じ(サブ)パッケージ。

つまり、Javaでの非常に標準的な慣行は、テストコードをソースコードと同じパッケージに、ファイルシステムの別の場所に配置することです。例えば、でMavenのビルドツール、条約はこれらのソースとテストファイルを置くことであろうsrc/main/java/odp/projsrc/test/java/odp/proj、それぞれ。ビルドツールがこれをコンパイルすると、両方のファイルセットがodp.projパッケージにsrc含まれますが、プロダクションアーティファクトにはファイルのみが含まれます。テストファイルは、本番ファイルを検証するためにビルド時にのみ使用されます。このセットアップにより、テストコードは同じパッケージ内にあるため、テストするコードの任意のパッケージプライベートコードまたは保護コードに自由にアクセスできます。

サブパッケージ間または兄弟パッケージ間でコードを共有したいが、テストや本番のケースではない場合、一部のライブラリで使用されているソリューションの1つは、その共有コードを公開することですが、内部ライブラリ向けであることを文書化します使用のみ。


0

メソッドの前にアクセス修飾子を置かずに、それはパッケージプライベートであると言います。
次の例を見てください。

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

PackageVisibleHelperクラスを使用して、PackageVisibleHelperFactoryがフリーズする前にプライベートにしておくと、どこからでもlaunchA(by PackageVisibleHelper)メソッドを呼び出すことができます。

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); 
    }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.