Сокрытие имени Java: трудный путь
У меня есть проблема со скрытием имен, которую очень сложно решить. Вот упрощенная версия, которая объясняет проблему:
Есть класс: 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 не позволяет этого.
9 ответов
Вы можете сыграть null
к типу, а затем вызвать метод для этого (который будет работать, так как целевой объект не участвует в вызове статических методов).
((net.foo.X) null).doSomething();
Это имеет преимущества
- отсутствие побочных эффектов (проблема с реализацией
net.foo.X
), - не требует переименования чего-либо (поэтому вы можете дать метод в
B
имя, которое вы хотите иметь; вот почемуimport static
не будет работать в вашем конкретном случае), - не требующий введения класса делегата (хотя это может быть хорошей идеей…), и
- не требующий дополнительных затрат или сложности работы с отражением API.
Недостатком является то, что этот код действительно ужасен! Для меня это генерирует предупреждение, и в целом это хорошо. Но так как он работает вокруг проблемы, которая в противном случае является совершенно непрактичной, добавление
@SuppressWarnings("static-access")
в соответствующей (минимальной!) точке включения компилятор закроется.
Вероятно, самый простой (не обязательно самый простой) способ справиться с этим - использовать класс делегата:
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
Вы можете использовать статический импорт:
import static net.foo.X.doSomething;
class B extends A {
void doX(){
doSomething();
}
}
Берегись этого B
а также A
не содержат именованные методы doSomething
Правильный способ сделать это - статический импорт, но в самом худшем случае вы МОЖЕТЕ создать экземпляр класса, используя отражение, если знаете его полное имя.
Java: newInstance класса, у которого нет конструктора по умолчанию
И затем вызовите метод в экземпляре.
Или просто вызовите сам метод с помощью отражения: вызов статического метода с использованием отражения
Class<?> clazz = Class.forName("net.foo.X");
Method method = clazz.getMethod("doSomething");
Object o = method.invoke(null);
Конечно, это явно последние курорты.
Не совсем ответ, но вы можете создать экземпляр X и вызвать для него статический метод. Это был бы способ (грязный, я признаю) вызвать ваш метод.
(new net.foo.X()).doSomething();
Нет необходимости выполнять какие-либо преобразования или подавлять странные предупреждения или создавать избыточные экземпляры. Просто трюк, использующий тот факт, что вы можете вызывать статические методы родительского класса через подкласс. (Подобно моему хакерскому решению здесь.)
Просто создайте такой класс
public final class XX extends X {
private XX(){
}
}
(Закрытый конструктор в этом последнем классе гарантирует, что никто не сможет случайно создать экземпляр этого класса.)
Тогда вы можете позвонить X.doSomething()
через него:
public class B extends A {
public void doSomething() {
XX.doSomething();
}
Что если вы попытаетесь получить пространство имен 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...
}
}
Это одна из причин того, что состав предпочтительнее наследования.
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();
}
}
Я бы использовал шаблон Стратегии.
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();
}
}
Теперь вы также развязали свою зависимость, поэтому ваши модульные тесты будут легче писать.