friendship friends example and java c++ friend accessor

java - friends - friend modifier c++



¿Hay alguna forma de simular el concepto de "amigo" de C++ en Java? (18)

Me gustaría poder escribir una clase Java en un paquete que pueda acceder a métodos no públicos de una clase en otro paquete sin tener que convertirla en una subclase de la otra clase. es posible?


A partir de Java 9, los módulos se pueden utilizar para que esto no sea un problema en muchos casos.


Acepto que en la mayoría de los casos, la palabra clave friend es innecesaria.

  • Package-private (también conocido como default) es suficiente en la mayoría de los casos donde tienes un grupo de clases muy entrelazadas
  • Para las clases de depuración que quieren acceso a las partes internas, normalmente hago que el método sea privado y accedo a él a través de la reflexión. La velocidad por lo general no es importante aquí
  • A veces, implementa un método que es un "truco" o que está sujeto a cambios. Lo hago público, pero uso @Deprecated para indicar que no debe confiar en este método existente.

Y finalmente, si es realmente necesario, existe el patrón de acceso de amigo mencionado en las otras respuestas.


Aquí hay un claro ejemplo de caso de uso con una clase Friend reutilizable. El beneficio de este mecanismo es la simplicidad de uso. Tal vez sea bueno para dar a las clases de prueba un acceso más amplio que el resto de la aplicación.

Para comenzar, aquí hay un ejemplo de cómo usar la clase 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; } }

Luego en otro paquete puedes hacer esto:

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

La clase Friend es la siguiente.

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

Sin embargo, el problema es que se puede abusar así:

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

Ahora bien, puede ser cierto que la clase Other no tiene ningún constructor público, por lo tanto, imposibilita el código del Abuser anterior. Sin embargo, si su clase tiene un constructor público, entonces probablemente sea recomendable duplicar la clase Friend como clase interna. Tome esta clase Other2 como un ejemplo:

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

Y entonces la clase Owner2 sería así:

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

Tenga en cuenta que la clase Other2.Friend tiene un constructor privado, por lo que esta es una forma mucho más segura de hacerlo.


Creo que el enfoque de usar el patrón de acceso de amigo es demasiado complicado. Tuve que enfrentar el mismo problema y resolví usar el buen y viejo constructor de copia, conocido 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; } }

En su aplicación puede escribir el siguiente código:

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

La ventaja de este método es que solo su aplicación tiene acceso a los datos protegidos. No es exactamente una sustitución de la palabra clave friend. Pero creo que es bastante adecuado cuando escribe bibliotecas personalizadas y necesita acceder a datos protegidos.

Cada vez que tenga que lidiar con instancias de ProtectedContainer puede envolver su ProtectedAccessor y obtener acceso.

También funciona con métodos protegidos. Los defines protegidos en tu API. Más adelante en su aplicación, escribe una clase contenedora privada y expone el método protegido como público. Eso es.


Creo que las clases de amigos en C ++ son como el concepto de clase interna en Java. Usando clases internas, en realidad puedes definir una clase envolvente y una cerrada. La clase incluida tiene acceso completo a los miembros públicos y privados de su clase adjunta. vea el siguiente enlace: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html


El concepto de "amigo" es útil en Java, por ejemplo, para separar una API de su implementación. Es común que las clases de implementación necesiten acceder a las partes internas de la clase API, pero estas no deben estar expuestas a los clientes API. Esto se puede lograr utilizando el patrón ''Friend Accessor'' como se detalla a continuación:

La clase expuesta a través de la 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 clase que proporciona la funcionalidad ''amigo'':

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

Ejemplo de acceso de una clase en el paquete de implementación ''amigo'':

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


En Java, es posible tener una "amistad relacionada con el paquete". Esto puede ser útil para pruebas unitarias. Si no especifica privado / público / protegido frente a un método, será "amigo en el paquete". Una clase en el mismo paquete podrá acceder a ella, pero será privada fuera de la clase.

