如何在Java中实现具有同形方法的接口?

在英语中,同形异义词对是两个拼写相同但含义不同的单词。

在软件工程中,一对同源方法是名称相同但要求不同的两种方法。让我们看一个人为的例子,使问题尽可能清晰:

interface I1 { 
    /** return 1 */ 
    int f()
}
interface I2 {
    /** return 2*/
    int f()
}
interface I12 extends I1, I2 {}

如何实现?C#有办法做到这一点,但Java没有。因此,唯一的解决方法是黑客攻击。如何最可靠地使用反射/字节码技巧/等来完成(即它不必是一个完美的解决方案,我只想要一个效果最好的解决方案)?I12


请注意,一些现有的闭源大量遗留代码,我无法合法地进行逆向工程,这需要一个类型的参数,并将两者都委托给作为参数的代码和作为参数的代码。因此,基本上我需要使一个实例知道它何时应该充当,何时应该充当 ,我相信这可以通过在直接调用方的运行时查看字节码来完成。我们可以假设调用方不使用反射,因为这是简单的代码。问题是的作者没想到Java从两个接口合并,所以现在我必须想出解决这个问题的最佳技巧。没有调用(显然,如果作者编写了一些实际调用的代码,他会在出售之前注意到这个问题)。I12I12I1I2I12I1I2I12fI12.fI12.f

请注意,我实际上是在寻找这个问题的答案,而不是如何重构我无法更改的代码。我正在寻找最好的启发式方法或确切的解决方案(如果存在的话)。请参阅Gray的答案以获取有效示例(我相信有更强大的解决方案)。


下面是一个具体的例子,说明两个接口中的同型方法问题是如何发生的。这是另一个具体的例子:

我有以下6个简单的类/接口。它类似于剧院周围的业务和在其中表演的艺术家。为了简单和具体,让我们假设它们都是由不同的人创建的。

Set表示一个集合,如集合论中所示:

interface Set {
    /** Complements this set,
        i.e: all elements in the set are removed,
        and all other elements in the universe are added. */
    public void complement();
    /** Remove an arbitrary element from the set */
    public void remove();
    public boolean empty();
}

HRDepartment用于表示员工。它使用复杂的流程来解码要雇用/解雇哪些员工:Set

import java.util.Random;
class HRDepartment {
    private Random random = new Random();
    private Set employees;

    public HRDepartment(Set employees) {
        this.employees = employees;
    }

    public void doHiringAndLayingoffProcess() {
        if (random.nextBoolean())
            employees.complement();
        else
            employees.remove();
        if (employees.empty())
            employees.complement();
    }
}

a的员工范围可能是向雇主申请的员工。因此,当在该集合上被调用时,所有现有员工都将被解雇,并且之前应用的所有其他员工都将被雇用。Setcomplement

Artist代表艺术家,例如音乐家或演员。艺术家有自我。当别人称赞他时,这种自我会增加:

interface Artist {
    /** Complements the artist. Increases ego. */
    public void complement();
    public int getEgo();
}

Theater进行表演,这可能会导致补充。剧院的观众可以在两场演出之间评判艺术家。表演者的自我越高,观众就越有可能喜欢,但如果自我超出了某个点,艺术家就会被观众负面地看待:ArtistArtistArtist

import java.util.Random;
public class Theater {
    private Artist artist;
    private Random random = new Random();

    public Theater(Artist artist) {
        this.artist = artist;
    }
    public void perform() {
        if (random.nextBoolean())
            artist.complement();
    }
    public boolean judge() {
        int ego = artist.getEgo();
        if (ego > 10)
            return false;
        return (ego - random.nextInt(15) > 0);
    }
}

ArtistSet只是一个和一个:ArtistSet

/** A set of associated artists, e.g: a band. */
interface ArtistSet extends Set, Artist {
}

TheaterManager运行节目。如果剧院的观众对艺术家的评价是负面的,剧院就会与人力资源部门交谈,人力资源部门反过来会解雇艺术家,雇用新艺术家等:

class TheaterManager {
    private Theater theater;
    private HRDepartment hr;

    public TheaterManager(ArtistSet artists) {
        this.theater = new Theater(artists);
        this.hr = new HRDepartment(artists);
    }

    public void runShow() {
        theater.perform();
        if (!theater.judge()) {
            hr.doHiringAndLayingoffProcess();
        }
    }
}

一旦你尝试实现一个:两个超接口都指定应该做其他事情,所以你必须以某种方式在同一类中实现两个具有相同签名的方法,问题就变得很明显了。 是 的同形异义词。ArtistSetcomplementcomplementArtist.complementSet.complement


答案 1

新想法,有点乱...

public class MyArtistSet implements ArtistSet {

    public void complement() {
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();

        // the last element in stackTraceElements is the least recent method invocation
        // so we want the one near the top, probably index 1, but you might have to play
        // with it to figure it out: could do something like this

        boolean callCameFromHR = false;
        boolean callCameFromTheatre = false;

        for(int i = 0; i < 3; i++) {
           if(stackTraceElements[i].getClassName().contains("Theatre")) {
               callCameFromTheatre = true;
           }
           if(stackTraceElements[i].getClassName().contains("HRDepartment")) {
               callCameFromHR = true;
           }
        }

        if(callCameFromHR && callCameFromTheatre) {
            // problem
        }
        else if(callCameFromHR) {
            // respond one way
        }
        else if(callCameFromTheatre) {
            // respond another way
        }
        else {
            // it didn't come from either
        }
    }
}

答案 2

尽管格雷·凯梅(Gray Kemmey)进行了勇敢的尝试,但我想说的是,正如你所说,这个问题是无法解决的。作为给定的一般规则,您无法知道调用它的代码是期望一个还是一个.ArtistSetArtistSet

此外,即使你可以,根据你对各种其他答案的评论,你实际上需要将一个传递给供应商提供的函数,这意味着该函数没有给编译器或人类任何关于它所期望的线索。对于任何技术上正确的答案,您都完全不走运。ArtistSet

作为完成工作的实际编程问题,我将执行以下操作(按此顺序):

  1. 向创建需要接口的人员以及生成接口本身的人员提交错误报告。ArtistSetArtistSet
  2. 向提供所需功能的供应商提交支持请求,并询问他们期望的行为是什么。ArtistSetcomplement()
  3. 实现函数以引发异常。complement()
public class Sybil implements ArtistSet {
  public void complement() { 
    throw new UnsupportedOperationException('What am I supposed to do'); 
  }
  ...
}

因为说真的,你不知道该怎么办。当这样称呼时,正确的做法是什么(您如何确定)?

class TalentAgent {
    public void pr(ArtistSet artistsSet) {
      artistSet.complement();
    }
}

通过引发异常,您有机会获得堆栈跟踪,该跟踪为您提供了有关调用方预期的两种行为中的哪一种的线索。幸运的是,没有人调用该函数,这就是为什么供应商在运输代码方面存在此问题的原因。运气不太好,但仍然有些,他们处理异常。如果不是这样,那么,至少现在您将有一个堆栈跟踪,您可以查看以确定调用方真正期望的内容并可能实现它(尽管我不寒而栗地想到以这种方式永久化错误,我已经解释了我将如何在另一个答案中做到这一点)。

顺便说一句,对于实现的其余部分,我会将所有内容委托给实际和通过构造函数传入的对象,以便以后可以轻松将其拉开。ArtistSet


推荐