Публично объявить закрытый тип пакета в сигнатуре метода - PullRequest
9 голосов
/ 10 марта 2011

Это возможно в Java:

package x;

public class X {

    // How can this method be public??
    public Y getY() {
        return new Y();
    }
}

class Y {}

Так почему веский компилятор Java позволяет мне объявлять метод getY() как public?Меня беспокоит то, что класс Y является частным пакетом, но метод доступа getY() объявляет его в сигнатуре метода.Но вне пакета x я могу присвоить результаты метода только Object:

// OK
Object o = new X().getY();

// Not OK:
Y y = new X().getY();

OK.Теперь я могу как-то составить пример, где это можно объяснить с помощью ковариация результата метода .Но, что еще хуже, я также могу сделать это:

package x;

public class X {
    public Y getY(Y result) {
        return result;
    }
}

class Y {}

Теперь я никогда не смогу позвонить getY(Y result) извне пакета x.Почему я могу это сделать? Почему компилятор позволяет мне объявлять метод так, что я не могу его вызвать?

Ответы [ 5 ]

6 голосов
/ 11 марта 2011

Много размышлений ушло на разработку Java, но иногда некоторые неоптимальные конструкции просто проскальзывают. Знаменитые Java Puzzlers ясно демонстрируют это.

Другой пакет все еще может вызывать метод с параметром package-private. Самый простой способ - передать его null. Но не потому, что вы все еще можете это назвать, такая конструкция действительно имеет смысл. Это нарушает основную идею, стоящую за package-private : это должен видеть только сам пакет. Большинство людей согласятся, что любой код, использующий эту конструкцию, по меньшей мере сбивает с толку и просто имеет неприятный запах. Было бы лучше не допустить этого.

Как примечание, тот факт, что это разрешено, открывает еще несколько угловых случаев. Например, из другого пакета Arrays.asList(new X().getY()) компилирует, но выдает ошибку IllegalAccessError при выполнении, поскольку пытается создать массив недоступного класса Y. Это просто показывает, что эта утечка недоступных типов не вписывается в допущения, которые делает остальная часть языкового дизайна .

Но, как и другие необычные правила в Java, это было разрешено в первых версиях Java. Потому что это не такая уж большая проблема, и поскольку обратная совместимость более важна для Java, улучшение этой ситуации (запрет ее) просто больше не стоит.

2 голосов
/ 11 марта 2011

Иногда полезно иметь открытые методы, которые возвращают экземпляр типа, который не является public, например, если этот тип реализует интерфейс.Часто фабрики работают так:

public interface MyInterface { }

class HiddenImpl implements MyInterface { }

public class Factory {
    public HiddenImpl createInstance() {
        return new HiddenImpl();
     }
}

Конечно, можно утверждать, что компилятор может заставить возвращаемое значение createInstance() быть MyInterface в этом случае.Тем не менее, есть как минимум два преимущества, позволяющие ему быть HiddenImpl.Одна из них заключается в том, что HiddenImpl может реализовывать несколько отдельных интерфейсов, и вызывающая сторона может свободно выбирать тип возвращаемого значения.Другое заключается в том, что вызывающие изнутри пакета могут использовать один и тот же метод для получения экземпляра HiddenImpl и использовать его как таковой, без необходимости его приведения или использования двух методов на фабрике (один общедоступный MyInterface createInstance() и один защищенный пакетом).HiddenImpl createPrivateInstance()), которые делают то же самое.

Причина, по которой допускается что-то вроде public void setY(Y param), аналогична.Могут быть открытые подтипы Y, и вызывающие стороны извне пакета могут передавать экземпляры этих типов.Опять же, здесь применяются те же два преимущества, что и выше (может быть несколько таких подтипов, и вызывающие абоненты из одного и того же пакета могут выбрать непосредственную передачу Y экземпляров).

2 голосов
/ 11 марта 2011

Прежде всего, вы можете вызвать метод.Тривиальный пример - это вызов в том же пакете.

Нетривиальный пример:

package x;
public class Z extends Y {}

package x2;
    x.getY( new Z() ); // compiles

Но дело не в этом.

Дело в том, что Java пытается запретить некоторые из явно бессмысленных проектов, но не может запретить все.

  1. что бессмысленно?это очень субъективно.

  2. если он слишком строгий, это плохо для развития, когда все еще пластично.

  3. спецификация языка уже слишком сложна;добавление большего количества правил выше человеческих возможностей.[1]

[1] http://java.sun.com/docs/books/jls/third_edition/html/names.html#6.6

1 голос
/ 11 марта 2011

Не могли бы вы сделать что-то вроде:

Object answer = foo1.getY();  
foo2.setY(  foo1.getY().getClass().cast(answer)  );

Да, это безобразно, глупо и бессмысленно, но вы все равно можете это сделать.предупреждение компилятора.

1 голос
/ 10 марта 2011

Большая причина разрешить это - разрешить непрозрачные типы . Представьте себе следующий сценарий:

package x;

public interface Foo;

class X
{
    public Foo getFoo ( )
    {
        return new Y( );
    }

    class Y implements Foo { }
}

Здесь мы имеем вашу ситуацию (внутренний класс с защитой пакета, экспортируемый через открытый API), но это имеет смысл, поскольку для вызывающей стороны возвращаемый Y является непрозрачным типом. Тем не менее, IIRC NetBeans предупреждает об этом типе поведения.

...