Приховування імені Java: важкий шлях


96

У мене проблема з приховуванням імен, яку надзвичайно важко вирішити. Ось спрощена версія, яка пояснює проблему:

Є клас: org.A

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

Тоді є клас net.foo.X

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

А тепер ось проблемний клас, який успадковує Aі хоче зателефонувати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
    }
}

Як бачите, це неможливо. Я не можу використовувати просте ім’я, Xоскільки воно приховане за успадкованим типом. Я не можу використовувати повністю кваліфіковане ім’я net.foo.X, оскільки netвоно приховане успадкованим полем.

BУ моїй кодовій базі лише клас ; класи net.foo.Xі org.Aє бібліотечними класами, тому я не можу їх змінювати!

Моє єдине рішення виглядає так: я міг би зателефонувати іншому класу, який, у свою чергу, дзвонить X.doSomething(); але цей клас існував би лише через зіткнення імені, яке здається дуже безладним! Чи немає розчин , в якому я можу напряму подзвонити X.doSomething()з B.doSomething()?

У мові, яка дозволяє вказати глобальний простір імен, наприклад, global::в C # або ::в C ++, я міг би просто вказати префікс netіз цим глобальним префіксом, але Java цього не дозволяє.


Деякі швидкі виправлення можуть бути таким: public void help(net.foo.X x) { x.doSomething(); }і зателефонувати зhelp(null);
Абсурд-розум

@ Absurd-Mind: Ну, виклик статичного методу через неіснуючий об'єкт здається ще гіршим, ніж моє рішення :). Я навіть отримаю попередження компілятора. Але правда, це було б ще одним хакерським рішенням на додаток до мого.
гексицид

@JamesB: Це призведе до помилки неправильного X! net.foo.Xмає метод, ні org.A.X!
gexicide

3
Чи є у успадкувати A? Спадщина може бути такою неприємною, як ви виявили ...
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 за ставлення до чистого коду. Але для мене здається, що це така ситуація, яку ви повинні зробити компромісом. Просто зробіть це та додайте приємний довгий коментар про те, чому вам це довелося зробити (можливо, із посиланням на це питання).
sampathsris

Відповіді:


84

Ви можете привести nullтип до типу, а потім викликати метод на цьому (що буде працювати, оскільки цільовий об'єкт не бере участь у виклику статичних методів).

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

У цьому є переваги

  • відсутність побічних ефектів (проблема з створенням екземплярів net.foo.X),
  • не вимагає перейменування чого-небудь (щоб ви могли вказати метод Bна ім’я, яке хочете, щоб воно мало; ось чому a import staticне працюватиме у вашому конкретному випадку),
  • не вимагає введення класу делегатів (хоча це може бути непоганою ідеєю ...), і
  • не вимагаючи накладних витрат або складності роботи з API відображення.

Недоліком є ​​те, що цей код справді жахливий! Для мене це генерує попередження, і це взагалі гарна річ. Але оскільки це вирішує проблему, яка в іншому випадку є абсолютно недоцільною, додавши a

@SuppressWarnings("static-access")

у відповідній (мінімальній!) точці, що охоплює, компілятор закриє.


1
Чому ні? Ви просто вчинили б правильно і переробили код.
Гімбі,

1
Статичне імпортування не набагато зрозуміліше, ніж це рішення, і воно працює не у всіх випадках (вас не в курсі, якщо в doSomethingбудь-якій точці вашої ієрархії є якийсь метод), так що найкраще рішення.
Voo

@Voo Я вільно визнаю, що насправді пішов і спробував усі варіанти, які інші люди перерахували як відповіді, відтворивши спочатку саме те, що було первісною проблемою, і я здивувався, як важко було обійти. Оригінальне питання акуратно закриває всі інші, приємніші варіанти.
Donal Fellows

1
Голосуйте за креативність, проте я ніколи б не робив цього у виробничому коді. Я вважаю, що опосередкованість є відповіддю на цю проблему (чи не на всі проблеми?).
ethanfar

Дякую Доналу за це рішення. Власне, це рішення висвітлює місця, де можна використовувати "Тип", а змінну не можна. Отже, у "приводі" компілятор вибирає "Тип", навіть якщо є змінна з тим самим іменем. Для інших таких цікавих випадків я б рекомендував 2 книги "Підводні камені Java": books.google.co.in/books/about/… books.google.co.in/books/about/…
RRM

38

Ймовірно, найпростіший (не обов'язково найпростіший) спосіб управління цим був би за допомогою класу-делегата:

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

і потім ...

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

Це дещо багатослівно, але дуже гнучко - ви можете змусити його поводитись як завгодно; плюс це працює приблизно однаково як з staticметодами, так і з екземплярами об'єктів

Детальніше про об'єкти делегатів тут: http://en.wikipedia.org/wiki/Delegation_pattern


Мабуть, найчистіше рішення. Я б, мабуть, скористався цим, якби мав цю проблему.
LordOfThePigs

37

Ви можете використовувати статичний імпорт:

import static net.foo.X.doSomething;

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

Слідкуйте за цим Bі Aне містять названих методівdoSomething


Чи не net.foo.X.doSomething має лише доступ до пакетів? Значення ви не може отримати доступ до нього з пакета com.bar
JamesB

2
@JamesB Так, але тоді повне запитання не мало б сенсу, оскільки метод не міг би бути доступним будь-яким чином. Думаю, це помилка під час спрощення прикладу
Абсурд-розум

1
Але оригінальне питання, схоже, активно хоче використовувати doSomethingяк назву методу в B...
Donal Fellows

3
@DonalFellows: Ви праві; це рішення не працює, якщо мій код повинен залишатися таким, як я розмістив. На щастя, я можу перейменувати метод. Однак, згадайте випадок, коли мій метод замінює інший метод, тоді я не можу його перейменувати. У цьому випадку ця відповідь справді не вирішить проблему. Але це було б навіть більш випадково, ніж моя проблема вже є, тому, можливо, ніколи не буде жодної людини, яка зіткнеться з такою проблемою при потрійному зіткненні імен :).
гексицид

4
Щоб було зрозуміло для всіх інших: це рішення не працює, як тільки у вас є doSomethingде-небудь в ієрархії успадкування (через те, як вирішуються цілі виклику методу). Тож Кнут допоможе вам, якщо ви хочете викликати toStringметод. Рішення, запропоноване Доналом, це те, що є в книзі Блоха про Java Puzzlers (я думаю, не шукав його), тому ми можемо вважати це справді авторитетною відповіддю :-)
Voo,

