Masquage de nom Java: la manière difficile

96

J'ai un problème de masquage de nom qui est extrêmement difficile à résoudre. Voici une version simplifiée qui explique le problème:

Il y a une classe: org.A

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

Puis il y a une classe net.foo.X

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

Et maintenant, voici la classe problématique qui hérite Aet veut appelernet.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
    }
}

Comme vous le voyez, ce n'est pas possible. Je ne peux pas utiliser le nom simple Xcar il est masqué par un type hérité. Je ne peux pas utiliser le nom complet net.foo.X, car il netest masqué par un champ hérité.

Seule la classe Best dans ma base de code; les classes net.foo.Xet org.Asont des classes de bibliothèque, donc je ne peux pas les modifier!

Ma seule solution ressemble à ceci: je pourrais appeler une autre classe qui à son tour appelle X.doSomething(); mais cette classe n'existerait qu'à cause du nom clash, qui semble très désordonné! Yat - il pas de solution dans laquelle je peux appeler directement à X.doSomething()partir B.doSomething()?

Dans un langage qui permet de spécifier l'espace de noms global, par exemple, global::en C # ou ::en C ++, je pourrais simplement préfixer netavec ce préfixe global, mais Java ne le permet pas.

gexicide
la source
Un correctif rapide pourrait être ceci: public void help(net.foo.X x) { x.doSomething(); }et appelez avechelp(null);
Absurd-Mind
@ Absurd-Mind: Eh bien, appeler une méthode statique via un objet inexistant semble encore plus compliqué que ma solution :). Je recevrai même un avertissement du compilateur. Mais c'est vrai, ce serait une autre solution hacky en plus de la mienne.
gexicide
@JamesB: Cela instancierait le mauvais X! net.foo.Xa la méthode, non org.A.X!
gexicide
3
Avez - vous avez hériter de A? L'héritage peut être si méchant, comme vous l'avez trouvé…
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 messy+1 pour l'attitude du code propre. Mais pour moi, il semble que ce soit une situation dans laquelle vous devriez faire un compromis. Faites-le simplement et jetez un long commentaire sur les raisons pour lesquelles vous deviez le faire (probablement avec un lien vers cette question).
sampathsris

Réponses:

84

Vous pouvez convertir a nullen type, puis appeler la méthode sur celui-ci (ce qui fonctionnera, car l'objet cible n'est pas impliqué dans l'appel des méthodes statiques).

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

Cela a les avantages de

  • être sans effet secondaire (problème d'instanciation net.foo.X),
  • ne nécessitant pas de renommer quoi que ce soit (vous pouvez donc donner à la méthode Ble nom que vous voulez; c'est pourquoi a import staticne fonctionnera pas dans votre cas exact),
  • ne nécessitant pas l'introduction de classe de délégué (bien que cela puisse être une bonne idée…), et
  • ne nécessitant pas la surcharge ou la complexité de travailler avec l'API de réflexion.

L'inconvénient est que ce code est vraiment horrible! Pour moi, cela génère un avertissement, et c'est une bonne chose en général. Mais comme cela fonctionne autour d'un problème qui est autrement tout à fait irréalisable, l'ajout d'un

@SuppressWarnings("static-access")

à un point englobant approprié (minimal!) fermera le compilateur.

Boursiers Donal
la source
1
Pourquoi pas? Vous feriez juste la bonne chose et refactoriser le code.
Gimby le
1
Les importations statiques ne sont pas beaucoup plus claires que cette solution et ne fonctionnent pas dans tous les cas (vous êtes foutu s'il y a une doSomethingméthode n'importe où dans votre hiérarchie), alors oui, la meilleure solution.
Voo le
@Voo J'admets volontiers que j'ai essayé toutes les options que d'autres personnes avaient énumérées comme réponses après avoir d'abord reproduit exactement le problème d'origine, et j'ai été surpris de voir à quel point il était difficile de contourner. La question originale ferme parfaitement toutes les autres options plus agréables.
Donal Fellows
1
Votez pour la créativité, je ne ferais jamais cela dans le code de production. Je crois que l'indirection est la réponse à ce problème (n'est-ce pas pour tous les problèmes?).
ethanfar le
Merci Donal pour cette solution. En fait, cette solution met en évidence les endroits où un "Type" peut être utilisé et une variable ne peut pas être utilisée. Ainsi, dans un "cast", le compilateur choisirait le "Type" même s'il y a une variable avec le même nom. Pour des cas plus intéressants, je recommanderais 2 livres «Java Pitfalls»: books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM
38

Le moyen le plus simple (pas nécessairement le plus simple) de gérer cela serait probablement d'utiliser une classe déléguée:

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

puis ...

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

C'est quelque peu verbeux, mais très flexible - vous pouvez le faire se comporter comme vous le souhaitez; de plus, il fonctionne à peu près de la même manière avec les staticméthodes et les objets instanciés

Plus d'informations sur les objets délégués ici: http://en.wikipedia.org/wiki/Delegation_pattern

blgt
la source
Probablement la solution la plus propre fournie. J'utiliserais probablement ça si j'avais ce problème.
LordOfThePigs
37

Vous pouvez utiliser une importation statique:

import static net.foo.X.doSomething;

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

Attention à cela Bet Ane contiennent pas de méthodes nomméesdoSomething

Esprit absurde
la source
Net.foo.X.doSomething n'a-t-il pas uniquement accès aux packages? Cela signifie que vous ne pouvez pas y accéder à partir du package com.bar
JamesB
2
@JamesB Oui, mais alors la question complète n'aurait pas de sens, puisque la méthode ne serait en aucun cas accessible. Je pense que c'est une erreur en simplifiant l'exemple
Absurd-Mind
1
Mais la question originale semble vouloir activement utiliser doSomethingcomme nom de la méthode en B
Donal Fellows
3
@DonalFellows: Vous avez raison; cette solution ne fonctionne pas si mon code devait rester tel que je l'ai publié. Heureusement, je peux renommer la méthode. Cependant, pensez à un cas où ma méthode remplace une autre méthode, alors je ne peux pas la renommer. Dans ce cas, cette réponse ne résoudrait en effet pas le problème. Mais ce serait encore plus une coïncidence que mon problème ne l'est déjà, alors peut-être qu'il n'y aura jamais d'humain qui rencontrera un tel problème avec un conflit de trois noms :).
gexicide
4
Pour être clair pour tout le monde: cette solution ne fonctionne pas dès que vous avez doSomethingn'importe où dans la hiérarchie d'héritage (en raison de la façon dont les cibles d'appel de méthode sont résolues). Alors Knuth vous aide si vous souhaitez appeler une toStringméthode. La solution proposée par Donal est celle qui se trouve dans le livre Java Puzzlers de Bloch (je pense que je ne l'ai pas recherchée), nous pouvons donc la considérer comme la réponse faisant autorité :-)
Voo
16

La bonne façon de faire les choses serait l'importation statique, mais dans le pire des cas, vous POUVEZ construire une instance de la classe en utilisant la réflexion si vous connaissez son nom complet.

Java: newInstance de classe qui n'a pas de constructeur par défaut

Et puis invoquez la méthode sur l'instance.

Ou, appelez simplement la méthode elle-même avec la réflexion: Appel d'une méthode statique à l'aide de la réflexion

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

Bien sûr, ce sont évidemment les derniers recours.

EpicPandaForce
la source
2
Cette réponse est de loin la plus grande exagération jusqu'à présent
Thumbs
3
Vous devriez obtenir un badge de complétion pour cette réponse; vous avez fourni la réponse pour cette personne pauvre singulière qui doit utiliser une ancienne version de Java et résoudre ce problème exact.
Gimby le
En fait, je n'ai pas pensé au fait que cette static importfonctionnalité était uniquement ajoutée Java 1.5. Je n'envie pas les gens qui ont besoin de se développer pour la version 1.4 ou inférieure, je devais le faire une fois et c'était terrible!
EpicPandaForce
1
@Gimby: Eh bien, il y a toujours la ((net.foo.X)null).doSomething()solution qui fonctionne dans l'ancien java. Mais si le type Acontient même un type interne net, ALORS c'est la seule réponse qui reste valide :).
gexicide
6

