Je voudrais pouvoir écrire une classe Java dans un package qui peut accéder aux méthodes non publiques d'une classe dans un autre package sans avoir à en faire une sous-classe de l'autre classe. Est-ce possible?
Je voudrais pouvoir écrire une classe Java dans un package qui peut accéder aux méthodes non publiques d'une classe dans un autre package sans avoir à en faire une sous-classe de l'autre classe. Est-ce possible?
Réponses:
Voici une petite astuce que j'utilise dans JAVA pour répliquer le mécanisme ami C ++.
Disons que j'ai une classe Romeo
et une autre classe Juliet
. Ils sont dans des packages différents (famille) pour des raisons de haine.
Romeo
veut cuddle
Juliet
et Juliet
veut seulement la laisser Romeo
cuddle
.
En C ++, Juliet
se déclarer Romeo
comme (amoureux) friend
mais il n'y a rien de tel en java.
Voici les cours et l'astuce:
Les dames d'abord :
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?");
}
}
Donc, la méthode Juliet.cuddle
est public
mais vous avez besoin d'un Romeo.Love
pour l'appeler. Il l'utilise Romeo.Love
comme une "sécurité de signature" pour s'assurer que seul Romeo
peut appeler cette méthode et vérifie que l'amour est réel afin que le runtime lance un NullPointerException
si c'est le cas null
.
Maintenant les garçons:
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);
}
}
La classe Romeo.Love
est publique, mais son constructeur l'est private
. Par conséquent, n'importe qui peut le voir, mais seul Romeo
peut le construire. J'utilise une référence statique afin Romeo.Love
que celle qui n'est jamais utilisée ne soit construite qu'une seule fois et n'impacte pas l'optimisation.
Par conséquent, ne Romeo
peut cuddle
Juliet
et ne peut - il parce que lui seul peut construire et accéder à une Romeo.Love
instance, qui est requis par Juliet
la cuddle
lui (ou bien elle va te frapper avec NullPointerException
).
Romeo
« s Love
pour Julia
éternelle en changeant le love
champ pour être final
;-).
Les concepteurs de Java ont explicitement rejeté l'idée de l'ami car elle fonctionne en C ++. Vous mettez vos "amis" dans le même paquet. La sécurité privée, protégée et intégrée est appliquée dans le cadre de la conception du langage.
James Gosling voulait que Java soit C ++ sans les erreurs. Je pense qu'il pensait que cet ami était une erreur parce qu'elle violait les principes de la POO. Les packages fournissent un moyen raisonnable d'organiser les composants sans être trop puristes sur la POO.
NR a souligné que vous pouviez tricher en utilisant la réflexion, mais même cela ne fonctionne que si vous n'utilisez pas SecurityManager. Si vous activez la sécurité standard Java, vous ne pourrez pas tricher avec réflexion à moins que vous n'écriviez une stratégie de sécurité pour l'autoriser spécifiquement.
friend
violait la POO (en particulier, plus que l'accès aux packages), il ne le comprenait vraiment pas (tout à fait possible, beaucoup de gens le comprennent mal).
Le concept «ami» est utile en Java, par exemple, pour séparer une API de son implémentation. Il est courant que les classes d'implémentation aient besoin d'accéder aux composants internes de classe API, mais ceux-ci ne doivent pas être exposés aux clients API. Ceci peut être réalisé en utilisant le modèle 'Friend Accessor' comme détaillé ci-dessous:
La classe exposée via l'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();
}
}
}
La classe offrant la fonctionnalité «ami»:
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);
}
Exemple d'accès à partir d'une classe dans le package d'implémentation «ami»:
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
Il existe deux solutions à votre question qui n'impliquent pas de conserver toutes les classes dans le même package.
La première consiste à utiliser le modèle Friend Accessor / Friend Package décrit dans (Practical API Design, Tulach 2008).
La seconde consiste à utiliser OSGi. Il y a un article ici expliquant comment OSGi y parvient.
Pour autant que je sache, ce n'est pas possible.
Peut-être pourriez-vous nous donner plus de détails sur votre conception. De telles questions sont probablement le résultat de défauts de conception.
Considérez juste
La réponse d'eirikma est simple et excellente. Je pourrais ajouter encore une chose: au lieu d'avoir une méthode accessible au public, getFriend () pour obtenir un ami qui ne peut pas être utilisé, vous pouvez aller plus loin et interdire d'obtenir l'ami sans jeton: getFriend (Service.FriendToken). Ce FriendToken serait une classe publique interne avec un constructeur privé, de sorte que seul le service pourrait en instancier une.
Voici un exemple de cas d'utilisation clair avec une Friend
classe réutilisable . L'avantage de ce mécanisme est la simplicité d'utilisation. Peut-être bon pour donner aux classes de tests unitaires plus d'accès que le reste de l'application.
Pour commencer, voici un exemple d'utilisation de la Friend
classe.
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;
}
}
Ensuite, dans un autre package, vous pouvez le faire:
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
La Friend
classe est la suivante.
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);
}
}
Cependant, le problème est qu'il peut être abusé comme suit:
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
Maintenant, il peut être vrai que la Other
classe n'a pas de constructeurs publics, ce qui rend le Abuser
code ci-dessus impossible. Toutefois, si votre classe ne un constructeur public alors il est probablement préférable de dupliquer la classe ami comme une classe interne. Prenez ce Other2
cours comme exemple:
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);
}
}
Et puis la Owner2
classe serait comme ça:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Notez que la Other2.Friend
classe a un constructeur privé, ce qui en fait un moyen beaucoup plus sûr de le faire.
La solution proposée n'était peut-être pas la plus simple. Une autre approche est basée sur la même idée qu'en C ++: les membres privés ne sont pas accessibles en dehors de la portée package / private, à l'exception d'une classe spécifique que le propriétaire se fait un ami.
La classe qui a besoin d'un accès ami à un membre doit créer une "classe ami" abstraite publique intérieure à laquelle la classe possédant les propriétés masquées peut exporter l'accès, en renvoyant une sous-classe qui implémente les méthodes d'implémentation d'accès. La méthode "API" de la classe d'amis peut être privée, elle n'est donc pas accessible en dehors de la classe qui a besoin d'un accès ami. Sa seule instruction est un appel à un membre protégé abstrait que la classe exportatrice implémente.
Voici le code:
D'abord le test qui vérifie que cela fonctionne réellement:
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());
}
}
Ensuite, le service qui a besoin d'un accès ami à un package membre privé de l'entité:
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();
}
}
Enfin: la classe Entity qui fournit un accès convivial à un membre privé du package uniquement à la classe 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();
}
};
}
}
D'accord, je dois admettre que c'est un peu plus long que "friend service :: Service;" mais il pourrait être possible de le raccourcir tout en conservant la vérification à la compilation en utilisant des annotations.
En Java, il est possible d'avoir une "convivialité liée au package". Cela peut être utile pour les tests unitaires. Si vous ne spécifiez pas privé / public / protégé devant une méthode, ce sera "ami dans le package". Une classe dans le même package pourra y accéder, mais elle sera privée en dehors de la classe.
Cette règle n'est pas toujours connue, et c'est une bonne approximation d'un mot-clé "ami" C ++. Je le trouve un bon remplacement.
Je pense que les classes d'amis en C ++ sont comme le concept de classe interne en Java. En utilisant des classes internes, vous pouvez réellement définir une classe englobante et une classe fermée. La classe fermée a un accès complet aux membres publics et privés de sa classe enfermante. voir le lien suivant: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Je pense que l'approche de l'utilisation du modèle d'accesseur ami est beaucoup trop compliquée. J'ai dû faire face au même problème et j'ai résolu en utilisant le bon vieux constructeur de copie, connu de C ++, en 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;
}
}
Dans votre application, vous pouvez écrire le code suivant:
public class MyApp {
private static class ProtectedAccessor extends ProtectedContainer {
protected ProtectedAccessor() {
super();
}
protected PrivateAccessor(ProtectedContainer prot) {
super(prot);
}
public String exposeProtected() {
return iwantAccess;
}
}
}
L'avantage de cette méthode est que seule votre application a accès aux données protégées. Ce n'est pas exactement une substitution du mot-clé ami. Mais je pense que cela convient parfaitement lorsque vous écrivez des bibliothèques personnalisées et que vous devez accéder à des données protégées.
Chaque fois que vous devez gérer des instances de ProtectedContainer, vous pouvez envelopper votre ProtectedAccessor et vous y avez accès.
Il fonctionne également avec des méthodes protégées. Vous les définissez protégés dans votre API. Plus loin dans votre application, vous écrivez une classe wrapper privée et exposez la méthode protégée comme publique. C'est tout.
ProtectedContainer
peut être sous-classé en dehors du package!
Si vous souhaitez accéder aux méthodes protégées, vous pouvez créer une sous-classe de la classe que vous souhaitez utiliser qui expose les méthodes que vous souhaitez utiliser en tant que public (ou interne à l'espace de noms pour être plus sûr), et avoir une instance de cette classe dans votre classe (utilisez-le comme proxy).
En ce qui concerne les méthodes privées (je pense), vous n'avez pas de chance.
Je suis d'accord que dans la plupart des cas, le mot-clé ami n'est pas nécessaire.
Et enfin, si c'est vraiment nécessaire, il y a le modèle d'accesseur ami mentionné dans les autres réponses.
Ne pas utiliser de mot-clé.
Vous pouvez "tricher" en utilisant la réflexion, etc., mais je ne recommanderais pas de "tricher".
Une méthode que j'ai trouvée pour résoudre ce problème est de créer un objet accesseur, comme ceci:
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();
}
}
Le premier code à appeler getAccessor()
"revendique la propriété" de l'accesseur. Habituellement, c'est du code qui crée l'objet.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
Cela a également un avantage sur le mécanisme ami de C ++, car il vous permet de limiter l'accès à un niveau par instance , par opposition à un niveau par classe . En contrôlant la référence de l'accesseur, vous contrôlez l'accès à l'objet. Vous pouvez également créer plusieurs accesseurs et donner un accès différent à chacun, ce qui permet un contrôle précis sur quel code peut accéder à quoi:
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();
}
}
Enfin, si vous souhaitez que les choses soient un peu plus organisées, vous pouvez créer un objet de référence, qui tient tout ensemble. Cela vous permet de revendiquer tous les accesseurs avec un seul appel de méthode, ainsi que de les conserver avec leur instance liée. Une fois que vous avez la référence, vous pouvez passer les accesseurs au code qui en a besoin:
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();
}
}
Après beaucoup de coups de tête (pas du bon genre), ce fut ma solution finale, et je l'aime beaucoup. Il est flexible, simple à utiliser et permet un très bon contrôle sur l'accès aux classes. (L' accès avec référence uniquement est très utile.) Si vous utilisez protégé au lieu de privé pour les accesseurs / références, les sous-classes de Foo peuvent même renvoyer des références étendues à partir de getReference
. Il ne nécessite également aucune réflexion, il peut donc être utilisé dans n'importe quel environnement.
Je préfère la délégation ou la composition ou la classe d'usine (en fonction du problème qui entraîne ce problème) pour éviter d'en faire une classe publique.
S'il s'agit d'un problème de "classes d'interface / d'implémentation dans différents packages", j'utiliserais une classe de fabrique publique qui serait dans le même package que le package impl et empêcherait l'exposition de la classe impl.
Si c'est un problème "Je déteste rendre cette classe / méthode publique juste pour fournir cette fonctionnalité à une autre classe dans un autre package", alors j'utiliserais une classe déléguée publique dans le même package et n'exposerais que cette partie de la fonctionnalité nécessaire par la classe "outsider".
Certaines de ces décisions sont dictées par l'architecture de chargement de classe du serveur cible (bundle OSGi, WAR / EAR, etc.), le déploiement et les conventions de dénomination des packages. Par exemple, la solution proposée ci-dessus, le modèle «Friend Accessor» est intelligente pour les applications java normales. Je me demande s'il est difficile de l'implémenter dans OSGi en raison de la différence de style de chargement de classe.
Je ne sais pas si cela est utile à personne, mais je l'ai traité de la manière suivante:
J'ai créé une interface (AdminRights).
Chaque classe qui devrait pouvoir appeler lesdites fonctions devrait implémenter AdminRights.
J'ai ensuite créé une fonction HasAdminRights comme suit:
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;
}
}
J'ai vu une fois une solution basée sur la réflexion qui effectuait une "vérification d'amis" au moment de l'exécution en utilisant la réflexion et la vérification de la pile d'appels pour voir si la classe appelant la méthode était autorisée à le faire. Étant un contrôle d'exécution, il présente l'inconvénient évident.