【问题标题】:Java newbie problem: package with private accessJava新手问题:具有私有访问权限的包
【发布时间】:2010-07-07 18:22:35
【问题描述】:

Pack.java 导入 pack.TestPack;但它无法访问它。我不明白为什么它无法访问该类,尽管导入。

错误

Pack.java:7: TestPack() is not public in pack.TestPack; cannot be accessed from outside package
        System.out.println(new TestPack().getHello());  
                           ^
1 error

Pack.java

import pack.TestPack;
import java.io.*;

public class Pack
{
        public static void main(String[] args){
                System.out.println(new TestPack().getHello());
        }
}

TestPack.java

package pack;
import java.util.*;
import java.io.*;

public class TestPack
{
        private String hello="if you see me, you ar inside class TestPack";
        public String getHello(){return hello;}
        TestPack(){}
}

【问题讨论】:

    标签: java package


    【解决方案1】:

    你应该公开 TestPack 的构造函数。

    public class TestPack
    {
            private String hello="if you see me, you ar inside class TestPack";
            public String getHello(){return hello;}
            public TestPack(){}
    }
    

    问题是,尽管 TestPack 的可见性是公开的,但它的无参数构造函数可见性是 package(这是您没有明确指定时的可见性)。

    package 可见性意味着同一个包中的类将能够看到它。由于TestPack和Pack不在同一个包中,所以Pack不能调用TestPack的构造函数。

    【讨论】:

    • 是的,但目标是私人的,我可以让它工作,但这不是我想要的。我想用私有来限制范围。
    • 您想限制哪个范围?建设者?班级'?如果您想要一个私有构造函数,您将无法在其自身之外实例化该类。通常,您需要私有构造函数的唯一场景是工厂类。
    • “同一个包”的意思是“在文件开头有'import pkg_name'还是需要同一个目录?
    • “公开”是否有方向限制?
    • @HH:如果您想从另一个包中实例化该类的实例,您需要公开您的构造函数。因此,不要:TestPack() { } 执行:public TestPack() {} 这样,您将能够从 Pack 类实例化 TestPack 的实例。
    【解决方案2】:

    在您使用 getHello 函数的方式中,您可能会开始考虑使用静态方法

    public class TestPack
    {
            private static String hello="if you see me, you ar inside class TestPack";
            public static String getHello(){return hello;}
            private TestPack(){}
    }
    

    那么你就这样做:

    public class Pack
    {
            public static void main(String[] args){
                    System.out.println(TestPack.getHello());
            }
    }
    

    【讨论】:

    • 您需要将 hello 定义为静态才能使其正常工作。
    【解决方案3】:

    我建议您不要将类公开,而是将构造函数公开,并让人们使用您的类实现的公共接口。最好将包的 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 时的陷阱进行了大量讨论、示例和细节。它有很多细节可以解释为什么您应该始终使用许多其他好的“交易技巧”来隐藏尽可能多的类。

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 2013-01-25
      • 2020-03-01
      • 2013-10-27
      • 2015-12-25
      • 2014-11-24
      • 1970-01-01
      • 2017-10-08
      相关资源
      最近更新 更多