176

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

编辑:如果Java中没有子包的概念,有什么办法吗?我有某些方法,我希望只对测试人员和该包的其他成员可用。我应该把所有东西都扔到同一个包裹里吗?使用广泛的反射?

4

9 回答 9

185

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

于 2009-12-27T22:32:27.083 回答
63

您的包的名称暗示这里的应用程序用于单元测试。使用的典型模式是将您希望测试的类和单元测试代码放在同一个包中(在您的情况下odp.proj)但在不同的源代码树中。所以你会把你的类src/odp/proj和你的测试代码放在test/odp/proj.

Java 确实有“包”访问修饰符,它是未指定时的默认访问修饰符(即,您没有指定公共、私有或受保护)。使用“package”访问修饰符,只有其中的类odp.proj才能访问方法。但请记住,在 Java 中,不能依赖访问修饰符来强制执行访问规则,因为通过反射,任何访问都是可能的。访问修饰符只是提示性的(除非存在限制性安全管理器)。

于 2009-12-27T22:36:33.150 回答
17

这里的大多数答案都表明Java中没有子包之类的东西,但这并不准确。该术语在Java 语言规范(JLS) 中定义,并且自规范的初始版本以来一直如此。

Java 15 JLS

包的成员是它的子包以及在包的所有编译单元中声明的所有顶级类类型和顶级接口类型。

例如,在 Java SE 平台 API 中:

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

子包的概念具有实际意义,因为它强制包和类/接口之间的命名约束:

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

这里有些例子:

  • 因为包java.awt有一个子包image,所以它不能(也不)包含名为 的类或接口类型的声明image
  • 如果有一个名为的包mouse和该包中的成员类型Button(然后可能被称为mouse.Button),则不能有任何具有完全限定名称的包mouse.Buttonmouse.Button.Click.
  • 如果com.nighthacks.java.jag是 一个类型的完全限定名称,则不能有任何完全限定名称为com.nighthacks.java.jag或的包com.nighthacks.java.jag.scrabble

然而,这个命名限制是语言赋予子包的唯一意义:

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

例如,一个包名oliver和另一个包名oliver.twist之间,或者包名evelyn.wood和包之间没有特殊的访问关系evelyn.waugh。也就是说,命名包中的代码对包oliver.twist中声明的类型的访问没有oliver任何其他包中的代码更好。


有了这个背景,我们就可以回答这个问题本身了。由于包与其子包之间或父包的两个不同子包之间没有特殊的访问关系,因此语言内无法以请求的方式使方法对两个不同的包可见,同时限制其访问其他包。这是一个记录在案的、有意的设计决策。

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

关于测试用例,Java 的标准做法是将类型的测试代码与其源代码放在同一个包中,但放在文件系统上的不同位置。例如,在MavenGradle构建工具中,约定是将源文件src/main/java/odp/proj放在 src/test/java/odp/proj. 由构建工具编译时,两个目录中的项目最终都在odp.proj包中,但只有src文件包含在生产工件中;测试文件仅在构建时用于验证生产文件。使用此设置,测试代码可以自由访问它正在测试的代码的任何包私有或受保护代码,因为它们将位于同一个包中。

如果您希望在不是测试/生产案例的子包或兄弟包之间共享代码,我见过一些库使用的一种解决方案是将共享代码公开,但记录它是用于内部库的仅使用。

于 2020-01-28T20:43:29.063 回答
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并为不存在的测试方法创建适当的文件夹。

于 2009-12-27T22:33:44.327 回答
5

当我在 IntelliJ 中执行此操作时,我的源代码树如下所示:

src         // source root
- odp
   - proj   // .java source here
- test      // test root
  - odp
     - proj // JUnit or TestNG source here
于 2009-12-27T22:41:00.523 回答
5

正如其他人所解释的那样,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()));
    }
}

请注意,这仅适用于扩展类(继承)可见的受保护成员,而不适用于仅对同一包中的子/扩展类可见的包私有成员。希望这可以帮助某人!

于 2018-01-11T18:36:36.930 回答
4

编辑:如果Java中没有子包的概念,有什么办法吗?我有某些方法,我希望只对测试人员和该包的其他成员可用。

这可能有点取决于你不显示它们的动机,但如果唯一的原因是你不想用仅用于测试的东西(或其他一些内部的东西)污染公共接口,我会把这些方法放在一个单独的公共接口并让“隐藏”方法的消费者使用该接口。它不会阻止其他人使用该界面,但我认为您没有理由这样做。

对于单元测试,如果可以不重写批次,请按照建议使用相同的包。

于 2009-12-27T22:39:34.177 回答
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
    }
}
于 2009-12-27T22:37:02.450 回答
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); 
    }
}
于 2017-04-01T07:51:48.087 回答