Pas vraiment LA réponse mais vous pouvez créer une instance de X et appeler la méthode statique dessus. Ce serait une façon (sale je l'admets) d'appeler votre méthode.

(new net.foo.X()).doSomething();
Michael Laffargue
la source
3
nullIl serait préférable de lancer le type puis d'appeler la méthode, car la création d'un objet peut avoir des effets secondaires que je ne veux pas.
gexicide le
@gexicide Cette idée de casting nullsemble être l'un des moyens les plus propres de le faire. Doit @SuppressWarnings("static-access")être sans avertissement…
Donal Fellows
Merci pour la précision null, mais le casting nulla toujours été un coup de cœur pour moi.
Michael Laffargue
4

Il n'est pas nécessaire de lancer ou de supprimer les avertissements étranges ou de créer une instance redondante. Juste une astuce utilisant le fait que vous pouvez appeler des méthodes statiques de classe parent via la sous-classe. (Similaire à ma solution hackish ici .)

Créez simplement une classe comme celle-ci

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

(Le constructeur privé de cette classe finale s'assure que personne ne peut créer accidentellement une instance de cette classe.)

Ensuite, vous êtes libre d'appeler X.doSomething()via:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }
billc.cn
la source
Intéressant, encore une autre approche. Cependant, la classe avec la méthode statique est une classe d'utilité finale.
gexicide
Ouais, je ne peux pas utiliser cette astuce dans ce cas. Encore une autre raison pour laquelle la méthode statique est un échec du langage et l'approche objet de Scala doit être adoptée.
billc.cn
Si vous allez quand même créer une autre classe, utiliser une classe déléguée comme décrit dans blgt answer est probablement la meilleure.
LordOfThePigs
@LordOfThePigs Cela évite cependant l'appel de méthode supplémentaire et la charge de refactoring future. La nouvelle classe sert essentiellement d'alias.
billc.cn
2

Que faire si vous essayez d'obtenir le gobalnamespace étant donné que tous les fichiers sont dans le même dossier. ( 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
la source
Comment cela marche-t-il? AFAIK getGlobal()n'est pas une méthode Java standard pour les packages ... (je pense que les packages ne peuvent avoir aucune méthode en Java ...)
siegi
1

C'est l'une des raisons pour lesquelles la composition est préférable à l'héritage.

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();
    }
}
émory
la source
0

J'utiliserais le modèle de stratégie.

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

Maintenant, vous avez également découplé votre dépendance, de sorte que vos tests unitaires seront plus faciles à écrire.

Erik Madsen
la source
Vous avez oublié d'ajouter un implements SomethingStrategysur la XSomethingStrategydéclaration de classe.
Ricardo Souza
@rcdmk Merci. Devrait être corrigé maintenant.
Erik Madsen