C’è un modo per simulare il concetto di “amico” C ++ in Java?

Mi piacerebbe essere in grado di scrivere una class Java in un pacchetto che possa accedere a metodi non pubblici di una class in un altro pacchetto senza dover renderlo una sottoclass dell’altra class. È ansible?

Ecco un piccolo trucco che uso in JAVA per replicare il meccanismo degli amici C ++.

Diciamo che ho una class Romeo e un’altra class di Juliet . Sono in pacchetti diversi (famiglia) per ragioni di odio.

Romeo vuole cuddle Juliet e Juliet vuole solo lasciare che Romeo cuddle .

In C ++, Juliet dichiarerebbe Romeo come friend (amante) ma non ci sono cose simili in Java.

Ecco le classi e il trucco:

Prima le signore :

 package capulet; import montague.Romeo; public class Juliet { public static void cuddle(Romeo.Love l) { l.hashCode(); System.out.println("O Romeo, Romeo, wherefore art thou Romeo?"); } } 

Quindi il metodo Juliet.cuddle è public ma hai bisogno di un Romeo.Love per chiamarlo. Utilizza questo Romeo.Love come una “firma di sicurezza” per garantire che solo Romeo possa chiamare questo metodo e chiama semplicemente hashCode su di esso in modo che il runtime genererà una NullPointerException se è null .

Adesso ragazzi:

 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 class Romeo.Love è pubblica, ma il suo costruttore è private . Quindi chiunque può vederlo, ma solo Romeo può costruirlo. Io uso un riferimento statico in modo che il Romeo.Love che non viene mai usato viene costruito una volta sola e non influisce sull’ottimizzazione.

Pertanto, Romeo può cuddle Juliet e solo lui può perché solo lui può build e accedere a un’istanza Romeo.Love , che è richiesta da Juliet per cuddle (altrimenti ti schiafferà con una NullPointerException ).

I progettisti di Java hanno respinto esplicitamente l’idea di amico come funziona in C ++. Hai messo i tuoi “amici” nello stesso pacchetto. La sicurezza privata, protetta e pacchettizzata viene applicata come parte del design della lingua.

James Gosling voleva che Java fosse C ++ senza errori. Credo che abbia sentito che quell’amico era un errore perché viola i principi dell’OOP. I pacchetti forniscono un modo ragionevole di organizzare i componenti senza essere troppo puristi su OOP.

NR ha sottolineato che si può imbrogliare usando il reflection, ma anche quello funziona solo se non si utilizza SecurityManager. Se si triggers la sicurezza standard di Java, non si sarà in grado di imbrogliare con la riflessione a meno che non si scriva la politica di sicurezza per consentirle specificamente.

Il concetto “amico” è utile in Java, ad esempio per separare un’API dalla sua implementazione. È normale che le classi di implementazione abbiano bisogno di accedere a interni di classi API, ma queste non devono essere esposte ai client API. Questo può essere ottenuto utilizzando il pattern “Friend Accessor” come descritto di seguito:

La class esposta tramite l’API:

 package api; public final class Exposed { static { // Declare classs in the implementation package as 'friends' Accessor.setInstance(new AccessorImpl()); } // Only accessible by 'friend' classs. Exposed() { } // Only accessible by 'friend' classs. 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 class che fornisce la funzionalità “amico”:

 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); } 

Esempio di accesso da una class nel pacchetto di implementazione “amico”:

 package impl; public final class FriendlyAccessExample { public static void main(String[] args) { Accessor accessor = Accessor.getInstance(); Exposed exposed = accessor.createExposed(); accessor.sayHello(exposed); } } 

Ci sono due soluzioni alla tua domanda che non implicano il mantenimento di tutte le classi nello stesso pacchetto.

Il primo è utilizzare il modello Friend Accessor / Friend Package descritto in (Practical API Design, Tulach 2008).

Il secondo è usare OSGi. C’è un articolo qui che spiega come OSGi realizza questo.

Domande correlate: 1 , 2 e 3 .

Per quanto ne so, non è ansible.

Forse, potresti darci qualche altro dettaglio sul tuo design. Domande come queste sono probabilmente il risultato di difetti di progettazione.

Prendi in considerazione

  • Perché queste classi sono in pacchetti diversi, se sono così strettamente correlati?
  • A ha accesso ai membri privati ​​di B o l’operazione deve essere spostata in class B e triggersta da A?
  • È davvero questo il modo di chiamare o gestire meglio gli eventi?

La risposta di eirikma è semplice ed eccellente. Potrei aggiungere ancora una cosa: invece di avere un metodo pubblicamente accessibile, getFriend () per ottenere un amico che non può essere usato, potresti fare un ulteriore passo avanti e non permettere di ottenere l’amico senza un token: getFriend (Service.FriendToken). Questo FriendToken sarebbe una class pubblica interna con un costruttore privato, in modo che solo il Servizio potesse istanziarne uno.

