Ocultação de nomes Java: a maneira mais difícil

96

Tenho um problema com a ocultação de nomes que é extremamente difícil de resolver. Aqui está uma versão simplificada que explica o problema:

Existe uma classe: org.A

package org;
public class A{
     public class X{...}
     ...
     protected int net;
}

Então há uma aula net.foo.X

package net.foo;
public class X{
     public static void doSomething();
}

E agora, aqui está a classe problemática que herda Ae deseja chamarnet.foo.X.doSomething()

package com.bar;
class B extends A {

    public void doSomething(){
        net.foo.X.doSomething(); // doesn't work; package net is hidden by inherited field
        X.doSomething(); // doesn't work; type net.foo.X is hidden by inherited X
    }
}

Como você vê, isso não é possível. Não posso usar o nome simples Xporque está oculto por um tipo herdado. Não posso usar o nome totalmente qualificado net.foo.Xporque netestá oculto por um campo herdado.

Apenas a classe Bestá em minha base de código; as classes net.foo.Xe org.Asão classes da biblioteca, então não posso alterá-las!

Minha única solução é a seguinte: eu poderia chamar outra classe que, por sua vez, chamaria X.doSomething(); mas essa classe só existiria por causa do nome clash, que parece muito confuso! Será que não há solução em que eu posso chamar diretamente X.doSomething()a partir de B.doSomething()?

Em uma linguagem que permite especificar o namespace global, por exemplo, global::em C # ou ::em C ++, eu poderia simplesmente prefixar netcom este prefixo global, mas Java não permite isso.

gexicida
fonte
Uma correção rápida poderia ser esta: public void help(net.foo.X x) { x.doSomething(); }e ligue comhelp(null);
Absurd-Mind
@ Absurd-Mind: Bem, chamar um método estático por meio de um objeto inexistente parece ainda mais confuso do que a minha solução :). Vou até receber um aviso do compilador. Mas é verdade, essa seria outra solução hacky além da minha.
gexicídio
@JamesB: Isso instanciaria o X errado! net.foo.Xtem o método, não org.A.X!
gexicídio
3
Você tem que herdar de A? Herança pode ser tão desagradável, como você descobriu ...
Donal Fellows
2
I could call another class that in turn calls X.doSomething(); but this class would only exist because of the name clash, which seems very messy1 para atitude de código limpo. Mas, para mim, parece que essa é uma situação em que você deve fazer uma troca. Basta fazer isso e lançar um comentário longo e agradável sobre por que você teve que fazer isso (provavelmente com um link para esta pergunta).
sampathsris

Respostas:

84

Você pode converter um nullpara o tipo e, em seguida, invocar o método naquele (o que funcionará, já que o objeto de destino não está envolvido na invocação de métodos estáticos).

((net.foo.X) null).doSomething();

Isso tem os benefícios de

  • sendo livre de efeitos colaterais (um problema com a instanciação net.foo.X),
  • não requer renomeação de nada (para que você possa dar ao método Bo nome que deseja; é por isso que um import staticnão funcionará no seu caso),
  • não exigindo a introdução de classe de delegado (embora isso possa ser uma boa ideia ...), e
  • não exigindo a sobrecarga ou complexidade de trabalhar com a API de reflexão.

A desvantagem é que esse código é realmente horrível! Para mim, isso gera um aviso, e isso é bom em geral. Mas, uma vez que está contornando um problema que, de outra forma, seria totalmente impraticável, adicionar um

@SuppressWarnings("static-access")

em um ponto delimitador apropriado (mínimo!) irá desligar o compilador.

