Проблема новичка в Java: пакет с закрытым доступом - PullRequest
4 голосов
/ 03 мая 2010

Pack.java импортирует pack.TestPack; но он не может получить к нему доступ. Я не могу понять, почему он не может получить доступ к классу, несмотря на импорт.

Error

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(){}
}

Ответы [ 3 ]

4 голосов
/ 03 мая 2010

Вы должны сделать конструктор 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.

0 голосов
/ 07 июля 2010

Я полагаю, что вы не делаете класс общедоступным, а делаете конструктор общедоступным, и у людей есть открытый интерфейс, который реализует ваш класс. Хорошей идеей будет запустить 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»).

Вариант темы - не использовать статический метод на фабричном классе; сделать это обычным методом. Альтернативы называются «статическими фабриками» или «фабриками экземпляров» в зависимости от того, является ли метод статическим или нет. Не делать метод статичным кажется более трудоемким для людей, использующих ваш пакет, поскольку им сначала нужно создать экземпляр вашего фабричного объекта, прежде чем использовать его для создания Widget. Полезно, когда люди могут захотеть установить некоторые значения по умолчанию для всех виджетов в конструкторе фабрики, а затем использовать не статические методы 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, должен настроить его внешне, чтобы запретить такие приемы, что полезно только в том случае, если вы хотите запустить чужой код, которому вы не доверяете, и помешать ему использовать такие приемы.

В книге «Эффективная Java» Джоша Блока, 2-е издание, много обсуждений, примеров и деталей подводных камней при попытке написать хороший API. В нем много подробностей, объясняющих, почему вы всегда должны скрывать как можно больше классов с помощью множества других хороших «хитростей торговли».

0 голосов
/ 03 мая 2010

При использовании функции 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());
        }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...