Domanda

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?

È stato utile?

Soluzione 3

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

Altri suggerimenti

Ecco un piccolo trucco che uso in JAVA per replicare il meccanismo amico di C ++.

Diciamo che ho una classe Romeo e un'altra classe Juliet . Sono in diversi pacchetti (famiglia) per motivi di odio.

Romeo vuole coccolare Juliet e Juliet vuole lasciare solo Romeo coccola lei.

In C ++, Juliet dichiarerebbe Romeo come (amante) amico 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 hai bisogno di un Romeo.Love per chiamarlo. Utilizza questo Romeo.Love come "sicurezza della firma" per assicurarsi che solo Romeo possa chiamare questo metodo e verificare che l'amore sia reale in modo che il runtime lanci un NullPointerException se è null .

Ora 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 il suo costruttore è private . Pertanto chiunque può vederlo, ma solo Romeo può costruirlo. Uso un riferimento statico in modo che il Romeo.Love che non viene mai usato sia costruito una sola volta e non influisca sull'ottimizzazione.

Pertanto, Romeo può coccolare Juliet e solo lui può perché solo lui può costruire e accedere a un Romeo.Love , richiesta da Juliet per coccolarla (altrimenti ti schiaffeggerà con un NullPointerException ).

I progettisti di Java hanno esplicitamente respinto l'idea di amico in quanto funziona in C ++. Inserisci 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 offrono un modo ragionevole di organizzare i componenti senza essere troppo puristi riguardo a OOP.

NR ha sottolineato che è possibile imbrogliare usando 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.

Esistono due soluzioni alla tua domanda che non comportano il mantenimento di tutte le classi nello stesso pacchetto.

Il primo è utilizzare il Friend Accessor / Package Friend 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 è possibile.

Forse potresti darci qualche dettaglio in più sul tuo design. Domande come queste sono probabilmente il risultato di difetti di progettazione.

Basta considerare

  • Perché queste classi si trovano in pacchetti diversi, se sono così strettamente correlate?
  • A deve accedere ai membri privati ??di B o l'operazione deve essere spostata in classe B e attivata da A?
  • È davvero una chiamata o la gestione degli eventi è migliore?

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 classe Friend 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 classe 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 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 classe 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... 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 classe Other non ha costruttori pubblici, rendendo quindi impossibile il precedente codice Abuser . Tuttavia, se la tua classe ha un costruttore pubblico, è probabilmente consigliabile duplicare la classe Friend come classe interna. Prendi questa classe 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 classe Owner2 sarebbe così:

public class Owner2 {
    private final String member = "value";

    public String getMember(final Other2.Friend friend) {
        friend.check();
        return member;
    }
}

Nota che la classe Other2.Friend 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 dovrebbe creare un abstract pubblico interno "classe di amici" a cui la classe proprietaria delle proprietà nascoste può esportare l'accesso, restituendo una sottoclasse che implementa i metodi di implementazione dell'accesso. L '"API" il metodo della classe di amici può essere privato, quindi non è accessibile al di fuori della classe che necessita dell'accesso di amici. La sua unica affermazione è una chiamata a un membro protetto astratto implementato dalla classe esportatrice.

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 necessita dell'accesso come 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 accesso amichevole 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();
            }
        };
    }
}

Okay, devo ammettere che è un po 'più lungo di " friend service :: Service; " 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 specifichi 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 un amico "C ++" parola chiave. 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 all'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 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 scrivi librerie personalizzate e devi accedere ai dati protetti.

Ogni volta che devi affrontare le 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.

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 tua 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.

  • Package-private (aka. default) è sufficiente nella maggior parte dei casi in cui hai un gruppo di classi fortemente intrecciate
  • Per le classi di debug che desiderano accedere agli interni, di solito rendo privato il metodo e accedo ad esso tramite la riflessione. La velocità di solito non è importante qui
  • A volte, implementi un metodo che è un " hack " o altrimenti che è soggetto a modifiche. Lo rendo pubblico, ma uso @Deprecated per indicare che non dovresti fare affidamento su questo metodo esistente.

E infine, se è davvero necessario, c'è il modello di accesso agli amici menzionato nelle altre risposte.

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

Potresti " imbrogliare " usando la riflessione ecc., ma non consiglierei "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 a chiamare getAccessor () " rivendica la proprietà " dell'accessorio. 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 ++, perché consente di limitare l'accesso a livello per istanza , a differenza di un livello per classe . Controllando il riferimento dell'accessorio, si controlla l'accesso all'oggetto. Puoi anche 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 accessor 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 quelli buoni), 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 solo con riferimento è molto utile.) Se si utilizza il protocollo protetto anziché privato per gli accessori / riferimenti, le sottoclassi di Foo possono persino 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 problema in molti casi.

Preferisco la delega o la composizione o la classe di fabbrica (a seconda del problema che causa questo problema) per evitare di renderlo una classe pubblica.

Se si tratta di una "interfaccia / classi di implementazione in diversi pacchetti" problema, quindi utilizzerei una classe di fabbrica pubblica che si troverebbe nello stesso pacchetto del pacchetto impl e impedirebbe l'esposizione della classe impl.

Se è un " odio rendere pubblica questa classe / metodo solo per fornire questa funzionalità per qualche altra classe in un pacchetto diverso " problema, quindi utilizzerei una classe di delegati pubblici nello stesso pacchetto ed esporrei solo quella parte della funzionalità richiesta dall'esterno " Classe.

Alcune di queste decisioni sono guidate dall'architettura di classloading del server di destinazione (bundle OSGi, WAR / EAR, ecc.), convenzioni di denominazione dei pacchetti e di distribuzione. Ad esempio, la soluzione sopra proposta, 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.

Una volta ho visto una soluzione basata sulla riflessione che faceva "controllare un amico" " in fase di esecuzione usando reflection e controllando lo stack di chiamate per vedere se la classe che chiama il metodo è stata autorizzata a farlo. Essendo un controllo di runtime, ha l'ovvio inconveniente.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top