我建议您不要将类公开,而是将构造函数公开,并让人们使用您的类实现的公共接口。最好将包的 API 启动为公共接口(可能还有一些公共抽象类),并通过不将它们标记为公共来隐藏您的实现类,以便您可以随着时间的推移更改它们。然后,您可以在您的包中提供一个公共工厂方法来实例化您的包私有类并将它们作为接口类型返回。这是一个公开的接口:
package stackoverflow;
public interface Widget {
public void doWidgetWork(String work);
}
这是“包私有”的实现。编译器不会让同一个包之外的代码导入,也不会使用这个类:
package stackoverflow;
/*package*/ class WidgetHidden implements Widget {
public WidgetHidden(String configOptionA, String configOptionB){
// ...
}
public WidgetHidden(){
// ...
}
public void doWidgetWork(String work)[
// ...
}
}
请注意,第二次出现的单词 /package/ 是注释(在 java 中使用该单词是不合法的),但许多程序员在该位置使用这样的注释来显示人们认为班级不公开并非偶然;它表示开发人员真的打算将该类故意“包私有”。为了让人们从你的包之外实例化这个类,你提供了一个静态工厂类(或者一个实例工厂类):
package stackoverflow;
public class WidgetFactory {
public static Widget newInstance( String configOptionA, String configOptionB) {
return new Widget( String configOptionA, String configOptionB);
}
}
工厂类的全部意义在于它隐藏了您的内部类(您隐藏为包私有的那些)。随着时间的推移,您可以更改工厂类以返回新类或重命名或删除 WidgetHidden 类。
许多框架通过将它们放入一个名称为“internal”的包中来指示其他开发人员不应使用哪些类。公共接口将在主包中(例如“com.stackoverflow.widget”)和隐藏类到您的内部包中,它只公开公共工厂类(例如“com.stackoverflow.widget.internal”)。
主题的一个变体是不在工厂类上使用静态方法;使其成为常规方法。替代方法称为“静态工厂”或“实例工厂”,具体取决于方法是否为静态。对于使用你的包的人来说,不使方法静态似乎更多工作,因为他们首先必须实例化你的工厂对象,然后才能使用它来创建小部件。当人们可能想要为工厂的构造函数上的所有小部件设置一些默认值然后使用非静态 newInstance 方法来指定默认值之外的任何内容时,这是有帮助的:
public class WidgetInstanceFactory {
private String defaultOptionA = null;
public WidgetInstanceFactory( String defaultOptionA ) {
this.defaultOptionA = defaultOptionA;
}
public Widget newInstance( String optionB ) {
return new WidgetHidden( this.defaultOptionA, optionB );
}
}
可以使用反射来绕过包私有保护来查找和调用构造函数。 Spring 框架的一个非常好的特性是,即使没有工厂类,它也会实例化不公开的类(尽管提供 Spring 也乐于使用的工厂类更为礼貌)。以下代码将起作用:
package stackoverflow.other;
class TestInstantiate {
private Widget myWidget = null;
public TestInstantiate(){
this.myWidget = instantiatePackagePrivateClass("stackoverflow.WidgetHidden");
}
private Widget instantiatePackagePrivateClass(String className)
throws ClassNotFoundException, NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
@SuppressWarnings("unchecked")
Class<FileUploadSequence> clazz = (Class<Widget>) Class.forName(className);
Constructor<Widget> constructor = clazz.getConstructor(new Class[]{});
constructor.setAccessible(true);
Widget widget = (Widget) constructor.newInstance((Object[])null);
return widget;
}
}
在该示例中,我使用了无参数构造函数,但显然您可以使用相同的方法找到并调用两个字符串构造函数。显然,这样的代码绕过了编写 WidgetHidden 的程序员的意图;他们想隐藏它,因为他们可能会改变它。任何使用这种后门来操作包私有对象的人都应该知道,类 WidgetHidden 不是他们正在使用的框架的公共 API 的一部分,因此它可能会被删除或更改,而不会被编写您正在使用的软件包。将其重命名为 WidgetInternal 并将其放入“内部”包中,这使得您告诉人们“不要使用”的情况越来越多。 JVM 具有可选的安全设置,可防止人们使用此类技巧;但是运行 JVM 的人必须在外部对其进行配置以禁止此类技巧,这仅在您想要运行您不信任的其他代码并防止它使用此类技巧时才有用。
Josha Block 第 2 版的《Effective Java》一书对尝试编写好的 API 时的陷阱进行了大量讨论、示例和细节。它有很多细节可以解释为什么您应该始终使用许多其他好的“交易技巧”来隐藏尽可能多的类。