Esta regla no siempre se conoce, y es una buena aproximación de una palabra clave "amigo" de C ++. Lo encuentro un buen reemplazo.


Hasta donde yo sé, no es posible.

Tal vez, podrías darnos más detalles sobre tu diseño. Preguntas como estas son probablemente el resultado de fallas de diseño.

Solo considera

  • ¿Por qué esas clases están en diferentes paquetes, si están tan estrechamente relacionados?
  • ¿Tiene A para acceder a miembros privados de B o debería moverse la operación a la clase B y ser activada por A?
  • ¿Esto realmente llama o es mejor el manejo de eventos?

Hay dos soluciones para su pregunta que no implican mantener todas las clases en el mismo paquete.

El primero es usar el patrón Friend Friender / Friend Package descrito en (Practical API Design, Tulach 2008).

El segundo es usar OSGi. Hay un artículo here explicando cómo OSGi logra esto.

Preguntas relacionadas: 1 , 2 y 3 .


La respuesta de eirikma es fácil y excelente. Podría agregar una cosa más: en lugar de tener un método accesible al público, getFriend () para obtener un amigo que no se puede usar, podría ir un paso más allá y no permitir que el amigo obtenga un token: getFriend (Service.FriendToken). Este FriendToken sería una clase pública interna con un constructor privado, por lo que solo Service podría crear una instancia.


La solución provista quizás no era la más simple. Otro enfoque se basa en la misma idea que en C ++: los miembros privados no son accesibles fuera del alcance del paquete / privado, a excepción de una clase específica que el propietario se hace amigo de sí mismo.

La clase que necesita el acceso de un amigo a un miembro debe crear una "clase de amigo" abstracta pública interna a la que la clase propietaria de las propiedades ocultas pueda exportar el acceso, devolviendo una subclase que implemente los métodos de implementación de acceso. El método "API" de la clase de amigo puede ser privado, por lo que no es accesible fuera de la clase que necesita el acceso de un amigo. Su única declaración es una llamada a un miembro abstracto protegido que implementa la clase exportadora.

Aquí está el código:

Primero, la prueba que verifica que esto realmente funciona:

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

Luego, el Servicio que necesita un amigo acceda a un paquete miembro privado de la Entidad:

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

Finalmente: la clase Entity que proporciona acceso amigable a un miembro privado del paquete solo a la clase 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(); } }; } }

De acuerdo, debo admitir que es un poco más largo que "friend service :: Service"; pero podría ser posible acortarlo conservando la verificación en tiempo de compilación mediante el uso de anotaciones.


Los diseñadores de Java rechazaron explícitamente la idea de amigo, ya que funciona en C ++. Pones a tus "amigos" en el mismo paquete. La seguridad privada, protegida y empaquetada se aplica como parte del diseño del idioma.

James Gosling quería que Java fuera C ++ sin los errores. Creo que sintió que ese amigo fue un error porque viola los principios de OOP. Los paquetes proporcionan una forma razonable de organizar los componentes sin ser demasiado puristas sobre OOP.

NR señaló que se podía hacer trampa usando el reflejo, pero incluso eso solo funciona si no se está utilizando el SecurityManager. Si activa la seguridad estándar de Java, no podrá hacer trampas con la reflexión a menos que escriba una política de seguridad para permitirla específicamente.


No usar una palabra clave o algo así.

Podrías "hacer trampa" usando el reflejo, etc., pero no recomendaría "hacer trampa".


Prefiero delegación o composición o clase de fábrica (dependiendo del problema que resulte en este problema) para evitar convertirla en una clase pública.

Si se trata de un problema de "clases de interfaz / implementación en paquetes diferentes", entonces usaría una clase de fábrica pública que estaría en el mismo paquete que el paquete impl y evitaría la exposición de la clase impl.

