Ukrywanie nazw w Javie: trudny sposób

96

Mam problem z ukrywaniem nazwy, który jest niezwykle trudny do rozwiązania. Oto uproszczona wersja, która wyjaśnia problem:

Jest klasa: org.A

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

Potem jest klasa net.foo.X

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

A teraz mamy problematyczną klasę, która dziedziczy Ai chce zadzwonićnet.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
    }
}

Jak widzisz, nie jest to możliwe. Nie mogę użyć prostej nazwy, Xponieważ jest ona ukryta przez dziedziczony typ. Nie mogę użyć w pełni kwalifikowanej nazwy net.foo.X, ponieważ netjest ona ukryta przez dziedziczone pole.

Tylko klasa Bjest w mojej bazie kodu; klasy net.foo.Xi org.Asą klasami bibliotecznymi, więc nie mogę ich zmieniać!

Moje jedyne rozwiązanie wygląda następująco: mógłbym wywołać inną klasę, która z kolei wywołuje X.doSomething(); ale ta klasa istniałaby tylko z powodu konfliktu nazw, który wydaje się bardzo niechlujny! Czy istnieje żadne rozwiązanie, w którym można wywołać bezpośrednio X.doSomething()z B.doSomething()?

W języku, który umożliwia określenie globalnej przestrzeni nazw, np. global::W C # lub ::C ++, mógłbym po prostu poprzedzić netten globalny prefiks, ale Java na to nie pozwala.

geksycyd
źródło
Może to być jakiś quickfix: public void help(net.foo.X x) { x.doSomething(); }i zadzwoń zhelp(null);
Absurd-Mind
@ Absurd-Mind: Cóż, wywołanie metody statycznej za pomocą nieistniejącego obiektu wydaje się jeszcze bardziej bałaganiarskie niż moje rozwiązanie :). Otrzymam nawet ostrzeżenie kompilatora. Ale to prawda, byłoby to kolejne hackerskie rozwiązanie oprócz mojego.
gexicide,
@JamesB: To spowodowałoby zainstalowanie niewłaściwego X! net.foo.Xma metodę, nie org.A.X!
gexicide,
3
Czy mają dziedziczyć A? Dziedziczenie może być takie paskudne, jak odkryłeś…
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 za postawę czystego kodu. Ale wydaje mi się, że jest to sytuacja, w której należy zrobić kompromis. Po prostu zrób to i wrzuć miły długi komentarz o tym, dlaczego musiałeś to zrobić (prawdopodobnie z linkiem do tego pytania).
sampathsris

Odpowiedzi:

84

Możesz rzutować nullna typ, a następnie wywołać metodę na tym (co będzie działać, ponieważ obiekt docelowy nie jest zaangażowany w wywołanie metod statycznych).

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

Ma to zalety

  • brak efektów ubocznych (problem z tworzeniem instancji net.foo.X),
  • nie wymaga zmiany nazwy czegokolwiek (więc możesz podać metodę w Bnazwie, którą chcesz, aby miała; dlatego import staticw Twoim przypadku nie zadziała),
  • niewymagające wprowadzenia klasy delegatów (choć może to być dobry pomysł…) i
  • nie wymaga nakładów pracy ani złożoności pracy z interfejsem API odbicia.

Wadą jest to, że ten kod jest naprawdę okropny! Dla mnie generuje ostrzeżenie i ogólnie to dobrze. Ale ponieważ rozwiązuje problem, który w przeciwnym razie jest całkowicie niepraktyczny, dodanie pliku

@SuppressWarnings("static-access")

w odpowiednim (minimalnym!) punkcie zamykającym zamknie kompilator.

