Java - 接口实现中的方法名冲突

92

如果我有两个接口,它们的目的非常不同,但是方法签名相同,那么如何使一个类同时实现这两个接口,而不被迫编写为两个接口服务的单个方法,并在方法实现中编写一些复杂的逻辑以检查调用所针对的对象类型并调用正确的代码?

在C#中,可以通过显式接口实现来解决这个问题。在Java中是否有任何等效的方法?


39
当一个类必须实现两个具有相同签名但执行不同操作的方法时,那么这个类几乎肯定做了太多的事情。 - Joachim Sauer
15
在我看来,以上不一定总是正确的。有时候,在一个类中,你需要符合外部约定(因此对方法签名有限制)但具有不同实现的方法。事实上,这些都是设计一个非平凡类时常见的需求。重载和覆盖是必要的机制,以允许进行不同操作的方法,这些方法在签名上可能没有区别,或者只有非常小的区别。我所说的只是更加严格,它不允许子类化/甚至不允许最小程度的签名变化。 - Bhaskar
2
我遇到了这样一个情况,一个名为"Address"的传统类实现了Person和Firm接口,并且拥有一个getName()方法,该方法只是从数据模型中返回一个字符串。一个新的业务需求指定Person.getName()方法需要返回一个格式为"姓氏, 名字"的字符串。经过多次讨论后,决定在数据库中重新格式化数据。 - belwood
13
仅仅说这个类几乎肯定做了太多的事情是没有建设性的。我现在遇到了这种情况,我的类有两个不同接口的方法名称冲突,但我的类并没有做太多的事情。目的相当相似,但略有不同。请不要通过指责提问者实现了糟糕的软件设计来维护一个明显严重有缺陷的编程语言! - j00hi
1
@nishanths,OP已经提到了C#,它通过“显式接口实现”来实现。这里有一个类实现了两个接口SampleClass:IControl,ISurface,每个接口都定义了一个Paint方法的示例:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/interfaces/explicit-interface-implementation - j00hi
显示剩余4条评论
7个回答

79

不,Java中没有办法在同一个类中用两种不同的方式实现相同的方法。

这样做可能会导致许多令人困惑的情况,这也是为什么Java禁止这样做的原因。

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}
你可以将两个实现不同接口的类组合成一个类。这样,这个类将具有两个接口的行为。
class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}

10
但是这样做,我无法将CompositeClass的实例传递到期望接口(ISomething或ISomething2)引用的位置?我甚至不能期望客户端代码能够将我的实例转换为适当的接口,那么这种限制不会让我失去一些东西吗?另请注意,以这种方式编写实际实现各自接口的类时,我们失去了将代码放在单个类中的好处,有时可能是一个严重的障碍。 - Bhaskar
10
@Bhaskar,你提出了有价值的观点。我最好的建议是在该类中添加一个ISomething1 CompositeClass.asInterface1();和一个ISomething2 CompositeClass.asInterface2();方法。然后你可以从复合类中获取一个或另一个。但是这个问题没有很好的解决方案。 - jjnguy
1
说到这可能会导致混淆的情况,你能举个例子吗?我们不能把接口名称添加到方法名称中作为额外的范围解析,从而避免冲突/混淆吗? - Bhaskar
1
如果接口需要一个long,但是类的用户期望int,像public long getCountAsLong() implements interface2.getCount {...}这样允许的话,会有多么混乱呢?或者假设collectionInterface有一个canAdd()方法,并且对于该类的所有实例它都返回false,那么private void AddStub(T newObj) implements coolectionInterface.Add又该如何处理呢? - supercat
1
@supercat 很令人困惑,显然。我甚至无法阅读那个评论... - Holger
显示剩余3条评论

14

在Java中没有真正的解决方法。您可以使用内部类作为解决方法:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

虽然它不允许从AlfaBeta转换为Beta,但向下转型通常是不好的。如果可以预期到一个Alfa实例往往也具有Beta方面,并且出于某种原因(通常是优化是唯一有效的原因),您想将其转换为Beta,则可以创建一个Alfa子接口,其中包含Beta asBeta()


你的意思是匿名类而不是内部类吗? - Zaid Masud
2
@ZaidMasud 我的意思是内部类,因为它们可以访问封闭对象的私有状态。这些内部类当然也可以是匿名的。 - gustafc

