【问题标题】:Java Name Hiding: The Hard WayJava名称隐藏:艰难的方式
【发布时间】:2014-08-25 16:04:59
【问题描述】:

我有一个非常难以解决的名称隐藏问题。这是解释问题的简化版本:

有一个类: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();但是这个类只会因为名字冲突而存在,看起来很乱!有没有可以直接从B.doSomething()调用X.doSomething()的解决方案?

在允许指定全局命名空间的语言中,例如 C# 中的 global:: 或 C++ 中的 ::,我可以简单地为 net 加上这个全局前缀,但 Java 不允许这样做。

【问题讨论】:

  • 一些快速修复可能是这样的:public void help(net.foo.X x) { x.doSomething(); } 并使用 help(null); 调用
  • @Absurd-Mind:嗯,通过不存在的对象调用静态方法似乎比我的解决方案更混乱 :)。我什至会收到编译器警告。但确实,这将是除我之外的另一个 hacky 解决方案。
  • @JamesB:这会实例化错误的 X! net.foo.X有方法,不是org.A.X
  • A继承吗?正如你所发现的那样,继承可以如此令人讨厌……
  • 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 表示清洁代码的态度。但对我来说,这似乎是你应该权衡的情况。只需执行此操作,然后就您为什么必须这样做(可能带有此问题的链接)提出一个很好的长评论。

标签: java name-clash member-hiding


【解决方案1】:

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

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

这有以下好处

  • 无副作用(实例化 net.foo.X 时出现问题),
  • 不需要重命名任何内容(因此您可以为 B 中的方法指定您想要的名称;这就是为什么 import static 不适用于您的具体情况),
  • 不需要引入委托类(尽管这可能是个好主意……),并且
  • 不需要使用反射 API 的开销或复杂性。

缺点是这段代码真的很糟糕!对我来说,它会产生警告,总的来说这是一件好事。但由于它正在解决一个完全不切实际的问题,因此添加一个

@SuppressWarnings("static-access")

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

【讨论】:

  • 为什么不呢?您只需做正确的事并重构代码。
  • 静态导入并没有比这个解决方案清晰得多并且在所有情况下都不起作用(如果你的层次结构中的任何地方都有 doSomething 方法,你就搞砸了),所以是最好的解决方案。
  • @Voo 我坦率地承认,我实际上去尝试了其他人列出的所有选项作为答案,首先准确地复制了原始问题是什么,我很惊讶解决问题的难度.最初的问题巧妙地关闭了所有其他更好的选择。
  • 为创造力投票,但我绝不会在生产代码中这样做。我相信间接是这个问题的答案(不是所有问题吗?)。
  • 感谢 Donal 提供的解决方案。实际上,这个解决方案突出了可以使用“类型”而不能使用变量的地方。因此,在 "cast" 中,即使存在同名的变量,编译器也会选择 "Type"。对于更多此类有趣的案例,我会推荐 2 本书“Java Pitfalls”:books.google.co.in/books/about/…books.google.co.in/books/about/…
【解决方案2】:

可能最简单(不一定是最简单)的管理方法是使用委托类:

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

【讨论】:

  • 可能是提供的最干净的解决方案。如果我有这个问题,我可能会使用它。
【解决方案3】:

您可以使用静态导入:

import static net.foo.X.doSomething;

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

注意BA 不包含名为doSomething 的方法

【讨论】:

  • net.foo.X.doSomething 不是只有包访问权限吗?这意味着您无法从包 com.bar 访问它
  • @JamesB 是的,但是完整的问题没有意义,因为该方法无法以任何方式访问。我认为这是简化示例时的错误
  • 但是原问题好像主动想用doSomething作为B中的方法名...
  • @DonalFellows:你是对的;如果我的代码必须按照我发布的方式保留,则此解决方案不起作用。幸运的是,我可以重命名该方法。但是,想想我的方法覆盖另一个方法的情况,然后我无法重命名它。在这种情况下,这个答案确实不能解决问题。但这比我的问题更加巧合,所以也许永远不会有人遇到三重名称冲突这样的问题:)。
  • 为了让其他人清楚:只要您在继承层次结构中的任何位置都有doSomething,此解决方案就不起作用(由于方法调用目标的解析方式)。因此,如果您想调用 toString 方法,Knuth 会帮助您。 Donal 提出的解决方案是 Bloch 的 Java Puzzlers 书中的解决方案(我认为,没有查过),所以我们可以认为它是权威的答案:-)
【解决方案4】:

做事的正确方法是静态导入,但在绝对最坏的情况下,如果你知道它的完全限定名,你可以使用反射构造一个类的实例。

Java: newInstance of class that has no default constructor

然后调用实例上的方法。

或者,只需使用反射调用方法本身: Invoking a static method using reflection

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

当然,这些显然是最后的手段。

【讨论】:

  • 这个答案是迄今为止最大的矫枉过正 - 竖起大拇指:)
  • 您应该获得此答案的完成主义者徽章;你为那个不得不使用古老版本的 Java 并解决这个确切问题的可怜人提供了答案。
  • 其实我没想到static import的功能只在Java 1.5中加入了。我不羡慕需要开发1.4或以下的人,我不得不一次,这太糟糕了!
  • @Gimby:嗯,仍然有 ((net.foo.X)null).doSomething() 解决方案适用于旧 java。但是,如果类型 A 甚至包含内部类型 netTHEN 这是唯一仍然有效的答案:)。
【解决方案5】:

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

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

【讨论】:

  • null 转换为类型,然后对其调用方法会更好,因为创建对象可能会产生我不想要的副作用。
  • @gexicide 铸造null 的想法似乎是更清洁的方法之一。需要@SuppressWarnings("static-access") 才能无警告...
  • 感谢null 提供的准确信息,但选择null 对我来说一直是个心碎。
【解决方案6】:

无需进行任何强制转换或抑制任何奇怪的警告或创建任何冗余实例。只是一个技巧,您可以通过子类调用父类静态方法。 (类似于我的 hackish 解决方案 here。)

创建一个这样的类

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

(这个最终类中的私有构造函数确保没有人会意外地创建这个类的实例。)

那么你就可以通过它拨打X.doSomething()了:

    public class B extends A {

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

【讨论】:

  • 有趣,又是另一种方法。但是,具有静态方法的类是最终实用程序类。
  • 是的,在这种情况下不能使用这个技巧。静态方法是一门语言失败的另一个原因,应该采用 Scala 的对象方法。
  • 如果您仍然要创建另一个类,那么使用 blgt 答案中描述的委托类可能是最好的。
  • @LordOfThePigs 这避免了额外的方法调用和未来的重构负担。新类基本上用作别名。
【解决方案7】:

假设所有文件都在同一个文件夹中,如果您尝试获取 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 中不能有任何方法...)
【解决方案8】:

这是组合优于继承的原因之一。

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

【讨论】:

    【解决方案9】:

    我会使用策略模式。

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

    现在您还解耦了依赖项,因此您的单元测试将更容易编写。

    【讨论】:

    • 您忘记在XSomethingStrategy 类声明中添加implements SomethingStrategy
    • @rcdmk 谢谢。现在应该修好了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多