Donal Fellows
fonte
1
Por que não? Você simplesmente faria a coisa certa e refatoraria o código.
Gimby
1
As importações estáticas não são muito mais claras do que esta solução e não funcionam em todos os casos (você está ferrado se houver um doSomethingmétodo em qualquer lugar em sua hierarquia), então sim, a melhor solução.
Voo
@Voo Admito francamente que tentei todas as opções que outras pessoas listaram como respostas, tendo primeiro reproduzido exatamente qual era o problema original, e fiquei surpreso com a dificuldade de contornar o problema. A pergunta original fecha perfeitamente todas as outras opções mais agradáveis.
Donal Fellows
1
Vote pela criatividade, mas eu nunca faria isso em código de produção. Eu acredito que a indireção é a resposta para este problema (não é para todos os problemas?).
ethanfar
Obrigado Donal por esta solução. Na verdade, esta solução destaca os locais onde um "Tipo" pode ser usado e uma variável não pode ser usada. Assim, em um "cast", o compilador escolheria o "Tipo" mesmo que houvesse uma variável com o mesmo nome. Para mais casos interessantes, eu recomendaria 2 livros "Java Pitfalls": books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM
38

Provavelmente, a maneira mais simples (não necessariamente a mais fácil) de gerenciar isso seria com uma classe delegada:

import net.foo.X;
class C {
    static void doSomething() {
         X.doSomething();
    }
}

e depois ...

class B extends A {
    void doX(){
        C.doSomething();
    }
}

Isso é um tanto prolixo, mas muito flexível - você pode fazer com que ele se comporte da maneira que quiser; além disso, ele funciona quase da mesma maneira com staticmétodos e objetos instanciados

Mais sobre objetos delegados aqui: http://en.wikipedia.org/wiki/Delegation_pattern

blgt
fonte
Provavelmente a solução mais limpa fornecida. Eu provavelmente usaria isso se tivesse esse problema.
LordOfThePigs
37

Você pode usar uma importação estática:

import static net.foo.X.doSomething;

class B extends A {
    void doX(){
        doSomething();
    }
}

Cuidado com isso Be Anão contém métodos nomeadosdoSomething

Mente Absurda
fonte
O net.foo.X.doSomething não tem apenas acesso ao pacote? Significando que você não pode acessá-lo do pacote com.bar
JamesB
2
@JamesB Sim, mas aí a pergunta completa não faria sentido, pois o método não seria acessível de forma alguma. Acho que isso é um erro ao simplificar o exemplo
Absurd-Mind
1
Mas a pergunta original parece querer ativamente usar doSomethingcomo o nome do método em B...
Donal Fellows
3
@DonalFellows: Você está certo; esta solução não funcionará se meu código tiver que permanecer como eu postei. Felizmente, posso renomear o método. No entanto, pense em um caso em que meu método substitui outro método, então não posso renomeá-lo. Nesse caso, essa resposta não resolveria o problema de fato. Mas isso seria ainda mais coincidentemente do que o meu problema já é, então talvez nunca existirá nenhum ser humano que encontrará tal problema com conflito de nomes triplo :).
gexicídio
4
Para deixar claro para todos os outros: Esta solução não funciona assim que você doSomethingatinge qualquer lugar na hierarquia de herança (devido a como os destinos de chamada de método são resolvidos). Então o Knuth o ajudará se você quiser chamar um toStringmétodo. A solução proposta por Donal é a que está no livro Java Puzzlers de Bloch (eu acho, não a procurei), então podemos considerá-la a resposta oficial realmente :-)
Voo
16

A maneira correta de fazer as coisas seria a importação estática, mas no pior cenário absoluto, você PODERIA construir uma instância da classe usando reflexão se souber seu nome totalmente qualificado.

Java: newInstance de classe que não possui construtor padrão

E então invoque o método na instância.

Ou apenas invoque o próprio método com reflexão: Invocando um método estático usando reflexão

Class<?> clazz = Class.forName("net.foo.X");
Method method = clazz.getMethod("doSomething");
Object o = method.invoke(null);

Claro, esses são obviamente os últimos recursos.

EpicPandaForce
fonte
2
Esta resposta é de longe o maior exagero até agora
gosto
3
Você deve obter um distintivo de completista para esta resposta; você forneceu a resposta para aquele pobre pobre que tem que usar uma versão antiga de Java e resolver esse problema exato.
Gimby
Na verdade, não pensei no fato de que esse static importrecurso foi adicionado apenas em Java 1.5. Não invejo as pessoas que precisam desenvolver para 1.4 ou inferior, tive que fazer uma vez e foi terrível!
EpicPandaForce
1
@Gimby: Bem, ainda existe a ((net.foo.X)null).doSomething()solução que funciona no Java antigo. Mas se o tipo Aainda contém um tipo interno net, ENTÃO esta é a única resposta que permanece válida :).
gexicídio
6