Ecco un chiaro esempio del caso d’uso con una class Friend riutilizzabile. Il vantaggio di questo meccanismo è la semplicità d’uso. Forse buono per dare alle classi di test unitari un accesso maggiore rispetto al resto dell’applicazione.

Per iniziare, ecco un esempio di come usare la class 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; } } 

Quindi in un altro pacchetto puoi fare questo:

 public class Other { private final Friend friend = new Friend(this); public void test() { String s = new Owner().getMember(friend); System.out.println(s); } } 

La class Friend è la seguente.

 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... classs) { for (final Class c : classs) if (c == as) return; is((Class)null); } } 

Tuttavia, il problema è che può essere abusato in questo modo:

 public class Abuser { public void doBadThings() { Friend badFriend = new Friend(new Other()); String s = new Owner().getMember(badFriend); System.out.println(s); } } 

Ora, potrebbe essere vero che la class Other non ha costruttori pubblici, rendendo quindi imansible il suddetto codice Abuser . Tuttavia, se la tua class ha un costruttore pubblico, è probabilmente consigliabile duplicare la class Friend come class interna. Prendi questa class Other2 come esempio:

 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); } } 

E quindi la class Owner2 sarebbe così:

 public class Owner2 { private final String member = "value"; public String getMember(final Other2.Friend friend) { friend.check(); return member; } } 

Si noti che la class Other2.Friend ha un costruttore privato, rendendo questo modo molto più sicuro di farlo.

La soluzione fornita non era forse la più semplice. Un altro approccio è basato sulla stessa idea del C ++: i membri privati ​​non sono accessibili al di fuori del pacchetto / ambito privato, tranne che per una class specifica che il proprietario si fa amico di sé.

La class che richiede l’accesso di un amico a un membro deve creare una “class di amici” astratta interna pubblica che la class proprietaria delle proprietà nascoste possa esportare, restituendo una sottoclass che implementa i metodi di implementazione dell’accesso. Il metodo “API” della class friend può essere privato, quindi non è accessibile al di fuori della class che ha bisogno di un accesso amico. La sua unica affermazione è una chiamata a un membro protetto astratto che la class di esportazione implementa.

Ecco il codice:

Innanzitutto il test che verifica che funzioni effettivamente:

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

Quindi il servizio che richiede l’accesso amico a un membro privato del pacchetto di entity framework:

 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 subclasss of. * Public, so the class itself is visible in Entity's package. */ public static abstract class EntityFriend { /** * Access method: private not visible (aka 'friendly') outside enclosing class. */ private String getEntityPackagePrivateData() { return getEntityPackagePrivateDataImpl(); } /** contribute access to private member by implementing this */ protected abstract String getEntityPackagePrivateDataImpl(); } } 

Infine: la class Entity che fornisce un accesso amichevole a un membro privato del pacchetto solo alla class 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(); } }; } } 

Ok, devo ammettere che è un po ‘più lungo di “servizio di amicizia :: Servizio;” ma potrebbe essere ansible accorciarlo mantenendo il controllo in fase di compilazione usando le annotazioni.

In Java è ansible avere una “familiarità correlata al pacchetto”. Questo può essere utile per il test dell’unità. Se non si specifica privato / pubblico / protetto di fronte a un metodo, sarà “amico nel pacchetto”. Una class nello stesso pacchetto sarà in grado di accedervi, ma sarà privata al di fuori della class.

Questa regola non è sempre nota ed è una buona approssimazione di una parola chiave “amico” C ++. Lo trovo un buon sostituto.

Penso che le classi di amici in C ++ siano come un concetto di class interiore in Java. Usando le classi interne puoi effettivamente definire una class che racchiude e una chiusa. La class chiusa ha pieno accesso ai membri pubblici e privati ​​della class che li ospita. vedere il seguente link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html

Penso che l’approccio dell’utilizzo del modello di accesso degli amici sia troppo complicato. Ho dovuto affrontare lo stesso problema e ho risolto utilizzando il buon vecchio costruttore di copie, noto da C ++, in 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; } } 

Nella tua applicazione puoi scrivere il seguente codice:

 public class MyApp { private static class ProtectedAccessor extends ProtectedContainer { protected ProtectedAccessor() { super(); } protected PrivateAccessor(ProtectedContainer prot) { super(prot); } public String exposeProtected() { return iwantAccess; } } } 

Il vantaggio di questo metodo è che solo la tua applicazione ha accesso ai dati protetti. Non è esattamente una sostituzione della parola chiave friend. Ma penso che sia abbastanza adatto quando si scrivono librerie personalizzate ed è necessario accedere ai dati protetti.

