Eu gostaria de poder escrever uma classe Java em um pacote que possa acessar métodos não públicos de uma classe em outro pacote sem precisar torná-la uma subclasse da outra classe. Isso é possível?
Eu gostaria de poder escrever uma classe Java em um pacote que possa acessar métodos não públicos de uma classe em outro pacote sem precisar torná-la uma subclasse da outra classe. Isso é possível?
Respostas:
Aqui está um pequeno truque que eu uso no JAVA para replicar o mecanismo de amigo do C ++.
Digamos que eu tenho uma aula Romeo
e outra aula Juliet
. Eles estão em pacotes diferentes (família) por razões de ódio.
Romeo
quer cuddle
Juliet
e Juliet
quer apenas deixá- Romeo
cuddle
la.
Em C ++, Juliet
declararia Romeo
como um (amante), friend
mas não existem tais coisas em java.
Aqui estão as aulas e o truque:
Damas primeiro :
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?");
}
}
Portanto, o método Juliet.cuddle
é, public
mas você precisa Romeo.Love
chamá-lo. Ele usa isso Romeo.Love
como uma "segurança de assinatura" para garantir que apenas seja Romeo
possível chamar esse método e verifica se o amor é real para que o tempo de execução gere um NullPointerException
se for null
.
Agora meninos:
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);
}
}
A classe Romeo.Love
é pública, mas seu construtor é private
. Portanto, qualquer um pode vê-lo, mas apenas Romeo
pode construí-lo. Eu uso uma referência estática para Romeo.Love
que o que nunca é usado seja construído apenas uma vez e não afete a otimização.
Portanto, Romeo
pode cuddle
Juliet
e só ele pode, porque só ele pode construir e acesso de um Romeo.Love
exemplo, que é exigido por Juliet
que cuddle
ela (ou então ela vai golpeá-lo com um NullPointerException
).
Romeo
's Love
para Julia
a eternidade, alterando o love
campo para ser final
;-).
Os designers de Java rejeitaram explicitamente a idéia de amigo, pois funciona em C ++. Você coloca seus "amigos" no mesmo pacote. A segurança privada, protegida e empacotada é imposta como parte do design do idioma.
James Gosling queria que o Java fosse C ++ sem os erros. Acredito que ele sentiu que esse amigo foi um erro porque viola os princípios de POO. Os pacotes fornecem uma maneira razoável de organizar componentes sem ser muito purista em relação ao POO.
NR apontou que você poderia trapacear usando a reflexão, mas mesmo isso só funciona se você não estiver usando o SecurityManager. Se você ativar a segurança padrão Java, não poderá enganar-se com reflexão, a menos que escreva uma política de segurança para permitir especificamente.
friend
violava a OOP (em particular, mais do que o acesso a pacotes), ele realmente não a entendeu (inteiramente possível, muitas pessoas entendem mal).
O conceito de 'amigo' é útil em Java, por exemplo, para separar uma API de sua implementação. É comum que as classes de implementação precisem acessar as classes internas da API, mas elas não devem ser expostas aos clientes da API. Isso pode ser alcançado usando o padrão 'Friend Accessor', conforme detalhado abaixo:
A classe exposta através da 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();
}
}
}
A classe que fornece a funcionalidade 'friend':
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);
}
Exemplo de acesso de uma classe no pacote de implementação 'friend':
package impl;
public final class FriendlyAccessExample {
public static void main(String[] args) {
Accessor accessor = Accessor.getInstance();
Exposed exposed = accessor.createExposed();
accessor.sayHello(exposed);
}
}
Existem duas soluções para sua pergunta que não envolvem manter todas as classes no mesmo pacote.
A primeira é usar o padrão Friend Accessor / Friend Package descrito em (Practical API Design, Tulach 2008).
O segundo é usar o OSGi. Há um artigo aqui explicando como o OSGi realiza isso.
Tanto quanto eu sei, não é possível.
Talvez você possa nos dar mais alguns detalhes sobre o seu design. Perguntas como essas provavelmente são o resultado de falhas de design.
Apenas considere
A resposta da eirikma é fácil e excelente. Posso acrescentar mais uma coisa: em vez de ter um método acessível ao público, getFriend () para obter um amigo que não pode ser usado, você pode dar um passo adiante e impedir que ele seja aceito sem um token: getFriend (Service.FriendToken). Esse FriendToken seria uma classe pública interna com um construtor privado, para que apenas o Serviço pudesse instanciar um.
Aqui está um exemplo claro de caso de uso com uma Friend
classe reutilizável . O benefício desse mecanismo é a simplicidade de uso. Talvez seja bom para dar mais acesso às classes de teste de unidade que o restante do aplicativo.
Para começar, aqui está um exemplo de como usar a 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;
}
}
Em outro pacote, você pode fazer isso:
public class Other {
private final Friend friend = new Friend(this);
public void test() {
String s = new Owner().getMember(friend);
System.out.println(s);
}
}
A Friend
classe é a seguinte.
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);
}
}
No entanto, o problema é que ele pode ser abusado da seguinte maneira:
public class Abuser {
public void doBadThings() {
Friend badFriend = new Friend(new Other());
String s = new Owner().getMember(badFriend);
System.out.println(s);
}
}
Agora, pode ser verdade que a Other
classe não tenha nenhum construtor público, tornando Abuser
impossível o código acima . No entanto, se sua classe não tem um construtor público, então provavelmente é aconselhável duplicar a classe amigo de uma classe interna. Tome esta Other2
classe como um exemplo:
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 então a Owner2
classe seria assim:
public class Owner2 {
private final String member = "value";
public String getMember(final Other2.Friend friend) {
friend.check();
return member;
}
}
Observe que a Other2.Friend
classe tem um construtor privado, tornando assim uma maneira muito mais segura de fazê-lo.
A solução fornecida talvez não fosse a mais simples. Outra abordagem é baseada na mesma idéia do C ++: membros privados não são acessíveis fora do pacote / escopo privado, exceto para uma classe específica em que o proprietário faz um amigo.
A classe que precisa de acesso de amigo a um membro deve criar uma "classe de amigo" abstrata pública interna à qual a classe que possui as propriedades ocultas pode exportar o acesso, retornando uma subclasse que implementa os métodos de implementação de acesso. O método "API" da classe de amigo pode ser privado, portanto, não é acessível fora da classe que precisa de acesso de amigo. Sua única declaração é uma chamada para um membro protegido abstrato que a classe exportadora implementa.
Aqui está o código:
Primeiro, o teste que verifica se isso 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());
}
}
Em seguida, o Serviço que precisa de acesso de amigo a um membro privado do pacote da Entidade:
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: a classe Entity que fornece acesso amigável a um membro privado do pacote apenas para a 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();
}
};
}
}
Ok, devo admitir que é um pouco mais longo que "friend service :: Service;" mas pode ser possível reduzi-lo, mantendo a verificação em tempo de compilação usando anotações.
Em Java, é possível ter uma "amizade relacionada ao pacote". Isso pode ser útil para testes de unidade. Se você não especificar privado / público / protegido na frente de um método, ele será "amigo no pacote". Uma classe no mesmo pacote poderá acessá-la, mas será privada fora da classe.
Essa regra nem sempre é conhecida e é uma boa aproximação de uma palavra-chave "amigo" em C ++. Acho um bom substituto.
Eu acho que as classes de amigo em C ++ são como o conceito de classe interna em Java. Usando classes internas, você pode realmente definir uma classe anexa e uma classe fechada. A classe fechada tem acesso total aos membros públicos e privados da classe envolvente. consulte o seguinte link: http://docs.oracle.com/javase/tutorial/java/javaOO/nested.html
Eu acho que a abordagem de usar o padrão de acessador de amigos é muito complicada. Eu tive que enfrentar o mesmo problema e resolvi usando o bom e antigo construtor de cópias, conhecido em C ++, em 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;
}
}
No seu aplicativo, você pode escrever o seguinte 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;
}
}
}
A vantagem desse método é que apenas seu aplicativo tem acesso aos dados protegidos. Não é exatamente uma substituição da palavra-chave friend. Mas acho que é bastante adequado quando você escreve bibliotecas personalizadas e precisa acessar dados protegidos.
Sempre que você precisar lidar com instâncias do ProtectedContainer, envolva seu ProtectedAccessor e obtenha acesso.
Também funciona com métodos protegidos. Você os define protegidos em sua API. Posteriormente em seu aplicativo, você escreve uma classe de invólucro particular e expõe o método protegido como público. É isso aí.
ProtectedContainer
pode ser subclassificado fora do pacote!
Se você deseja acessar métodos protegidos, pode criar uma subclasse da classe que deseja usar que expõe os métodos que você deseja usar como público (ou interno para o namespace para ser mais seguro) e ter uma instância dessa classe em sua classe. (use-o como um proxy).
No que diz respeito aos métodos privados (eu acho), você está sem sorte.
Concordo que, na maioria dos casos, a palavra-chave friend é desnecessária.
E, finalmente, se for realmente necessário, existe o padrão de acessador de amigo mencionado nas outras respostas.
Não está usando uma palavra-chave ou algo assim.
Você poderia "trapacear" usando reflexão etc., mas eu não recomendaria "trapacear".
Um método que encontrei para resolver esse problema é criar um objeto acessador, assim:
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();
}
}
O primeiro código a chamar getAccessor()
"reivindica propriedade" do acessador. Geralmente, esse é o código que cria o objeto.
Foo bar = new Foo(); //This object is safe to share.
FooAccessor barAccessor = bar.getAccessor(); //This one is not.
Isso também tem uma vantagem sobre o mecanismo de amigo do C ++, porque permite limitar o acesso em um nível por instância , em oposição a um nível por classe . Controlando a referência do acessador, você controla o acesso ao objeto. Você também pode criar vários acessadores e conceder acesso diferente a cada um, o que permite um controle refinado sobre qual código pode acessar o que:
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();
}
}
Por fim, se quiser que as coisas sejam um pouco mais organizadas, você pode criar um objeto de referência, que mantém tudo junto. Isso permite reivindicar todos os acessadores com uma chamada de método, além de mantê-los juntos com sua instância vinculada. Depois de ter a referência, você pode passar os acessadores para o código que precisa:
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();
}
}
Depois de muita pancada na cabeça (não do tipo bom), essa foi minha solução final, e eu gostei muito. É flexível, simples de usar e permite um controle muito bom sobre o acesso à classe. (O acesso somente com referência é muito útil.) Se você usar protegido em vez de privado para os acessadores / referências, as subclasses de Foo podem até retornar referências estendidas de getReference
. Também não requer reflexão, portanto pode ser usado em qualquer ambiente.
Prefiro delegação ou composição ou classe de fábrica (dependendo do problema que resulta nesse problema) para evitar torná-lo uma classe pública.
Se for um problema "interface / classes de implementação em pacotes diferentes", eu usaria uma classe de fábrica pública que estaria no mesmo pacote que o pacote impl e impediria a exposição da classe impl.
Se for um problema "Eu odeio tornar esta classe / método público apenas para fornecer essa funcionalidade a alguma outra classe em um pacote diferente", eu usaria uma classe delegada pública no mesmo pacote e exporia apenas essa parte da funcionalidade necessário pela classe "de fora".
Algumas dessas decisões são orientadas pela arquitetura de carregamento de classe do servidor de destino (pacote configurável OSGi, WAR / EAR etc.), convenções de implementação e nomeação de pacotes. Por exemplo, a solução proposta acima, o padrão 'Friend Accessor' é inteligente para aplicativos java normais. Gostaria de saber se fica complicado implementá-lo no OSGi devido à diferença no estilo de carregamento de classe.
Eu não sei se é de alguma utilidade para alguém, mas eu lidei com isso da seguinte maneira:
Eu criei uma interface (AdminRights).
Toda classe que deve ser capaz de chamar essas funções deve implementar AdminRights.
Então eu criei uma função HasAdminRights da seguinte maneira:
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;
}
}
Certa vez, vi uma solução baseada em reflexão que fazia "verificação de amigos" em tempo de execução usando reflexão e verificação da pilha de chamadas para ver se a classe que chamava o método tinha permissão para fazê-lo. Sendo uma verificação de tempo de execução, tem a desvantagem óbvia.