Nascondere nomi Java: The Hard Way


96

Ho un problema con il nascondere il nome che è estremamente difficile da risolvere. Ecco una versione semplificata che spiega il problema:

C'è una classe: org.A

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

Poi c'è una classe net.foo.X

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

E ora, ecco la classe problematica che eredita Ae vuole chiamarenet.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
    }
}

Come vedi, questo non è possibile. Non posso usare il nome semplice Xperché è nascosto da un tipo ereditato. Non posso utilizzare il nome completo net.foo.X, perché netè nascosto da un campo ereditato.

Solo la classe Bè nella mia base di codice; le classi net.foo.Xe org.Asono classi di libreria, quindi non posso modificarle!

La mia unica soluzione è questa: potrei chiamare un'altra classe che a sua volta chiama X.doSomething(); ma questa classe esisterebbe solo a causa del nome clash, che sembra molto disordinato! Non c'è soluzione in cui posso chiamare direttamente X.doSomething()da B.doSomething()?

In un linguaggio che consente di specificare lo spazio dei nomi globale, ad esempio global::in C # o ::in C ++, potrei semplicemente aggiungere netquesto prefisso globale, ma Java non lo consente.


Qualche soluzione rapida potrebbe essere questa: public void help(net.foo.X x) { x.doSomething(); }e chiamare conhelp(null);
Absurd-Mind

@ Absurd-Mind: Beh, chiamare un metodo statico tramite un oggetto inesistente sembra ancora più complicato della mia soluzione :). Riceverò anche un avviso del compilatore. Ma è vero, questa sarebbe un'altra soluzione hacky oltre alla mia.
gexicide

@ JamesB: Questo instanzerebbe la X sbagliata! net.foo.Xha il metodo, no org.A.X!
gexicide

3
Non si dispone di ereditare da A? L'eredità può essere così brutta, come hai scoperto ...
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 per atteggiamento di codice pulito. Ma per me sembra che questa sia una situazione in cui dovresti fare un compromesso. Fallo semplicemente e lancia un lungo e piacevole commento sul motivo per cui hai dovuto farlo (probabilmente con un collegamento a questa domanda).
sampathsris

Risposte:


84

Puoi eseguire il cast di un nullal tipo e quindi richiamare il metodo su quello (che funzionerà, poiché l'oggetto di destinazione non è coinvolto nel richiamo di metodi statici).

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

Questo ha i vantaggi di

  • essere privi di effetti collaterali (un problema con la creazione di istanze net.foo.X),
  • non richiedere la ridenominazione di nulla (quindi puoi dare il metodo nel Bnome che vuoi che abbia; ecco perché a import staticnon funzionerà nel tuo caso esatto),
  • non richiede l'introduzione della classe delegato (anche se potrebbe essere una buona idea ...), e
  • non richiede l'overhead o la complessità di lavorare con l'API di riflessione.

Lo svantaggio è che questo codice è davvero orribile! Per me, genera un avviso, e questa è una buona cosa in generale. Ma poiché sta aggirando un problema che altrimenti sarebbe del tutto impraticabile, l'aggiunta di un file

@SuppressWarnings("static-access")

in un punto di chiusura appropriato (minimo!) chiuderà il compilatore.


1
Perchè no? Dovresti solo fare la cosa giusta e rifattorizzare il codice.
Gimby

1
Le importazioni statiche non sono molto più chiare di questa soluzione e non funzionano in tutti i casi (sei fregato se c'è un doSomethingmetodo ovunque nella tua gerarchia), quindi sì la migliore soluzione.
Voo

@Voo Ammetto liberamente che in realtà sono andato e ho provato tutte le opzioni che altre persone avevano elencato come risposte avendo prima riprodotto esattamente quale fosse il problema originale, e sono rimasto sorpreso da quanto fosse difficile aggirare. La domanda originale chiude chiaramente tutte le altre opzioni più belle.
Donal Fellows

1
Vota per la creatività, però non lo farei mai nel codice di produzione. Credo che l'indirizzamento sia la risposta a questo problema (non è per tutti i problemi?).
ethanfar

Grazie Donal per questa soluzione. In realtà, questa soluzione evidenzia i punti in cui un "Tipo" può essere utilizzato e una variabile non può essere utilizzata. Quindi, in un "cast", il compilatore sceglierebbe il "Tipo" anche se esiste una variabile con lo stesso nome. Per altri casi così interessanti, consiglierei 2 libri "Java Pitfalls": books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM

38

Probabilmente il modo più semplice (non necessariamente il più semplice) per gestirlo sarebbe con una classe delegato:

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

e poi ...

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

Questo è un po 'prolisso, ma molto flessibile: puoi farlo in modo che si comporti come preferisci; inoltre funziona più o meno allo stesso modo sia con staticmetodi che con oggetti istanziati

Maggiori informazioni sugli oggetti delegato qui: http://en.wikipedia.org/wiki/Delegation_pattern


Probabilmente la soluzione più pulita fornita. Probabilmente lo userei se avessi quel problema.
LordOfThePigs

37

Puoi utilizzare un'importazione statica:

import static net.foo.X.doSomething;

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

Attenzione a questo Be Anon contenere metodi denominatidoSomething


Net.foo.X.doSomething non ha accesso solo ai pacchetti? Significa che non puoi accedervi dal pacchetto com.bar
JamesB

2
@ JamesB Sì, ma allora la domanda completa non avrebbe senso, poiché il metodo non sarebbe accessibile in alcun modo. Penso che questo sia un errore semplificando l'esempio
Absurd-Mind

1
Ma la domanda originale sembra voler attivamente utilizzare doSomethingcome nome del metodo in B...
Donal Fellows

3
@DonalFellows: hai ragione; questa soluzione non funziona se il mio codice dovesse rimanere come l'ho pubblicato. Fortunatamente, posso rinominare il metodo. Tuttavia, pensa a un caso in cui il mio metodo sovrascrive un altro metodo, quindi non posso rinominarlo. In questo caso, questa risposta non risolverebbe effettivamente il problema. Ma questo sarebbe anche più per coincidenza di quanto non sia già il mio problema, quindi forse non ci sarà mai nessun essere umano che incontrerà un simile problema con il triplo nome dello scontro :).
gexicide

4
Per renderlo chiaro per tutti gli altri: questa soluzione non funziona non appena ci si trova in un doSomethingpunto qualsiasi della gerarchia di ereditarietà (a causa del modo in cui vengono risolti gli obiettivi della chiamata al metodo). Quindi Knuth ti aiuta se vuoi chiamare un toStringmetodo. La soluzione proposta da Donal è quella che si trova nel libro Java Puzzlers di Bloch (credo di non averlo cercato), quindi possiamo considerarla davvero la risposta autorevole :-)
Voo

16

Il modo corretto di fare le cose sarebbe l'importazione statica, ma nello scenario peggiore in assoluto, POTRESTI costruire un'istanza della classe usando la riflessione se conosci il suo nome completo.

Java: newInstance di classe che non ha un costruttore predefinito

E quindi invoca il metodo sull'istanza.

Oppure, invoca il metodo stesso con la riflessione: invocando un metodo statico usando la riflessione

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

Naturalmente, queste sono ovviamente le ultime località.


2
Questa risposta è di gran lunga la più grande esagerazione fino ad ora - pollice in alto :)
gexicide