16

Правильним способом буде статичний імпорт, але в абсолютно гіршому випадку ви МОЖЕТЕ побудувати екземпляр класу за допомогою відображення, якщо ви знаєте його повністю кваліфіковане ім'я.

Java: newInstance класу, який не має конструктора за замовчуванням

А потім викликати метод на екземпляр.

Або просто викликайте сам метод із відображенням: Виклик статичного методу за допомогою відображення

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

Звичайно, це, очевидно, останні курорти.


2
Ця відповідь на сьогоднішній день є найбільшим
перенасиченням

3
За цю відповідь ви повинні отримати значок завершення; Ви надали відповідь для того особливого бідного, котрий повинен використовувати стародавню версію Java і вирішити саме цю проблему.
Гімбі,

Я насправді не думав про те, що static importфункція була додана лише в Java 1.5. Я не заздрю ​​людям, яким потрібно розвиватися до 1.4 або нижче, мені довелося одного разу, і це було жахливо!
EpicPandaForce

1
@Gimby: Ну, все ще є ((net.foo.X)null).doSomething()рішення, яке працює в старій Java. Але якщо тип Aнавіть містить внутрішній тип net, ПОТІМ це єдина відповідь, яка залишається дійсною :).
gexicide

6

Не зовсім відповідь, але ви можете створити екземпляр X і викликати на ньому статичний метод. Це був би спосіб (визнаю брудним) назвати свій метод.

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

3
Передача nullтипу, а потім виклик методу на ньому було б краще, оскільки створення об’єкта може мати побічні ефекти, яких я не хочу.
гексицид

@gexicide Ця ідея кастингу null, здається, є одним із чистіших способів це зробити. Потрібно @SuppressWarnings("static-access")мати попередження…
Donal Fellows

Дякую за точність null, але кастинг nullзавжди був для мене серцем.
Майкл Лаффарг

4

Немає необхідності робити будь-яке приведення або придушувати якісь дивні попередження або створювати будь-який надлишковий екземпляр. Просто хитрість, використовуючи той факт, що ви можете викликати статичні методи батьківського класу через підклас. (Подібно до мого хакерського рішення тут .)

Просто створіть такий клас

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

(Приватний конструктор у цьому фінальному класі гарантує, що ніхто випадково не зможе створити екземпляр цього класу.)

Тоді ви можете дзвонити X.doSomething()через нього:

    public class B extends A {

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

Цікавий, ще один підхід. Однак клас із статичним методом є кінцевим класом корисності.
гексицид

Так, не можу використовувати цей трюк у такому випадку. Ще одна причина, чому статичний метод - це невдача мови, і слід застосовувати об’єктний підхід Scala.
billc.cn

Якщо ви все-таки збираєтеся створити інший клас, то, мабуть, найкраще використовувати клас делегатів, як описано у відповіді blgt.
LordOfThePigs

@LordOfThePigs Це дозволяє уникнути додаткового виклику методу та майбутнього навантаження на рефакторинг. Новий клас в основному служить псевдонімом.
billc.cn

2

Що робити, якщо ви спробуєте отримати простір імен gobal, якщо всі файли знаходяться в одній папці. ( 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...

         }
     }

Як це працює? AFAIK getGlobal()не є стандартним методом Java для пакетів ... (я думаю, пакети не можуть мати жодних методів на Java ...)
siegi

1

Це одна з причин того, що склад кращий за спадщину.

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

Я б використав шаблон Стратегії.

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

Тепер ви також відокремили свою залежність, тому ваші модульні тести стануть простішими для написання.


Ви забули додати implements SomethingStrategyв XSomethingStrategyоголошенні класу.
Рікардо Соуза,

@rcdmk Дякую. Це слід виправити зараз.
Ерік Мадсен,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.