Pergunta

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?

Foi útil?

Solução 3

O conceito de 'amigo' é útil em Java, por exemplo, para separar uma API de sua implementação. É comum as classes de implementação precisam de acesso aos internos da classe API, mas não devem ser expostos aos clientes da API. Isso pode ser alcançado usando o padrão 'Acessador de amigos', conforme detalhado abaixo:

A aula 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 turma que fornece a funcionalidade de '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);
}

Exemplo de acesso de uma aula 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);
    }
}

Outras dicas

Aqui está um pequeno truque que eu uso em Java para replicar o mecanismo de amigo C ++.

Vamos dizer que eu tenho uma aula Romeo e outra aula Juliet. Eles estão em diferentes pacotes (família) por motivos de ódio.

Romeo quer cuddle Juliet e Juliet quer apenas deixar Romeo cuddle sua.

Em C ++, Juliet declararia Romeo Como um (amante) friend Mas não há essas 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?");
    }

}

Então o método Juliet.cuddle é public Mas você precisa de um Romeo.Love para chamá -lo. Ele usa isso Romeo.Love como uma "segurança de assinatura" para garantir que apenas Romeo pode chamar esse método e verifica se o amor é real para que o tempo de execução jogue 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úblico, mas seu construtor é private. Portanto, qualquer um pode ver, mas apenas Romeo pode construí -lo. Eu uso uma referência estática para que o Romeo.Love Isso nunca é usado apenas é construído uma vez e não afeta a otimização.

Portanto, Romeo posso cuddle Juliet e somente ele pode porque só ele pode construir e acessar um Romeo.Love instância, que é exigida por Juliet para cuddle ela (ou então ela vai dar um tapa em você com um NullPointerException).

Os designers de Java rejeitaram explicitamente a idéia de amigo, pois funciona no C ++. Você coloca seus "amigos" no mesmo pacote. A segurança privada, protegida e embalada é aplicada como parte do design do idioma.

James Gosling queria que Java fosse C ++ sem os erros. Eu acredito que ele sentiu que o amigo foi um erro porque viola os princípios do OOP. Os pacotes fornecem uma maneira razoável de organizar componentes sem serem muito puristas sobre o OOP.

A NR apontou que você poderia trapacear usando a reflexão, mas mesmo isso funciona apenas se você não estiver usando o SecurityManager. Se você ativar a segurança padrão Java, não poderá trair com reflexão, a menos que escreva a política de segurança para permitir especificamente.

Existem duas soluções para sua pergunta que não envolvem manter todas as classes no mesmo pacote.

O primeiro é usar o acessador de amizade/Pacote de amigos Padrão descrito em (PRÁTICO API Design, Tulach 2008).

O segundo é usar osgi. Há um artigo aqui Explicando como Osgi faz isso.

Perguntas relacionadas: 1, 2, e 3.

Até onde eu sei, não é possível.

Talvez você possa nos dar mais detalhes sobre o seu design. Perguntas como essas são provavelmente o resultado de falhas de design.

Apenas considere

  • Por que essas classes estão em pacotes diferentes, se estão tão intimamente relacionadas?
  • A para acessar membros particulares de B ou a operação deve ser movida para a Classe B e acionada por A?
  • Isso está realmente ligando ou está melhor no caminho para eventos?

A resposta de Eirikma é fácil e excelente. Eu posso acrescentar mais uma coisa: em vez de ter um método publicamente acessível, getfrifle () para obter um amigo que não pode ser usado, você pode dar um passo adiante e proibir que o amigo sem um token: getfriend (service.friendToken). Este FriendToken seria uma aula pública interna com um construtor privado, para que apenas o serviço pudesse instanciar um.

Aqui está um exemplo claro de caixa de uso com um reutilizável Friend classe. O benefício desse mecanismo é a simplicidade do uso. Talvez seja bom para dar mais acesso a classes de teste de unidade do que o restante do aplicativo.

Para começar, aqui está um exemplo de como usar o 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;
    }
}

Então, 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);
    }
}

o Friend A aula é 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 assim:

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 o Other A classe não tem nenhum construtores públicos, fazendo o acima Abuser código impossível. No entanto, se sua classe faz Tenha um construtor público, provavelmente é aconselhável duplicar a aula de amigos como uma classe interna. Pegue isso Other2 classe como 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 o Owner2 A classe seria assim:

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

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

Observe que o Other2.Friend A classe tem um construtor privado, tornando -o uma maneira muito mais segura de fazê -lo.

A solução apresentada talvez não tenha sido a mais simples.Outra abordagem é baseada na mesma ideia do C++:membros privados não são acessíveis fora do escopo package/private, exceto para uma classe específica da qual o proprietário se torna amigo.