Ogni volta che devi gestire istanze di ProtectedContainer puoi avvolgere il tuo ProtectedAccessor intorno ad esso e puoi ottenere l’accesso.

Funziona anche con metodi protetti. Li definisci protetti nella tua API. Più avanti nella tua applicazione scrivi una class wrapper privata ed esponi il metodo protetto come pubblico. Questo è tutto.

Se si desidera accedere a metodi protetti, è ansible creare una sottoclass della class che si desidera utilizzare che espone i metodi che si desidera utilizzare come pubblici (o interni allo spazio dei nomi per essere più sicuri) e avere un’istanza di tale class nella class (usalo come proxy).

Per quanto riguarda i metodi privati ​​(credo) tu sei sfortunato.

Sono d’accordo che nella maggior parte dei casi la parola chiave amico non è necessaria.

  • Package-private (aka. Default) è sufficiente nella maggior parte dei casi in cui si ha un gruppo di classi fortemente intrecciate
  • Solitamente per le classi di debug che richiedono l’accesso a internals, di solito il metodo è privato e l’accesso tramite la reflection. La velocità di solito non è importante qui
  • A volte, si implementa un metodo che è un “hack” o altrimenti che è sobject a modifiche. L’ho reso pubblico, ma uso @Deprecated per indicare che non si deve fare affidamento su questo metodo esistente.

E infine, se è veramente necessario, c’è il modello di accesso degli amici menzionato nelle altre risposte.

Non usare una parola chiave o giù di lì.

Potresti “imbrogliare” usando la riflessione, ecc., Ma non consiglierei “barare”.

Un metodo che ho trovato per risolvere questo problema è creare un object accessor, in questo modo:

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

Il primo codice a chiamare getAccessor() “rivendica la proprietà” dell’accessor. Di solito, questo è il codice che crea l’object.

 Foo bar = new Foo(); //This object is safe to share. FooAccessor barAccessor = bar.getAccessor(); //This one is not. 

Ciò ha anche un vantaggio rispetto al meccanismo amico di C ++, poiché consente di limitare l’accesso a livello di istanza , anziché a livello di class . Controllando il riferimento accessor, si controlla l’accesso all’object. Puoi anche creare più accessor e dare accesso a ciascuno di essi, il che consente un controllo preciso su quale codice può accedere a ciò:

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

Infine, se desideri che le cose siano un po ‘più organizzate, puoi creare un object di riferimento che trattiene tutto insieme. Ciò consente di richiedere a tutti gli utenti con una chiamata al metodo e di mantenerli insieme all’istanza collegata. Una volta che hai il riferimento, puoi passare gli accessor al codice che ne ha bisogno:

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

Dopo un sacco di colpi alla testa (non del tipo buono), questa era la mia soluzione finale, e mi piace molto. È flessibile, semplice da usare e consente un ottimo controllo sull’accesso di class. (L’accesso con solo riferimento è molto utile.) Se si utilizzano protetti anziché privati ​​per gli accessor / riferimenti, le sottoclassi di Foo possono anche restituire riferimenti estesi da getReference . Inoltre non richiede alcuna riflessione, quindi può essere utilizzato in qualsiasi ambiente.

A partire da Java 9, i moduli possono essere utilizzati per rendere questo un non-problema in molti casi.

Preferisco la delega o la composizione o la class di fabbrica (a seconda del problema che si risolve in questo problema) per evitare di renderlo una class pubblica.

Se si tratta di un problema di “interfaccia / classi di implementazione in pacchetti diversi”, utilizzerei una class di fabbrica pubblica nello stesso pacchetto del pacchetto impl e impedire l’esposizione della class impl.

Se si tratta di un problema “Odio rendere pubblico questo metodo class / solo per fornire questa funzionalità per un’altra class in un altro pacchetto”, utilizzare una class di delega pubblica nello stesso pacchetto ed esporre solo quella parte della funzionalità necessario per la class “outsider”.

Alcune di queste decisioni sono guidate dall’architettura classloading del server di destinazione (bundle OSGi, WAR / EAR, ecc.), Dalle convenzioni di denominazione dei pacchetti e di implementazione. Ad esempio, la soluzione proposta sopra, modello “Friend Accessor” è intelligente per le normali applicazioni java. Mi chiedo se sia difficile implementarlo in OSGi a causa della differenza nello stile di caricamento delle classi.

Una volta ho visto una soluzione basata su una riflessione che eseguiva “friend checking” in fase di runtime usando reflection e controllando lo stack di chiamate per vedere se la class che chiamava il metodo era autorizzata a farlo. Essendo un controllo di runtime, ha l’ovvio inconveniente.