Java:子包可见性?


150

我的项目中有两个软件包:odp.projodp.proj.test。我希望某些方法仅对这两个软件包中的类可见。我怎样才能做到这一点?

编辑:如果在Java中没有子包的概念,有没有解决的办法?我有某些方法只想供测试人员和该软件包的其他成员使用。我应该把所有东西都放进同一个包装吗?使用广泛的反思?




2
顺便说一句,测试仅应从包的外部对对象的行为进行测试。从测试访问包作用域方法/类告诉我测试可能是在测试实现,而不是行为。使用maven或gradle之类的构建工具,它们将使您的测试易于在相同的类路径中运行,但不会包含在最终的jar中(这是一件好事),因此不需要它们具有不同的程序包名称。但是无论如何将它们放在单独的程序包中是为了强制您不要访问私有/默认范围,从而仅测试公共api。
derekv 2015年

3
如果您是以纯行为驱动的方式工作,并且希望您的测试仅进行黑盒测试,则可能是这样。但是在某些情况下,实现所需行为需要不可避免的高循环复杂性。在这种情况下,最好将实现分解为更小,更简单的块(仍然对实现私有),并编写一些单元测试以对通过这些块的不同路径执行白盒测试。
詹姆斯·伍兹

Answers:


165

你不能 在Java中,没有子包的概念,因此odp.projodp.proj.test是完全独立的包。


10
尽管我喜欢这种方式,但是大多数IDE将具有相同名称的程序包放在一起却令人困惑。感谢您的澄清。
JacksOnF1re

这不是严格准确的:JLS确实定义了子包,尽管它们唯一的语言含义是禁止“针对具有与顶级类型相同的简单名称的子包的包”。我刚刚为这个问题添加了答案,详细说明了这一点。
贾斯汀

59

软件包的名称表明此处的应用程序用于单元测试。使用的典型模式是将要测试的类和单元测试代码放在同一包中(在您的情况下odp.proj),但放在不同的源树中。因此,您可以将类放入src/odp/proj,将测试代码放入test/odp/proj

Java确实具有“包”访问修饰符,这是未指定时的默认访问修饰符(即,您未指定public,private或protected)。使用“包”访问修饰符,只有中的类odp.proj才能访问方法。但是请记住,在Java中,不能依靠访问修饰符来强制执行访问规则,因为通过反射,任何访问都是可能的。访问修饰符仅是提示性的(除非存在限制性的安全管理器)。


11

odp.proj和之间没有特殊关系odp.proj.test-它们恰好被命名为显然相关。

如果odp.proj.test软件包仅提供测试,则可以使用相同的软件包名称(odp.proj)。像Eclipse和Netbeans这样的IDE将创建单独的文件夹(src/main/java/odp/projsrc/test/java/odp/proj具有相同程序包名称但具有JUnit语义的和)。

请注意,这些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中没有“子包”这样的东西:所有包都是隔离的,不会从其父级继承任何东西。

从另一个包访问受保护的类成员的一种简单方法是扩展类并覆盖成员。

例如,要访问ClassInApackage 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 6中就已经存在于Java语言规范中,甚至可能更远(对于Java的早期版本,似乎没有JLS的免费版本)。自Java 6起,围绕子包的语言在JLS中并未发生太大变化。

Java 13 JLS

包的成员是其子包,并且在包的所有编译单元中声明了所有顶级类类型和顶级接口类型。

例如,在Java SE Platform API中:

  • 封装java有子包awtappletiolangnet,和util,但没有编译单元。
  • 该程序包java.awt有一个名为的子程序包image,以及许多包含类和接口类型的声明的编译单元。

子包概念是相关的,因为它强制在包与类/接口之间进行命名约束:

一个程序包可能不包含两个相同名称的成员,否则会导致编译时错误。

这里有些例子:

  • 由于该程序包java.awt具有子程序包image,因此它不能(也没有)包含名为的类或接口类型的声明image
  • 如果有一个名为的程序包,mouse并且该程序包中有一个成员类型Button(然后可能称为mouse.Button),则不能有任何具有完全限定名称mouse.Button或的程序包mouse.Button.Click
  • 如果com.nighthacks.java.jag是类型的完全限定名称,则不能存在任何完全限定名称为com.nighthacks.java.jag或的包com.nighthacks.java.jag.scrabble

但是,此命名限制是该语言为子包提供的唯一含义:

程序包的分层命名结构旨在方便以常规方式组织相关程序包,但除禁止具有与该程序包中声明的顶级类型具有相同简单名称的子程序包的子程序包外,其本身没有其他意义。 。

例如,有一个名为包之间没有特殊的调用关系oliver,并命名为另一个包oliver.twist,或命名的包间evelyn.woodevelyn.waugh。也就是说,与其他任何程序包中的代码相比,名为程序包中的代码oliver.twist无法更好地访问该程序包中声明的类型oliver


在这种情况下,我们可以回答问题本身。由于在包及其子包之间或父包的两个不同子包之间显然没有特殊的访问关系,因此该语言中没有办法以所请求的方式使方法对两个不同的包可见。这是有据可查的故意设计决策。

可以使该方法公开,并且所有包(包括odp.projodp.proj.test)都可以访问给定的方法,或者可以将该方法设置为私有包(默认可见性),并且必须将需要直接访问它的所有代码放入与方法相同的(子)包。

也就是说,Java中非常标准的做法是将测试代码与源代码放在同一包中,但是放在文件系统上的不同位置。例如,在Maven构建工具中,约定是将这些源文件和测试文件分别放在src/main/java/odp/proj和中 src/test/java/odp/proj。当构建工具对此进行编译时,两组文件最终都包含在odp.proj包中,但是src生产人工制品中仅包含这些文件。测试文件仅在构建时用于验证生产文件。通过此设置,测试代码可以自由访问其正在测试的代码的任何程序包私有或受保护代码,因为它们将位于同一程序包中。

如果您要在非测试/生产情况下的子包或同级包之间共享代码,我已经看到一些库使用的一种解决方案是将共享代码公开,但要记录该文件供内部库使用仅使用。


0

如果不将access修饰符放在方法的前面,则可以说它是包私有的。
看下面的例子。

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); 
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.