Я хотел бы иметь возможность написать класс Java в одном пакете, который может обращаться к закрытым методам класса в другом пакете, не делая его подклассом другого класса. Это возможно?
Я хотел бы иметь возможность написать класс Java в одном пакете, который может обращаться к закрытым методам класса в другом пакете, не делая его подклассом другого класса. Это возможно?
Ответы:
Вот небольшой трюк, который я использую в JAVA для репликации механизма друзей C ++.
Допустим, у меня есть класс Romeo
и другой класс Juliet
. Они в разных пакетах (семейных) по причинам ненависти.
Romeo
хочет cuddle
Juliet
и Juliet
хочет только позволить Romeo
cuddle
ей.
В C ++, Juliet
объявил Romeo
бы (любовник), friend
но в Java нет таких вещей.
Вот классы и хитрость:
Дамы вперед :
package capulet;
import montague.Romeo;
public class Juliet {
public static void cuddle(Romeo.Love love) {
Objects.requireNonNull(love);
System.out.println("O Romeo, Romeo, wherefore art thou Romeo?");
}
}
Так что метод Juliet.cuddle
есть, public
но вам нужно Romeo.Love
вызвать его. Он использует это Romeo.Love
как «безопасность подписи», чтобы гарантировать, что только он Romeo
может вызвать этот метод, и проверит, что любовь реальна, так что среда выполнения выбросит, NullPointerException
если это так null
.
Теперь мальчики:
package montague;
import capulet.Juliet;
public class Romeo {
public static final class Love { private Love() {} }
private static final Love love = new Love();
public static void cuddleJuliet() {
Juliet.cuddle(love);
}
}
Класс Romeo.Love
является общедоступным, но его конструктор private
. Поэтому любой может увидеть это, но только Romeo
может построить это. Я использую статическую ссылку, поэтому то, Romeo.Love
что никогда не используется, создается только один раз и не влияет на оптимизацию.
Поэтому, Romeo
может , cuddle
Juliet
и только он может , потому что только он может построить и доступ к Romeo.Love
экземпляру, которая необходима Juliet
для cuddle
нее (или же она будет хлопнуть вас с NullPointerException
).
Romeo
это Love
для Julia
вечного, изменив love
поле на final
;-).
Разработчики Java явно отвергли идею друга, как это работает в C ++. Вы кладете своих «друзей» в один пакет. Частная, защищенная и пакетная защита обеспечивается как часть языкового дизайна.
Джеймс Гослинг хотел, чтобы Java была C ++ без ошибок. Я считаю, что он чувствовал, что этот друг был ошибкой, потому что он нарушает принципы ООП. Пакеты обеспечивают разумный способ организации компонентов, не слишком заботясь об ООП.
Н.Р. указал, что вы можете обмануть с помощью отражения, но даже это работает, только если вы не используете SecurityManager. Если вы включите стандартную безопасность Java, вы не сможете обмануть с помощью рефлексии, если не напишите политику безопасности, которая специально разрешит это.
friend
нарушил ООП (в частности, больше, чем доступ к пакетам), то он действительно не понимал этого (вполне возможно, многие люди неправильно это понимают).
Концепция «друга» полезна в Java, например, для отделения API от его реализации. Для классов реализации обычно требуется доступ к внутренним компонентам класса API, но они не должны быть доступны клиентам API. Это может быть достигнуто с помощью шаблона «Friend Accessor», как описано ниже:
Класс, предоставляемый через API:
package api;
public final class Exposed {
static {
// Declare classes in the implementation package as 'friends'
Accessor.setInstance(new AccessorImpl());
}
// Only accessible by 'friend' classes.
Exposed() {
}
// Only accessible by 'friend' classes.
void sayHello() {
System.out.println("Hello");
}
static final class AccessorImpl extends Accessor {
protected Exposed createExposed() {
return new Exposed();
}
protected void sayHello(Exposed exposed) {
exposed.sayHello();
}
}
}
Класс, обеспечивающий функциональность «друг»:
package impl;
public abstract class Accessor {
private static Accessor instance;
static Accessor getInstance() {
Accessor a = instance;
if (a != null) {
return a;
}
return createInstance();
}
private static Accessor createInstance() {
try {
Class.forName(Exposed.class.getName(), true,
Exposed.class.getClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e);
}
return instance;
}
public static void setInstance(Accessor accessor) {
if (instance != null) {
throw new IllegalStateException(
"Accessor instance already set");
}
instance = accessor;
}
protected abstract Exposed createExposed();
protected abstract void sayHello(Exposed exposed);
}
Пример доступа из класса в пакете реализации 'friend':
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
Есть два решения для вашего вопроса, которые не включают хранение всех классов в одном пакете.
Во-первых, использовать шаблон Friend Accessor / Friend Package , описанный в (Практическое проектирование API, Tulach 2008).
Второе - использовать OSGi. Существует статья здесь объяснить , как OSGi решает эту задачу.
Насколько я знаю, это невозможно.
Может быть, вы могли бы дать нам более подробную информацию о вашем дизайне. Подобные вопросы, вероятно, являются результатом недостатков дизайна.
Просто подумай
Ответ Эйрикмы прост и превосходен. Я мог бы добавить еще одну вещь: вместо общедоступного метода getFriend (), чтобы получить друга, которого нельзя использовать, вы можете пойти еще дальше и запретить получение друга без токена: getFriend (Service.FriendToken). Этот FriendToken будет внутренним открытым классом с закрытым конструктором, так что только Service может его создать.
Вот ясный пример использования с повторно используемым Friend
классом. Преимущество этого механизма заключается в простоте использования. Может быть, хорошо для того, чтобы дать классам модульного теста больший доступ, чем остальной части приложения.
Для начала, вот пример того, как использовать Friend
класс.
public class Owner {
private final String member = "value";
public String getMember(final Friend friend) {
// Make sure only a friend is accepted.
friend.is(Other.class);
return member;
}
}
Тогда в другом пакете вы можете сделать это:
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
Friend
Класс следующим образом .
public final class Friend {
private final Class as;
public Friend(final Object is) {
as = is.getClass();
}
public void is(final Class c) {
if (c == as)
return;
throw new ClassCastException(String.format("%s is not an expected friend.", as.getName()));
}
public void is(final Class... classes) {
for (final Class c : classes)
if (c == as)
return;
is((Class)null);
}
}
Однако проблема в том, что им можно злоупотреблять так:
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
Теперь, возможно, верно, что у Other
класса нет открытых конструкторов, поэтому приведенный выше Abuser
код невозможен. Однако, если ваш класс делает общедоступный конструктор , то это, вероятно , целесообразно дублировать класс друга как внутренний класс. Возьмите этот Other2
класс в качестве примера:
public class Other2 {
private final Friend friend = new Friend();
public final class Friend {
private Friend() {}
public void check() {}
}
public void test() {
String s = new Owner2().getMember(friend);
System.out.println(s);
}
}
И тогда Owner2
класс будет таким:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Обратите внимание, что у Other2.Friend
класса есть приватный конструктор, что делает этот способ более безопасным.
Предоставленное решение было, возможно, не самым простым. Другой подход основан на той же идее, что и в C ++: закрытые члены не доступны вне пакета / частной области, за исключением определенного класса, который владелец делает своим другом.
Класс, которому нужен доступ друга к члену, должен создать внутренний публичный абстрактный «класс друга», в который класс, владеющий скрытыми свойствами, может экспортировать доступ, возвращая подкласс, который реализует методы реализации доступа. Метод «API» класса-друга может быть закрытым, поэтому он недоступен за пределами класса, которому требуется доступ-друг. Его единственное утверждение - это вызов абстрактного защищенного члена, который реализует экспортирующий класс.
Вот код:
Сначала тест, который проверяет, что это действительно работает:
package application;
import application.entity.Entity;
import application.service.Service;
import junit.framework.TestCase;
public class EntityFriendTest extends TestCase {
public void testFriendsAreOkay() {
Entity entity = new Entity();
Service service = new Service();
assertNull("entity should not be processed yet", entity.getPublicData());
service.processEntity(entity);
assertNotNull("entity should be processed now", entity.getPublicData());
}
}
Затем Сервис, которому нужен доступ друга к пакетному приватному члену Entity:
package application.service;
import application.entity.Entity;
public class Service {
public void processEntity(Entity entity) {
String value = entity.getFriend().getEntityPackagePrivateData();
entity.setPublicData(value);
}
/**
* Class that Entity explicitly can expose private aspects to subclasses of.
* Public, so the class itself is visible in Entity's package.
*/
public static abstract class EntityFriend {
/**
* Access method: private not visible (a.k.a 'friendly') outside enclosing class.
*/
private String getEntityPackagePrivateData() {
return getEntityPackagePrivateDataImpl();
}
/** contribute access to private member by implementing this */
protected abstract String getEntityPackagePrivateDataImpl();
}
}
Наконец, класс Entity, обеспечивающий дружественный доступ к закрытому члену пакета только для класса application.service.Service.
package application.entity;
import application.service.Service;
public class Entity {
private String publicData;
private String packagePrivateData = "secret";
public String getPublicData() {
return publicData;
}
public void setPublicData(String publicData) {
this.publicData = publicData;
}
String getPackagePrivateData() {
return packagePrivateData;
}
/** provide access to proteced method for Service'e helper class */
public Service.EntityFriend getFriend() {
return new Service.EntityFriend() {
protected String getEntityPackagePrivateDataImpl() {
return getPackagePrivateData();
}
};
}
}
Хорошо, я должен признать, что это немного дольше, чем "friend service :: Service;" но может быть возможно сократить его, сохранив проверку во время компиляции с использованием аннотаций.
В Java возможно иметь «дружеское отношение к пакету». Это может быть полезно для модульного тестирования. Если вы не укажете private / public / protected перед методом, он будет "другом в пакете". Класс в том же пакете сможет получить к нему доступ, но он будет закрытым вне класса.
Это правило не всегда известно, и оно является хорошим приближением к ключевому слову «Друг» в C ++. Я считаю это хорошей заменой.
Я думаю, что классы друзей в C ++ похожи на концепцию внутреннего класса в Java. Используя внутренние классы, вы можете определить класс, включающий в себя класс. Закрытый класс имеет полный доступ к открытым и закрытым членам включающего его класса. см. следующую ссылку: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Я думаю, что подход к использованию паттерна доступа друга слишком сложен. Мне пришлось столкнуться с той же проблемой, и я решил использовать старый добрый конструктор копирования, известный из C ++, в Java:
public class ProtectedContainer {
protected String iwantAccess;
protected ProtectedContainer() {
super();
iwantAccess = "Default string";
}
protected ProtectedContainer(ProtectedContainer other) {
super();
this.iwantAccess = other.iwantAccess;
}
public int calcSquare(int x) {
iwantAccess = "calculated square";
return x * x;
}
}
В вашем приложении вы можете написать следующий код:
public class MyApp {
private static class ProtectedAccessor extends ProtectedContainer {
protected ProtectedAccessor() {
super();
}
protected PrivateAccessor(ProtectedContainer prot) {
super(prot);
}
public String exposeProtected() {
return iwantAccess;
}
}
}
Преимущество этого метода в том, что только ваше приложение имеет доступ к защищенным данным. Это не совсем замена ключевого слова друга. Но я думаю, что это очень удобно, когда вы пишете пользовательские библиотеки и вам нужен доступ к защищенным данным.
Всякий раз, когда вам приходится иметь дело с экземплярами ProtectedContainer, вы можете обернуть ваш ProtectedAccessor вокруг него, и вы получите доступ.
Он также работает с защищенными методами. Вы определяете их защищенными в вашем API. Позже в вашем приложении вы пишете закрытый класс-обертку и выставляете защищенный метод как открытый. Вот и все.
ProtectedContainer
может быть подкласс за пределами пакета!
Если вы хотите получить доступ к защищенным методам, вы можете создать подкласс класса, который вы хотите использовать, который предоставляет методы, которые вы хотите использовать как общедоступные (или внутренние для пространства имен, чтобы быть более безопасными), и иметь экземпляр этого класса в своем классе. (используйте его как прокси).
Что касается частных методов (я думаю), вам не повезло.
Я согласен с тем, что в большинстве случаев ключевое слово Friend не требуется.
И, наконец, если это действительно необходимо, в других ответах упоминается схема доступа к друзьям.
Не используя ключевое слово или около того.
Вы можете «обмануть», используя рефлексию и т. Д., Но я бы не рекомендовал «обманывать».
Метод, который я нашел для решения этой проблемы, заключается в создании объекта доступа, например, так:
class Foo {
private String locked;
/* Anyone can get locked. */
public String getLocked() { return locked; }
/* This is the accessor. Anyone with a reference to this has special access. */
public class FooAccessor {
private FooAccessor (){};
public void setLocked(String locked) { Foo.this.locked = locked; }
}
private FooAccessor accessor;
/** You get an accessor by calling this method. This method can only
* be called once, so calling is like claiming ownership of the accessor. */
public FooAccessor getAccessor() {
if (accessor != null)
throw new IllegalStateException("Cannot return accessor more than once!");
return accessor = new FooAccessor();
}
}
Первый код, который называется getAccessor()
«утверждает право собственности» на метод доступа. Обычно это код, который создает объект.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
Это также имеет преимущество перед механизмом друга C ++, поскольку позволяет ограничивать доступ на уровне экземпляра , а не на уровне класса . Управляя ссылкой доступа, вы контролируете доступ к объекту. Вы также можете создавать несколько средств доступа и предоставлять каждому доступ по-разному, что позволяет детально контролировать, какой код может получить доступ к чему:
class Foo {
private String secret;
private String locked;
/* Anyone can get locked. */
public String getLocked() { return locked; }
/* Normal accessor. Can write to locked, but not read secret. */
public class FooAccessor {
private FooAccessor (){};
public void setLocked(String locked) { Foo.this.locked = locked; }
}
private FooAccessor accessor;
public FooAccessor getAccessor() {
if (accessor != null)
throw new IllegalStateException("Cannot return accessor more than once!");
return accessor = new FooAccessor();
}
/* Super accessor. Allows access to secret. */
public class FooSuperAccessor {
private FooSuperAccessor (){};
public String getSecret() { return Foo.this.secret; }
}
private FooSuperAccessor superAccessor;
public FooSuperAccessor getAccessor() {
if (superAccessor != null)
throw new IllegalStateException("Cannot return accessor more than once!");
return superAccessor = new FooSuperAccessor();
}
}
Наконец, если вы хотите, чтобы вещи были немного более организованными, вы можете создать эталонный объект, который содержит все вместе. Это позволяет запрашивать все средства доступа одним вызовом метода, а также хранить их вместе со связанным экземпляром. Получив ссылку, вы можете передать методы доступа к нужному коду:
class Foo {
private String secret;
private String locked;
public String getLocked() { return locked; }
public class FooAccessor {
private FooAccessor (){};
public void setLocked(String locked) { Foo.this.locked = locked; }
}
public class FooSuperAccessor {
private FooSuperAccessor (){};
public String getSecret() { return Foo.this.secret; }
}
public class FooReference {
public final Foo foo;
public final FooAccessor accessor;
public final FooSuperAccessor superAccessor;
private FooReference() {
this.foo = Foo.this;
this.accessor = new FooAccessor();
this.superAccessor = new FooSuperAccessor();
}
}
private FooReference reference;
/* Beware, anyone with this object has *all* the accessors! */
public FooReference getReference() {
if (reference != null)
throw new IllegalStateException("Cannot return reference more than once!");
return reference = new FooReference();
}
}
После долгих ударов головой (не очень), это было мое окончательное решение, и мне оно очень нравится. Он гибкий, простой в использовании и позволяет очень хорошо контролировать доступ к классам. ( Доступ только со ссылкой очень полезен.) Если вы используете для доступа / ссылок защищенный вместо частного, подклассы Foo могут даже возвращать расширенные ссылки из getReference
. Он также не требует отражения, поэтому его можно использовать в любой среде.
Я предпочитаю делегирование или композицию или фабричный класс (в зависимости от проблемы, которая приводит к этой проблеме), чтобы не делать его публичным классом.
Если это проблема «интерфейсов / классов реализации в разных пакетах», то я бы использовал общедоступный класс фабрики, который был бы в том же пакете, что и пакет impl, и предотвратил бы раскрытие класса impl.
Если возникает проблема «я не хочу делать этот класс / метод общедоступным только для того, чтобы обеспечить эту функциональность для какого-то другого класса в другом пакете», то я бы использовал открытый класс делегата в том же пакете и выставил бы только эту часть функциональности. нужен "чужому" классу.
Некоторые из этих решений основаны на архитектуре загрузки классов целевого сервера (комплект OSGi, WAR / EAR и т. Д.), Соглашениях о развертывании и именовании пакетов. Например, предложенное выше решение, паттерн «Friend Accessor» является умным для обычных Java-приложений. Интересно, сложно ли реализовать его в OSGi из-за различий в стиле загрузки классов?
Я не знаю, полезно ли это кому-либо, но я справился с этим следующим образом:
Я создал интерфейс (AdminRights).
Каждый класс, который должен иметь возможность вызывать указанные функции, должен реализовывать AdminRights.
Затем я создал функцию HasAdminRights следующим образом:
private static final boolean HasAdminRights()
{
// Gets the current hierarchy of callers
StackTraceElement[] Callers = new Throwable().getStackTrace();
// Should never occur with me but if there are less then three StackTraceElements we can't check
if (Callers.length < 3)
{
EE.InvalidCode("Couldn't check for administrator rights");
return false;
} else try
{
// Now we check the third element as this function is the first and the function wanting to check for the rights the second. We try to use it as a subclass of AdminRights.
Class.forName(Callers[2].getClassName()).asSubclass(AdminRights.class);
// If everything worked up to now, it has admin rights!
return true;
} catch (java.lang.ClassCastException | ClassNotFoundException e)
{
// In the catch, something went wrong and we can deduce that the caller has no admin rights
EE.InvalidCode(Callers[1].getClassName() + " doesn't have administrator rights");
return false;
}
}
Однажды я увидел решение, основанное на отражении, которое выполняло «проверку друга» во время выполнения, используя отражение и проверку стека вызовов, чтобы узнать, разрешено ли это делать классу, вызывающему метод. Будучи проверкой во время выполнения, она имеет очевидный недостаток.