A classe que precisa de acesso amigo a um membro deve criar uma "classe amiga" pública interna abstrata para a qual a classe que possui as propriedades ocultas possa exportar o acesso, retornando uma subclasse que implementa os métodos de implementação de acesso.O método "API" da classe amigo pode ser privado, portanto não é acessível fora da classe que precisa de acesso amigo.Sua única instrução é uma chamada para um membro abstrato protegido 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 pacote membro privado 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 do que "Serviço de Amigos :: Serviço;" Mas pode ser possível diminuí-lo enquanto mantém a verificação do tempo de compilação usando anotações.

Em Java, é possível ter uma "amizade relacionada ao pacote". Isso pode ser usado 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 aula no mesmo pacote poderá acessá -lo, mas será privado fora da classe.

Esta regra nem sempre é conhecida e é uma boa aproximação de uma palavra -chave "amigo" C ++. Acho que é um bom substituto.

Eu acho que as aulas de amigos no C ++ são como conceito de classe interna em Java. Usando as classes internas, você pode realmente definir uma classe anexante e uma em anexo. A classe fechada tem acesso total aos membros públicos e privados de sua classe anexando. Veja 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 amizade é 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 o seu aplicativo tem acesso aos dados protegidos. Não é exatamente uma substituição da palavra -chave do amigo. 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, você pode envolver o seu ProtectedAccessor e você obtém acesso.

Também funciona com métodos protegidos. Você os define protegidos em sua API. Mais tarde, no seu aplicativo, você escreve uma classe de wrapper privativa e expõe o método protegido como público. É isso.

Se você deseja acessar métodos protegidos, poderá criar uma subclasse da classe que deseja usar que expõe os métodos que deseja usar como público (ou interno ao espaço para nome para ser mais seguro) e ter uma instância dessa classe em sua classe (Use -o como proxy).

No que diz respeito aos métodos privados (acho), você está sem sorte.

Concordo que, na maioria dos casos, a palavra -chave do amigo é desnecessária.

  • Package-Private (também conhecido como padrão) é suficiente na maioria dos casos em que você tem um grupo de classes fortemente entrelaçadas
  • Para aulas de depuração que desejam acesso aos internos, geralmente faço o método privado e o acessio via reflexão. A velocidade geralmente não é importante aqui
  • Às vezes, você implementa um método que é um "hack" ou não está sujeito a alterações. Eu o torno público, mas uso @deprecated para indicar que você não deve confiar nesse método existente.

E, finalmente, se é realmente necessário, existe o padrão de acessador de amigos mencionado nas outras respostas.

Não usando uma palavra -chave ou mais.

Você poderia "trapacear" usando a reflexão etc., mas eu não recomendaria "trapaça".

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 ligar getAccessor() "Propriedade de reivindicações" 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 amigos de C ++, porque permite limitar o acesso a um por instance nível, em oposição a um por classe nível. Ao controlar a referência do acessador, você controla o acesso ao objeto. Você também pode criar vários acessores e fornecer acesso diferente a cada um, o que permite o controle de granulação fina sobre o que o 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 você quiser que as coisas sejam um pouco mais organizadas, você pode criar um objeto de referência, que mantém tudo junto. Isso permite que você reivindique todos os acessores com uma chamada de método, além de mantê -los juntos com a instância vinculada. Depois de ter a referência, você pode passar os acessores ao 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 cabeça (não do tipo bom), essa foi a minha solução final, e eu gosto muito disso. É flexível, simples de usar e permite um controle muito bom sobre o acesso de classe. (O apenas com referência O acesso é muito útil.) Se você usar protegido em vez de privado para os acessores/referências, as subclasses de Foo podem até retornar referências estendidas de getReference. Também não requer reflexão, por isso pode ser usada em qualquer ambiente.

A partir do Java 9, módulos podem ser usados ​​para tornar isso um problema em muitos casos.

Prefiro a delegação, a composição ou a classe de fábrica (dependendo da questão que resulta nesse problema) para evitar torná -la uma classe pública.

Se for um problema "interface/implementação em diferentes pacotes", eu usaria uma classe de fábrica pública que no mesmo pacote do pacote Impl e impediria a exposição da classe Impl.

Se for um "eu odeio tornar esse método/método público apenas para fornecer essa funcionalidade para outra classe em um pacote diferente", então eu usaria uma classe de delegado público no mesmo pacote e exporá apenas essa parte da funcionalidade Necessário da classe "Outsider".

Algumas dessas decisões são conduzidas pela arquitetura de carga de classe do servidor de destino (pacote Osgi, guerra/ouvido, etc.), convenções de implantação e nomeação de pacotes. Por exemplo, a solução proposta acima, o padrão de 'acessador de amizade' é inteligente para aplicativos Java normais. Gostaria de saber se é complicado implementá -lo no OSGI devido à diferença no estilo de carga de classe.

Certa vez, vi uma solução baseada em reflexão que "verificava" em tempo de execução usando a reflexão e verificando a pilha de chamadas para ver se a classe chamando o método foi autorizada a fazê -lo. Sendo uma verificação de tempo de execução, ele tem a desvantagem óbvia.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top