Si se trata de un problema "Odio hacer que esta clase / método sea público solo para proporcionar esta funcionalidad para alguna otra clase en un paquete diferente", entonces usaría una clase pública de delegado en el mismo paquete y expondría solo esa parte de la funcionalidad necesario por la clase "outsider".

Algunas de estas decisiones son impulsadas por la arquitectura de carga de clases del servidor de destino (paquete OSGi, WAR / EAR, etc.), la implementación y las convenciones de nomenclatura de paquetes. Por ejemplo, la solución propuesta anteriormente, el patrón ''Friend Accessor'' es inteligente para aplicaciones java normales. Me pregunto si es complicado implementarlo en OSGi debido a la diferencia en el estilo de carga de clases.


Si desea acceder a métodos protegidos, puede crear una subclase de la clase que desea usar que exponga los métodos que desea usar como públicos (o internos al espacio de nombres para que sean más seguros) y tener una instancia de esa clase en su clase (úselo como un proxy).

En lo que respecta a los métodos privados, creo que no tienes suerte.


Un método que he encontrado para resolver este problema es crear un objeto de acceso, como ese:

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

El primer código para llamar a getAccessor() "reclama propiedad" del getAccessor() de acceso. Por lo general, este es el código que crea el objeto.

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

Esto también tiene una ventaja sobre el mecanismo de amigo de C ++, porque le permite limitar el acceso en un nivel por instancia , a diferencia de un nivel por clase . Al controlar la referencia de acceso, usted controla el acceso al objeto. También puede crear varios descriptores de acceso y otorgar acceso diferente a cada uno, lo que permite un control detallado sobre qué código puede acceder a qué:

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

Finalmente, si desea que las cosas estén un poco más organizadas, puede crear un objeto de referencia, que lo mantenga todo unido. Esto le permite reclamar todos los accesores con una llamada a método, así como mantenerlos juntos con su instancia vinculada. Una vez que tenga la referencia, puede pasar los accesos al código que lo necesita:

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

Después de mucho ruido (no del bueno), esta fue mi solución final, y me gustó mucho. Es flexible, fácil de usar y permite un muy buen control sobre el acceso de clase. (El acceso con referencia solamente es muy útil.) Si usa protected en lugar de private para los accessors / references, las subclases de Foo incluso pueden devolver referencias extendidas de getReference . Tampoco requiere ningún reflejo, por lo que puede usarse en cualquier entorno.


Una vez vi una solución basada en la reflexión que hizo "friend checking" en tiempo de ejecución usando reflection y revisando la pila de llamadas para ver si la clase que llamaba al método tenía permiso para hacerlo. Al ser un control en tiempo de ejecución, tiene el inconveniente obvio.


Aquí hay un pequeño truco que utilizo en JAVA para replicar el mecanismo amigo de C ++.

Digamos que tengo una clase Romeo y otra clase Juliet . Están en diferentes paquetes (familia) por razones de odio.

Romeo quiere cuddle Juliet y Juliet solo quiere que Romeo cuddle .

En C ++, Juliet declararía a Romeo como un friend (amante) pero no existen tales cosas en java.

Aquí están las clases y el truco:

Mujeres primero :

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

Entonces, el método Juliet.cuddle es public pero necesitas un Romeo.Love para llamarlo. Utiliza este Romeo.Love como "seguridad de firma" para garantizar que solo Romeo pueda invocar este método y simplemente llame a hashCode para que el tiempo de ejecución hashCode una NullPointerException si es null .

Ahora muchachos:

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 clase Romeo.Love es pública, pero su constructor es private . Por lo tanto, cualquiera puede verlo, pero solo Romeo puede construirlo. Utilizo una referencia estática para que Romeo.Love que nunca se usa solo se construya una vez y no afecte a la optimización.

Por lo tanto, Romeo puede cuddle Juliet y solo él puede porque solo él puede construir y acceder a una instancia de Romeo.Love , que es requerida por Juliet para cuddle (o de lo contrario ella te abofeteará con una NullPointerException ).