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.Xorg.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

@ Absurd-Mind:好吧,通过不存在的对象调用静态方法似乎比我的解决方案还糟:)。我什至会收到编译器警告。但没错,这将是我的又一个hacky解决方案。
杀人事件

@JamesB:这会导致错误的X!net.foo.X有方法,没有org.A.X
杀人事件

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 2014年

Answers:


84

您可以将a强制null转换为该类型,然后在该类型上调用方法(这将起作用,因为目标对象不涉及静态方法的调用)。

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

这样做的好处是

  • 没有副作用(实例化存在问题net.foo.X),
  • 不需要重命名(因此您可以使用B想要的名称来命名该方法;这就是为什么a import static在您的具体情况下不起作用的原因),
  • 不需要引入委托类(尽管这可能是个好主意……),并且
  • 不需要使用反射API的开销或复杂性。

缺点是这段代码真的太可怕了!对我来说,它会发出警告,这通常是一件好事。但是,由于它可以解决原本根本不切实际的问题,因此请添加

@SuppressWarnings("static-access")

在适当的(最小!)封闭点将关闭编译器。


1
为什么不?您只需要做正确的事情并重构代码即可。
2014年

1
静态导入并没有比该解决方案清晰得多,并且在所有情况下都无法正常工作(如果doSomething层次结构中的任何位置都有方法,那么您会很费力),因此,最好的解决方案是。
Voo

@Voo我自由地承认自己实际上去了,并尝试了其他人列出的所有选项,这些选项首先完全重现了原始问题,然后让我吃惊的是如何解决。最初的问题巧妙地关闭了所有其他更好的选择。
Donal Fellows 2014年

1
投票支持创造力,但是我永远不会在生产代码中做到这一点。我相信间接解决方案可以解决这个问题(不是所有问题都可以解决吗?)。
ethanfar

感谢Donal提供的解决方案。实际上,此解决方案突出显示了可以使用“类型”而不能使用变量的地方。因此,在“ cast”中,即使存在具有相同名称的变量,编译器也会选择“ Type”。对于更多此类有趣的情况,我将推荐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 2014年

37

您可以使用静态导入:

import static net.foo.X.doSomething;

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

当心这BA不包含命名方法doSomething


net.foo.X.doSomething是否仅具有程序包访问权限?这意味着您无法从com.bar包中访问它
JamesB 2014年

2
@JamesB是的,但是随后的完整问题将变得毫无意义,因为该方法将无法以任何方式进行访问。我认为在简化示例时会出错
Absurd-Mind 2014年

1
但是最初的问题似乎很想doSomethingB…中用作方法的名称
Donal Fellows 2014年

3
@DonalFellows:你是对的;如果我的代码必须保持原样,则此解决方案将不起作用。幸运的是,我可以重命名该方法。但是,考虑一下我的方法覆盖另一个方法,然后无法重命名的情况。在这种情况下,此答案确实无法解决问题。但这比我的问题更加偶然,因此也许永远不会有人会遇到三重名称冲突:)这样的问题。
杀人事件

4
为了使其他所有人都清楚:doSomething继承层次结构中的任何位置(由于如何解决方法调用目标),此解决方案都无法立即使用。因此,如果您想调用toString方法,Knuth会为您提供帮助。Donal提出的解决方案是Bloch的Java Puzzlers一书中的解决方案(我认为还没有查找),因此我们可以认为它是权威的答案:-)
Voo 2014年

16

正确的处理方式是静态导入,但是在绝对最坏的情况下,如果知道类的完全限定名称,则可以使用反射来构造类的实例。

Java:没有默认构造函数的class的newInstance

然后在实例上调用该方法。

或者,仅使用反射调用方法本身: 使用反射调用静态方法

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

当然,这些显然是不得已的方法。


2
这个答案是迄今为止最大的过大
杀手

3
您应该得到一个完成主义者徽章来回答这个问题。您为必须使用古老版本的Java并解决此确切问题的单身穷人提供了答案。
2014年

实际上,我没有考虑过static import仅在中添加功能的事实Java 1.5。我不羡慕需要开发1.4或更低版本的人,我只需要开发一次,那就太糟糕了!
EpicPandaForce 2014年

1
@Gimby:嗯,仍然有((net.foo.X)null).doSomething()可以在旧Java中使用的解决方案。但是,如果type A甚至包含一个内部type net那么这是唯一保持有效的答案:)。
杀人罪2014年

6

不是真正的答案,但是您可以创建X的实例并对其调用静态方法。那是调用您的方法的一种方法(我承认很脏)。

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

3
强制null转换为类型,然后在其上调用方法会更好,因为创建对象可能会产生我不希望的副作用。
杀人事件

@gexicide铸造的想法null似乎是更清洁的方法之一。需要无@SuppressWarnings("static-access")预警…
Donal Fellows 2014年

多亏了的精确度null,但是投射null对我而言一直是令人心碎的。
Michael Laffargue 2014年

4

无需执行任何强制转换或取消任何奇怪的警告或创建任何冗余实例。您可以通过子类调用父类的静态方法,这只是一个技巧。(类似于我这里的骇人听闻的解决方案。)

像这样创建一个类

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

(最后一个类中的私有构造函数确保没有人可以意外地创建此类的实例。)

然后,您可以X.doSomething()通过它自由打电话:

    public class B extends A {

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

有趣,还有另一种方法。但是,带有静态方法的类是最终的实用程序类。
杀人事件

是的,在这种情况下不能使用此技巧。静态方法成为语言失败的另一个原因是应该采用Scala的对象方法。
billc.cn 2014年

如果仍然要创建另一个类,则最好使用blgt answer中描述的委托类。
LordOfThePigs 2014年

@LordOfThePigs这样可以避免额外的方法调用和将来的重构负担。新类基本上用作别名。
billc.cn 2014年

2

如果在所有文件都位于同一文件夹中的情况下尝试获取gobalnamespace,该怎么办。(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 2014年

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 SomethingStrategyXSomethingStrategy类声明上添加。
里卡多·索扎

@rcdmk谢谢。现在应该修复。
Erik Madsen 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.