Публично объявить закрытый тип пакета в сигнатуре метода
Это возможно в 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();
ХОРОШО. Теперь я могу как-то составить пример, где это можно объяснить с помощью ковариации результата метода. Но что еще хуже, я также могу сделать это:
package x;
public class X {
public Y getY(Y result) {
return result;
}
}
class Y {}
Теперь я никогда не мог позвонить getY(Y result)
снаружи x
пакет. Почему я могу это сделать? Почему компилятор позволяет мне объявлять метод так, что я не могу его вызвать?
5 ответов
Мы много думали о разработке Java, но иногда некоторые неоптимальные проекты просто проскальзывали. Знаменитые Java Puzzlers ясно демонстрируют это.
Другой пакет все еще может вызывать метод с параметром package-private. Самый простой способ - это пройти null
, Но не потому, что вы все еще можете это назвать, такая конструкция действительно имеет смысл. Это нарушает основную идею, стоящую за package-private: это должен видеть только сам пакет. Большинство людей согласятся с тем, что любой код, использующий эту конструкцию, по меньшей мере сбивает с толку и просто имеет неприятный запах. Было бы лучше не допустить этого.
Как примечание, тот факт, что это разрешено, открывает еще несколько угловых случаев. Например, из другого пакета делать Arrays.asList(new X().getY())
компилируется, но выдает IllegalAccessError при выполнении, потому что пытается создать массив недоступного класса Y. Это просто показывает, что эта утечка недоступных типов не вписывается в допущения, которые делает остальная часть языкового дизайна.
Но, как и другие необычные правила в Java, это было разрешено в первых версиях Java. Потому что это не такая уж большая проблема, и поскольку обратная совместимость более важна для Java, улучшение этой ситуации (ее запрет) просто больше не стоит.
Иногда полезно иметь открытые методы, которые возвращают экземпляр типа, который не является 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
экземпляры напрямую).
Прежде всего, вы можете вызвать метод. Тривиальный пример вызывает его в том же пакете.
Нетривиальный пример:
package x;
public class Z extends Y {}
package x2;
x.getY( new Z() ); // compiles
Но это не главное.
Дело в том, что Java пытается запретить некоторые из явно бессмысленных проектов, но не может запретить все.
что бессмысленно? это очень субъективно.
если это слишком строго, это плохо для развития, когда вещи все еще пластичны.
языковая спецификация уже слишком сложна; добавление большего количества правил выше человеческих возможностей. [1]
[1] http://java.sun.com/docs/books/jls/third_edition/html/names.html
Основная причина, по которой это разрешено - это использование непрозрачных типов. Представьте себе следующий сценарий:
package x;
public interface Foo;
class X
{
public Foo getFoo ( )
{
return new Y( );
}
class Y implements Foo { }
}
Здесь мы имеем вашу ситуацию (внутренний класс с защитой пакетов, экспортируемый через открытый API), но это имеет смысл, поскольку для вызывающей стороны возвращаемый Y является непрозрачным типом. Тем не менее, IIRC NetBeans предупреждает об этом типе поведения.
Не могли бы вы сделать что-то вроде:
Object answer = foo1.getY();
foo2.setY( foo1.getY().getClass().cast(answer) );
Да, это безобразно, глупо и бессмысленно, но вы все равно можете это сделать.
Тем не менее, я считаю, что ваш оригинальный код будет выдавать предупреждение компилятора.