Donal Fellows
źródło
1
Dlaczego nie? Po prostu zrobiłbyś właściwą rzecz i zrefaktorował kod.
Gimby,
1
Import statyczny nie jest o wiele bardziej przejrzysty niż to rozwiązanie i nie działa we wszystkich przypadkach (masz spieprzone, jeśli istnieje doSomethingmetoda gdziekolwiek w twojej hierarchii), więc tak, najlepsze rozwiązanie.
Voo
@Voo Przyznaję, że faktycznie poszedłem i wypróbowałem wszystkie opcje, które inni ludzie wymieniali jako odpowiedzi, po pierwszym odtworzeniu dokładnie tego, co było pierwotnym problemem, i byłem zaskoczony, jak trudno było obejść ten problem. Oryginalne pytanie zgrabnie zamyka wszystkie inne, ładniejsze opcje.
Donal Fellows
1
Głosuj na kreatywność, ale nigdy nie zrobiłbym tego w kodzie produkcyjnym. Uważam, że niekierowanie jest odpowiedzią na ten problem (czy nie dotyczy wszystkich problemów?).
ethanfar
Dzięki Donalowi za to rozwiązanie. Właściwie to rozwiązanie podkreśla miejsca, w których można użyć „Typu”, a zmiennej nie można. Zatem w „rzutowaniu” kompilator wybrałby „Typ”, nawet jeśli istnieje zmienna o tej samej nazwie. W przypadku większej liczby takich interesujących przypadków polecam 2 książki „Java Pitfalls”: books.google.co.in/books/about/… books.google.co.in/books/about/ ...
RRM
38

Prawdopodobnie najprostszym (niekoniecznie najłatwiejszym) sposobem radzenia sobie z tym byłoby użycie klasy delegatów:

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

i wtedy ...

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

Jest to nieco rozwlekłe, ale bardzo elastyczne - możesz sprawić, by zachowywał się w dowolny sposób; ponadto działa w podobny sposób zarówno z staticmetodami, jak i obiektami, których instancje są tworzone

Więcej o obiektach delegatów tutaj: http://en.wikipedia.org/wiki/Delegation_pattern

blgt
źródło
Prawdopodobnie najczystsze dostępne rozwiązanie. Prawdopodobnie użyłbym tego, gdybym miał ten problem.
LordOfThePigs,
37

Możesz użyć importu statycznego:

import static net.foo.X.doSomething;

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

Uważaj na to Bi Anie używaj nazwanych metoddoSomething

Absurdalny umysł
źródło
Czy net.foo.X.doSomething nie ma tylko dostępu do pakietu? Oznacza to, że nie możesz uzyskać do niego dostępu z pakietu com.bar
JamesB,
2
@JamesB Tak, ale wtedy pełne pytanie nie miałoby sensu, ponieważ metoda nie byłaby w żaden sposób dostępna. Myślę, że to błąd podczas upraszczania przykładu
Absurd-Mind
1
Ale pierwotne pytanie wydaje się aktywnie używać doSomethingjako nazwy metody w B
Donal Fellows,
3
@DonalFellows: Masz rację; to rozwiązanie nie działa, jeśli mój kod musi pozostać taki, jaki opublikowałem. Na szczęście mogę zmienić nazwę metody. Pomyśl jednak o przypadku, w którym moja metoda zastępuje inną metodę, wtedy nie mogę zmienić jej nazwy. W takim przypadku ta odpowiedź rzeczywiście nie rozwiązałaby problemu. Ale byłoby to jeszcze bardziej przypadkowe niż mój problem już jest, więc może nigdy nie będzie człowieka, który napotka taki problem z potrójnym zderzeniem nazw :).
gexicide,
4
Aby było to jasne dla wszystkich innych: To rozwiązanie nie działa, gdy tylko masz doSomethinggdziekolwiek w hierarchii dziedziczenia (ze względu na sposób rozwiązywania celów wywołania metod). Więc Knuth pomoże ci, jeśli chcesz wywołać toStringmetodę. Rozwiązanie zaproponowane przez Donala to to, które znajduje się w książce Blocha o Java Puzzlers (myślę, że tego nie sprawdziłem), więc możemy uznać to za autorytatywną odpowiedź :-)
Voo
16

Właściwym sposobem wykonywania tego zadania byłby import statyczny, ale w absolutnie najgorszym przypadku MOŻESZ skonstruować instancję klasy przy użyciu funkcji Refleksja, jeśli znasz jej w pełni kwalifikowaną nazwę.

Java: newInstance klasy, która nie ma domyślnego konstruktora

Następnie wywołaj metodę w instancji.

Lub po prostu wywołaj samą metodę z refleksją: wywołanie metody statycznej przy użyciu odbicia

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