3
Dovresti ottenere un distintivo di completista per questa risposta; hai fornito la risposta a quel singolare povero che deve usare un'antica versione di Java e risolvere esattamente questo problema.
Gimby

In realtà non ho pensato al fatto che la static importfunzionalità fosse aggiunta solo in Java 1.5. Non invidio le persone che hanno bisogno di sviluppare per 1.4 o meno, ho dovuto farlo una volta ed è stato terribile!
EpicPandaForce

1
@ Gimby: Bene, c'è ancora la ((net.foo.X)null).doSomething()soluzione che funziona nel vecchio java. Ma se il tipo Acontiene anche un tipo interno net, ALLORA questa è l'unica risposta che rimane valida :).
gexicide

6

Non proprio LA risposta, ma potresti creare un'istanza di X e chiamare il metodo statico su di essa. Sarebbe un modo (sporco lo ammetto) per chiamare il tuo metodo.

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

3
Eseguire nullil cast sul tipo e quindi chiamare il metodo su di esso sarebbe meglio, poiché la creazione di un oggetto potrebbe avere effetti collaterali che non voglio.
gexicide

@gexicide L'idea del casting nullsembrerebbe essere uno dei modi più puliti per farlo. Ha bisogno @SuppressWarnings("static-access")di essere senza avvertimenti ...
Donal Fellows

Grazie per la precisione null, ma il casting mi nullha sempre spezzato il cuore.
Michael Laffargue

4

Non è necessario eseguire alcun cast o sopprimere strani avvisi o creare istanze ridondanti. Solo un trucco usando il fatto che puoi chiamare metodi statici della classe genitore tramite la sottoclasse. (Simile alla mia soluzione hacker qui .)

Basta creare una classe come questa

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

(Il costruttore privato in questa classe finale si assicura che nessuno possa creare accidentalmente un'istanza di questa classe.)

Quindi sei libero di chiamare X.doSomething()tramite esso:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }

Interessante, ancora un altro approccio. Tuttavia, la classe con il metodo statico è una classe di utilità finale.
gexicide

Sì, non puoi usare questo trucco in quel caso. Ancora un altro motivo per cui il metodo statico è un errore del linguaggio e l'approccio agli oggetti di Scala dovrebbe essere adottato.
billc.cn

Se hai intenzione di creare comunque un'altra classe, l'utilizzo di una classe delegato come descritto nella risposta blgt è probabilmente il migliore.
LordOfThePigs

@LordOfThePigs Ciò evita tuttavia la chiamata di metodo extra e il futuro carico di refactoring. La nuova classe serve fondamentalmente come alias.
billc.cn

2

E se provassi a ottenere lo spazio dei nomi gobal dato che tutti i file si trovano nella stessa cartella. ( 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...

         }
     }

Come funziona? AFAIK getGlobal()non è un metodo Java standard per i pacchetti ... (Penso che i pacchetti non possano avere alcun metodo in Java ...)
siegi

1

Questo è uno dei motivi per cui la composizione è preferibile all'eredità.

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

0

Userei il modello Strategia.

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

Ora hai anche disaccoppiato la tua dipendenza, quindi i tuoi unit test saranno più facili da scrivere.


Hai dimenticato di aggiungere un implements SomethingStrategynella XSomethingStrategydichiarazione della classe.
Ricardo Souza

@rcdmk Grazie. Dovrebbe essere risolto ora.
Erik Madsen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.