Vorrei essere in grado di scrivere una classe Java in un pacchetto che può accedere a metodi non pubblici di una classe in un altro pacchetto senza renderlo una sottoclasse dell'altra classe. È possibile?
Vorrei essere in grado di scrivere una classe Java in un pacchetto che può accedere a metodi non pubblici di una classe in un altro pacchetto senza renderlo una sottoclasse dell'altra classe. È possibile?
Risposte:
Ecco un piccolo trucco che uso in JAVA per replicare il meccanismo amico di C ++.
Diciamo che ho una lezione Romeo
e un'altra lezione Juliet
. Sono in diversi pacchetti (famiglia) per motivi di odio.
Romeo
vuole cuddle
Juliet
e Juliet
vuole solo lasciarla Romeo
cuddle
.
In C ++, Juliet
dichiarerebbe Romeo
come (amante) friend
ma non ci sono cose simili in Java.
Ecco le lezioni e il trucco:
Prima le signore :
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?");
}
}
Quindi il metodo Juliet.cuddle
è public
ma è necessario un Romeo.Love
per chiamarlo. Lo utilizza Romeo.Love
come "sicurezza della firma" per garantire che solo Romeo
questo metodo possa essere chiamato e controlla che l'amore sia reale, in modo che l'autonomia lanci un NullPointerException
se lo è 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 classe Romeo.Love
è pubblica, ma lo è il suo costruttore private
. Quindi chiunque può vederlo, ma solo Romeo
può costruirlo. Uso un riferimento statico in modo Romeo.Love
che quello che non viene mai utilizzato sia costruito una sola volta e non influisca sull'ottimizzazione.
Pertanto, Romeo
può cuddle
Juliet
e solo lui può perché solo lui può costruire e accedere a un Romeo.Love
esempio, che è richiesto dalla Juliet
a cuddle
lei (altrimenti lei schiaffo con un NullPointerException
).
Romeo
's Love
per Julia
eterno modificando il love
campo per essere final
;-).
I progettisti di Java hanno esplicitamente respinto l'idea di amico in quanto funziona in C ++. Metti i tuoi "amici" nello stesso pacchetto. La sicurezza privata, protetta e in pacchetto è applicata come parte del design della lingua.
James Gosling voleva che Java fosse C ++ senza errori. Credo che abbia ritenuto quell'amico un errore perché viola i principi OOP. I pacchetti forniscono un modo ragionevole per organizzare i componenti senza essere troppo puristi riguardo a OOP.
NR ha sottolineato che è possibile imbrogliare con la riflessione, ma funziona anche se non si utilizza SecurityManager. Se si attiva la sicurezza standard Java, non sarà possibile imbrogliare con la riflessione se non si scrive una politica di sicurezza per consentirla in modo specifico.
friend
violasse OOP (in particolare, più dell'accesso ai pacchetti) , non lo capiva davvero (del tutto possibile, molte persone lo fraintendono).
Il concetto di "amico" è utile in Java, ad esempio, per separare un'API dalla sua implementazione. È comune che le classi di implementazione abbiano bisogno di accedere agli interni della classe API, ma questi non dovrebbero essere esposti ai client API. Ciò può essere ottenuto utilizzando il modello "Accesso amico" come descritto di seguito:
La classe esposta tramite 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 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 classe 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);
}
}
Esistono due soluzioni alla tua domanda che non comportano il mantenimento di tutte le classi nello stesso pacchetto.
Il primo è utilizzare il modello di Accessor Friend / Package Friend descritto in (Practical API Design, Tulach 2008).
Il secondo è usare OSGi. C'è un articolo qui che spiega come OSGi realizza questo.
Per quanto ne so, non è possibile.
Forse, potresti darci qualche dettaglio in più sul tuo design. Domande come queste sono probabilmente il risultato di difetti di progettazione.
Basta considerare
La risposta di eirikma è semplice ed eccellente. Potrei aggiungere un'altra cosa: invece di avere un metodo accessibile pubblicamente, getFriend () per ottenere un amico che non può essere usato, potresti fare un ulteriore passo e non consentire di ottenere l'amico senza un token: getFriend (Service.FriendToken). Questo FriendToken sarebbe una classe pubblica interna con un costruttore privato, in modo che solo il Servizio possa istanziarne una.
Ecco un chiaro esempio di caso d'uso con una Friend
classe riutilizzabile . Il vantaggio di questo meccanismo è la semplicità d'uso. Forse è utile per dare alle classi di unit test più accesso rispetto al resto dell'applicazione.
Per iniziare, ecco un esempio di come utilizzare 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;
}
}
Quindi in un altro pacchetto puoi farlo:
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 è 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... classes) {
for (final Class c : classes)
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, può essere vero che la Other
classe non ha costruttori pubblici, rendendo quindi impossibile il Abuser
codice sopra . Tuttavia, se la classe ha un costruttore pubblico, allora è probabilmente consigliabile duplicare la classe amico come una classe interna. Prendi questa Other2
classe 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 poi la Owner2
classe sarebbe così:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Nota che la Other2.Friend
classe ha un costruttore privato, rendendolo così un modo molto più sicuro di farlo.
La soluzione fornita non era forse la più semplice. Un altro approccio si basa sulla stessa idea del C ++: i membri privati non sono accessibili al di fuori del pacchetto / ambito privato, ad eccezione di una classe specifica che il proprietario rende amico di se stesso.
La classe che necessita dell'accesso di un amico a un membro deve creare una "classe di amicizia" astratta pubblica interna alla quale la classe proprietaria delle proprietà nascoste può esportare l'accesso, restituendo una sottoclasse che implementa i metodi di implementazione dell'accesso. Il metodo "API" della classe amico può essere privato, quindi non è accessibile al di fuori della classe che necessita dell'accesso amico. La sua unica affermazione è una chiamata a un membro protetto astratto implementato dalla classe esportatrice.
Ecco il codice:
Innanzitutto il test che verifica che questo 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 necessita dell'accesso di un amico a un membro privato pacchetto di 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();
}
}
Infine: la classe Entity che fornisce un facile accesso a un membro privato del pacchetto solo alla 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();
}
};
}
}
Va bene, devo ammettere che è un po 'più lungo di "servizio amici :: Servizio;" ma potrebbe essere possibile accorciarlo mantenendo il controllo del tempo di compilazione utilizzando le annotazioni.
In Java è possibile avere una "amicizia relativa al pacchetto". Questo può essere utile per i test unitari. Se non si specifica privato / pubblico / protetto davanti a un metodo, sarà "amico nel pacchetto". Una classe nello stesso pacchetto sarà in grado di accedervi, ma sarà privata al di fuori della classe.
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 il concetto di classe interna in Java. Usando le classi interne puoi effettivamente definire una classe chiusa e una classe chiusa. La classe chiusa ha pieno accesso ai membri pubblici e privati della sua classe allegata. vedi il seguente link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Penso che l'approccio dell'uso del modello di accesso agli amici sia troppo complicato. Ho dovuto affrontare lo stesso problema e ho risolto usando il buon vecchio costruttore di copie, noto dal 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 potresti 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 l'applicazione ha accesso ai dati protetti. Non è esattamente una sostituzione della parola chiave friend. Ma penso che sia abbastanza adatto quando scrivi librerie personalizzate e devi accedere ai dati protetti.
Ogni volta che hai a che fare con istanze di ProtectedContainer puoi avvolgerlo attorno al tuo ProtectedAccessor e ottenere l'accesso.
Funziona anche con metodi protetti. Li definisci protetti nella tua API. Più avanti nella tua applicazione scrivi una classe wrapper privata ed esponi il metodo protetto come pubblico. Questo è tutto.
ProtectedContainer
può essere sottoclassato al di fuori del pacchetto!
Se si desidera accedere a metodi protetti, è possibile creare una sottoclasse della classe che si desidera utilizzare che esponga i metodi che si desidera utilizzare come pubblici (o interni allo spazio dei nomi per essere più sicuri) e disporre di un'istanza di quella classe nella propria classe (usalo come proxy).
Per quanto riguarda i metodi privati (penso) sei sfortunato.
Sono d'accordo che nella maggior parte dei casi la parola chiave friend non è necessaria.
E infine, se è davvero 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 di "imbrogliare".
Un metodo che ho trovato per risolvere questo problema è quello di creare un oggetto 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 da chiamare getAccessor()
"rivendica la proprietà" dell'accessor. Di solito, questo è il codice che crea l'oggetto.
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 classe . Controllando il riferimento dell'accessorio, si controlla l'accesso all'oggetto. È inoltre possibile creare più accessori e fornire un accesso diverso a ciascuno, il che consente un controllo approfondito su quale codice può accedere a cosa:
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 oggetto di riferimento, che tiene tutto insieme. Ciò consente di rivendicare tutti gli accessori con una chiamata di metodo, nonché di tenerli insieme alla loro istanza collegata. Una volta ottenuto il riferimento, è possibile passare gli accessi 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 molti colpi alla testa (non del genere buono), questa è stata la mia soluzione finale, e mi piace molto. È flessibile, semplice da usare e consente un ottimo controllo sull'accesso alle classi. (L' accesso con solo riferimento è molto utile.) Se si utilizza protetto anziché privato per gli accessori / riferimenti, le sottoclassi di Foo possono persino restituire riferimenti estesi da getReference
. Inoltre non richiede alcun riflesso, quindi può essere utilizzato in qualsiasi ambiente.
Preferisco la delega o la composizione o la classe di fabbrica (a seconda del problema che causa questo problema) per evitare di renderla una classe pubblica.
Se si tratta di un problema di "classi di interfaccia / implementazione in pacchetti diversi", utilizzerei una classe di fabbrica pubblica che si troverebbe nello stesso pacchetto del pacchetto impl e impedirebbe l'esposizione della classe impl.
Se si tratta di un problema "Odio rendere pubblica questa classe / metodo solo per fornire questa funzionalità per qualche altra classe in un pacchetto diverso", utilizzerei una classe delegata pubblica nello stesso pacchetto ed esporrei solo quella parte della funzionalità necessario alla classe "outsider".
Alcune di queste decisioni sono guidate dall'architettura di classloading del server di destinazione (bundle OSGi, WAR / EAR, ecc.), Dalle convenzioni sulla distribuzione e sulla denominazione dei pacchetti. Ad esempio, la soluzione sopra proposta, il modello "Friend Accessor" è intelligente per le normali applicazioni Java. Mi chiedo se diventa difficile implementarlo in OSGi a causa della differenza nello stile di caricamento delle classi.
Non so se sia utile a nessuno, ma l'ho gestito nel modo seguente:
Ho creato un'interfaccia (AdminRights).
Ogni classe che dovrebbe essere in grado di chiamare dette funzioni dovrebbe implementare AdminRights.
Quindi ho creato una funzione HasAdminRights come segue:
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;
}
}
Una volta ho visto una soluzione basata su reflection che ha fatto "il controllo degli amici" in fase di esecuzione usando la riflessione e il controllo dello stack di chiamate per vedere se la classe che chiamava il metodo era autorizzata a farlo. Essendo un controllo di runtime, ha l'ovvio inconveniente.