Oczywiście są to oczywiście ostateczności.

EpicPandaForce
źródło
2
Ta odpowiedź jest jak dotąd największą
przesadą
3
Za tę odpowiedź powinieneś otrzymać odznakę ukończenia; podałeś odpowiedź dla tego wyjątkowego biedaka, który musi używać starożytnej wersji Javy i dokładnie rozwiązać ten problem.
Gimby,
Właściwie nie myślałem o tym, że static importfunkcja została dodana tylko w Java 1.5. Nie zazdroszczę ludziom, którzy muszą rozwijać się do 1.4 lub poniżej, kiedyś musiałem i było okropnie!
EpicPandaForce
1
@Gimby: Cóż, nadal istnieje ((net.foo.X)null).doSomething()rozwiązanie, które działa w starej Javie. Ale jeśli typ Azawiera nawet typ wewnętrzny net, WTEDY jest to jedyna odpowiedź, która pozostaje ważna :).
gexicide,
6

Nie do końca jest to odpowiedź, ale możesz stworzyć instancję X i wywołać na niej metodę statyczną. To byłby sposób (przyznaję nieprzyzwoity) na nazwanie twojej metody.

(new net.foo.X()).doSomething();
Michael Laffargue
źródło
3
Rzutowanie nullna typ, a następnie wywołanie metody byłoby lepsze, ponieważ tworzenie obiektu może mieć efekty uboczne, których nie chcę.
gexicide,
@gexicide Ten pomysł na casting nullwydaje się być jednym z czystszych sposobów na zrobienie tego. Musi @SuppressWarnings("static-access")być wolny od ostrzeżeń…
Donal Fellows
Dzięki za precyzję null, ale rzucanie nullzawsze łamało mi serce.
Michael Laffargue,
4

Nie ma potrzeby rzucania ani tłumienia dziwnych ostrzeżeń ani tworzenia zbędnych instancji. Tylko sztuczka wykorzystująca fakt, że możesz wywołać statyczne metody klasy nadrzędnej za pośrednictwem podklasy. (Podobne do mojego hackerskiego rozwiązania tutaj .)

Po prostu stwórz taką klasę

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

(Prywatny konstruktor w tej ostatniej klasie zapewnia, że ​​nikt nie może przypadkowo utworzyć wystąpienia tej klasy).

Następnie możesz zadzwonić X.doSomething()za jego pośrednictwem:

    public class B extends A {

        public void doSomething() {
            XX.doSomething();
        }
billc.cn
źródło
Ciekawe, ale inne podejście. Jednak klasa z metodą statyczną jest ostatnią klasą narzędziową.
gexicide,
Tak, w takim przypadku nie można użyć tej sztuczki. Jeszcze jeden powód, dla którego metoda statyczna jest błędem języka i należy przyjąć podejście obiektowe Scali.
billc.cn
Jeśli mimo wszystko masz zamiar utworzyć inną klasę, to prawdopodobnie najlepsze jest użycie klasy delegata, jak opisano w odpowiedzi blgt.
LordOfThePigs,
@LordOfThePigs Pozwala to jednak uniknąć dodatkowego wywołania metody i przyszłego obciążenia refaktoryzacji. Nowa klasa zasadniczo służy jako alias.
billc.cn
2

Co jeśli spróbujesz uzyskać przestrzeń nazw gobal, biorąc pod uwagę, że wszystkie pliki znajdują się w tym samym folderze. ( 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
źródło
Jak to działa? AFAIK getGlobal()nie jest standardową metodą Java dla pakietów ... (myślę, że pakiety nie mogą mieć żadnych metod w Javie ...)
siegi
1

Jest to jeden z powodów, dla których kompozycja jest lepsza niż dziedziczenie.

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
źródło
0

Użyłbym wzorca strategii.

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

Teraz oddzieliłeś również swoją zależność, więc testy jednostkowe będą łatwiejsze do napisania.

Erik Madsen
źródło
Zapomniałeś dodać znak implements SomethingStrategyw XSomethingStrategydeklaracji klasy.
Ricardo Souza
@rcdmk Dziękuję. Powinien zostać naprawiony teraz.
Erik Madsen