代码之家  ›  专栏  ›  技术社区  ›  hhh

Java新手问题:带有私有访问的包

  •  4
  • hhh  · 技术社区  · 16 年前

    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
    

    包.java

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

    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(){}
    }
    
    3 回复  |  直到 16 年前
        1
  •  4
  •   Pablo Santa Cruz    16 年前

    您应该公开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

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

        2
  •  0
  •   msemelman    16 年前

    在使用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());
            }
    }
    
        3
  •  0
  •   simbo1905    16 年前

    我建议不要公开类,而是公开构造函数,让人们使用类实现的公共接口。一个好主意是将包的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)[
          // ... 
        }
    }
    

    注意这个词的第二次出现/ 包裹 /是一个注释(在java中使用这个词是不合法的),但是许多程序员在这个位置使用这样的注释来向人们表明类不是公共的不是偶然的;这意味着开发人员确实有意将类设置为“包私有”。为了让人们从包的外部实例化类,您提供了一个静态工厂类(或者一个实例工厂类):

    package stackoverflow;
    public class WidgetFactory {
        public static Widget newInstance( String configOptionA, String configOptionB) {
            return new Widget( String configOptionA, String configOptionB);
        } 
    }
    

    许多框架通过将其他开发人员不应使用的类放入名为“internal”的包中来指示它们。公共接口将位于主包(例如“com.stackoverflow.widget”)中,而隐藏类则位于只公开公共工厂类的内部包(例如“com.stackoverflow.widget.internal”)中。

    主题的一个变化是不在工厂类上使用静态方法;使之成为常规方法。根据方法是否是静态的,替代方法被称为“静态工厂”或“实例工厂”。对于使用您的包的人来说,不使方法成为静态的似乎需要更多的工作,因为他们首先必须实例化您的工厂对象,然后才能使用它来创建小部件。当人们可能希望为工厂构造函数上的所有小部件设置一些默认值,然后使用none static 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并将其放入一个“internal”包中,使其成为您告诉人们“不要使用”的情况。JVM有可选的安全设置,可以防止人们使用这些技巧;但是运行JVM的人必须在外部将其配置为不允许使用这种技巧,这种技巧只有在您希望运行不信任的其他代码并阻止它使用这种技巧时才有用。

    joshablock第2版的《有效的Java》一书中有很多讨论和例子,详细介绍了编写一个好的API时的陷阱。它有很多细节来解释为什么你应该总是寻找隐藏尽可能多的类与许多其他好的“交易技巧”。