Não é realmente A resposta, mas você pode criar uma instância de X e chamar o método estático nela. Essa seria uma forma (suja, admito) de chamar o seu método.

(new net.foo.X()).doSomething();
Michael Laffargue
fonte
3
Converter nullpara o tipo e, em seguida, chamar o método nele seria melhor, já que criar um objeto pode ter efeitos colaterais que eu não desejo.
gexicídio
@gexicide Essa ideia de casting nullparece ser uma das maneiras mais limpas de fazer isso. Precisa @SuppressWarnings("static-access")ser livre de avisos ...
Donal Fellows
Obrigado pela precisão sobre null, mas o elenco nullsempre foi uma quebra de coração para mim.
Michael Laffargue
4

Não há necessidade de fazer qualquer elenco ou suprimir quaisquer avisos estranhos ou criar qualquer instância redundante. Apenas um truque usando o fato de que você pode chamar métodos estáticos da classe pai por meio da subclasse. (Semelhante à minha solução hackeada aqui .)

Basta criar uma classe como esta

public final class XX extends X {
    private XX(){
    }
}

(O construtor privado nesta classe final garante que ninguém possa criar acidentalmente uma instância desta classe.)

Então você está livre para ligar X.doSomething()por meio dele:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }
billc.cn
fonte
Interessante, ainda outra abordagem. No entanto, a classe com o método estático é uma classe de utilitário final.
gexicídio
Sim, não posso usar esse truque nesse caso. Mais uma razão pela qual o método estático é uma falha de linguagem e a abordagem de objeto do Scala deve ser adotada.
billc.cn
Se você vai criar outra classe de qualquer maneira, usar uma classe delegada conforme descrito em blgt answer é provavelmente o melhor.
LordOfThePigs
@LordOfThePigs Isso evita a chamada de método extra e a carga de refatoração futura. A nova classe serve basicamente como um apelido.
billc.cn
2

E se você tentar obter o gobalnamespace, já que todos os arquivos estão na mesma pasta. ( http://www.beanshell.org/javadoc/bsh/class-use/NameSpace.html )

    package com.bar;
      class B extends A {

       public void doSomething(){
         com.bar.getGlobal().net.foo.X.doSomething(); // drill down from the top...

         }
     }
Vectoria
fonte
Como é que isso funciona? AFAIK getGlobal()não é um método Java padrão para pacotes ... (acho que os pacotes não podem ter nenhum método em Java ...)
siegi
1

Essa é uma das razões pelas quais a composição é preferível à herança.

package com.bar;
import java.util.concurrent.Callable;
public class C implements Callable<org.A>
{
    private class B extends org.A{
    public void doSomething(){
        C.this.doSomething();
    }
    }

    private void doSomething(){
    net.foo.X.doSomething();
    }

    public org.A call(){
    return new B();
    }
}
emory
fonte
0

Eu usaria o padrão de estratégia.

public interface SomethingStrategy {

   void doSomething();
}

public class XSomethingStrategy implements SomethingStrategy {

    import net.foo.X;

    @Override
    void doSomething(){
        X.doSomething();
    }
}

class B extends A {

    private final SomethingStrategy strategy;

    public B(final SomethingStrategy strategy){
       this.strategy = strategy;
    }

    public void doSomething(){

        strategy.doSomething();
    }
}

Agora você também desacoplou sua dependência, para que seus testes de unidade sejam mais fáceis de escrever.

Erik Madsen
fonte
Você se esqueceu de adicionar um implements SomethingStrategyna XSomethingStrategydeclaração da classe.
Ricardo Souza
@rcdmk Obrigado. Deve ser consertado agora.
Erik Madsen