13

如果你遇到这个问题,很可能是因为你在应该使用委托的地方使用了继承。如果你需要为相同的底层数据模型提供两个不同但类似的接口,那么你应该使用视图来以一种廉价的方式提供对数据的访问。

举个具体的例子,假设你想要实现CollectionMyCollection(它不继承自Collection并且具有不兼容的接口)。你可以提供Collection getCollectionView()MyCollection getMyCollectionView()函数,使用相同的底层数据提供CollectionMyCollection的轻量级实现。

对于前面的情况... 假设你确实需要一个整数数组和一个字符串数组。你应该拥有一个类型为List<Integer>的成员和一个类型为List<String>的成员,并引用这些成员,而不是尝试从两者都继承。即使你只需要一个整数列表,也最好在这种情况下使用组合/委托而不是继承。


我不这么认为。你忘记了那些需要你实现不同接口以与它们兼容的库。当你使用多个冲突的库时,你会更经常遇到这种情况,而不是在自己的代码中遇到这种情况。 - nightpool
3
如果您使用多个需要不同接口的库,仍然不需要一个对象实现两个接口;可以让对象拥有用于返回两个不同接口的访问器(并在将对象传递给其中一个底层库时调用相应的访问器)。 - Michael Aaron Safyan

2
唯一我想到的解决方案是使用参考对象来实现多个接口。
例如:假设你要实现2个接口。
public interface Framework1Interface {

    void method(Object o);
}

and

public interface Framework2Interface {
    void method(Object o);
}

你可以将它们封装在两个Facador对象中:
public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

并且

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

最终你想要的课程应该是这样的。
public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

现在,您可以使用getAs*方法来“公开”您的类


1
"经典的"Java问题也影响了我的Android开发...原因似乎很简单:使用的框架/库越多,事情就越容易失控... 在我的情况下,我有一个继承自android.app.Application的BootstrapperApp类, 而同样的类还应该实现MVVM框架的Platform接口以进行集成。 在一个getString()方法上发生了方法冲突,这个方法被两个接口声明,并且应该在不同的上下文中有不同的实现。 解决方法(丑陋..我认为)是使用一个内部类来实现所有平台方法,仅仅因为一个小的方法签名冲突...在某些情况下,这个借用的方法甚至根本没有被使用(但影响了主要设计语义)。 我倾向于认为C#风格的显式上下文/命名空间指示是有帮助的。"

1
直到我开始使用Java进行Android开发,我才意识到C#是多么周到和功能丰富。我曾经认为那些C#的特性是理所当然的。Java缺少太多功能。 - Damn Vegetables

0
你可以使用适配器模式来使其工作。为每个接口创建两个适配器并使用它们。它应该解决问题。

-1

当你完全控制所涉及的所有代码并可以事先实现时,一切都很好。 现在想象一下,你有一个现有的公共类,在许多地方使用一个方法。

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

现在你需要将它传递给现成的WizzBangProcessor,该处理器需要类来实现WBPInterface...该接口还有一个getName()方法,但是与您的具体实现不同,该接口期望该方法返回一种Wizz Bang Processing类型的名称。

在C#中,这将是一个微不足道的事情。

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

在Java Tough中,您需要确定现有部署代码库中的每个点,需要从一个接口转换为另一个接口。当然,WizzBangProcessor公司应该使用getWizzBangProcessName(),但他们也是开发人员。在他们的情况下,getName很好。实际上,在Java之外,大多数其他基于OO的语言都支持此功能。Java罕见地强制要求所有接口都使用相同的方法名称进行实现。
大多数其他语言都有编译器,可以轻松地指定“此类中与已实现接口中此方法签名匹配的方法即为其实现”这样的指令。毕竟,定义接口的整个目的就是允许将定义从实现中抽象出来。(更不用说在Java界面中具有默认方法,更不用说默认覆盖了……因为肯定设计用于汽车的每个组件都应该能够被猛撞进入飞行汽车并正常工作-嘿,它们都是汽车...我敢肯定您的卫星导航默认功能不会受到默认俯仰和横滚输入的影响,因为汽车只会偏航!)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接