Java中的反射转换和方法重载分派

5
请注意,所有代码都是为了传达我的问题的核心思想而简化的示例。虽然需要进行轻微编辑,但应该可以编译和运行。
我有几个类都实现了一个共同的接口。
public interface Inter{}
public class Inter1 implements Inter{}
public class Inter2 implements Inter{}

我有一个独立的类,其中有一个Inter类型的列表。根据用户输入,我使用这个列表来存储和删除Inter1和Inter2类型。

java.util.ArrayList<Inter> inters = new java.util.ArrayList<Inter>();

我还有一系列的重载方法,用于处理每个实现之间的交互方式,以及针对2个“Inter”的默认实现。

void doSomething(Inter in1, Inter in2){
    System.out.println("Inter/Inter");     
}
void doSomething(Inter1 in1, Inter1 in2){
    System.out.println("Inter1/Inter11");    
}
void doSomething(Inter2 in1, Inter1 in2){
    System.out.println("Inter2/Inter1");    
}

这些方法会定期进行调用,如下所示:
for(int i = 0; i < inters.size() - 1; i++){
    for(int o = i+1; o < inters.size(); o++){
        Inter in1 = inters.get(i);    Inter in2 = inters.get(o);

        doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2));

        System.out.println("Class 1: " + in1.getClass().getName());
        System.out.println("Class 2: " + in2.getClass().getName());
    }
}

这个的例子输出如下:
Inter/Inter
Class 1: Inter
Class 2: Inter
Inter/Inter
Class 1: Inter
Class 2: Inter1
Inter/Inter
Class 1: Inter1
Class 2: Inter1

看输出结果,很明显在调用doSomething(Inter in1, Inter in2)时,即使其他方法应该被调用。有趣的是,输出的类名是正确的。
为什么Java在运行时使用反射确定类类型时还具有静态方法重载呢? 有没有办法让Java做到这一点?我知道可以使用反射和Class.getMethod()以及method.invoke()来获得想要的结果,但使用强制转换会更加简洁。
我意识到之前已经问过类似概念的问题,但是虽然所有答案都很有启发性,但没有一个能让我满意。 双重分派看起来可以工作,但这意味着需要重新设计很多代码,因为我经常使用这种类型的东西。
2个回答

7

我看我们正在讨论与以下事项相关的内容:

doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2));

根据您对输出类型始终为Inter的惊讶,似乎您对此情况有些困惑。特别是,您似乎认为in1.getClass().cast(in1)in2.getClass().cast(in2)由于其不同的运行时类型而应强制使用不同的重载方法。然而,这是错误的。
方法重载分辨率是静态发生的。这意味着它基于方法的两个参数的声明类型进行。由于in1in2都声明为Inter,所以选择的方法显然是void doSomething(Inter in1, Inter in2)
这里的要点是in1声明为Inter。这意味着in1.getClass()在静态分析方面与Inter.class基本相同--getClass只返回一个Class<? extends Inter>。因此,这些转换是无用的,您只会得到第一个重载。

谢谢,我希望情况不是这样的,因为这真的很烦人。我只是觉得这种东西应该是动态的,但它会让一切变得更加复杂... 哦,好吧,我想我只能使用反射 API,尽管看起来有点混乱。 - Adno

1

Java语言规范(JLS)第15.12节方法调用表达式详细解释了编译器选择正确方法进行调用的过程。

在那里,您会注意到这是一个编译时任务。 JLS在第15.12.2小节中说:

此步骤使用方法名称参数表达式的类型来定位既可访问又适用的方法。可能有多个这样的方法,在这种情况下,选择最具体的方法。

在您的情况下,这意味着由于您传递了两个Integer类型的对象,因此最具体的方法是正好接收它们的方法。

为了验证其编译时性质,您可以执行以下测试。

声明一个类如下并编译它。

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

声明一个第二个类,调用第一个类的一个方法并编译它。

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

如果调用主函数,输出将会显示“Number”。 现在,在ChooseMethod类中添加第二个更具体的方法,并重新编译它(但不要重新编译其他类)。
public void doSomething(Integer i) {
 System.out.println("Integer");
}

如果您再次运行主程序,输出仍然是Number

基本上,这是在编译时决定的。如果重新编译MethodChooser类(带有主函数的那个类),并再次运行程序,则输出将为Integer

因此,如果您想强制选择其中一个重载方法,则参数的类型必须与编译时参数的类型相对应,而不仅仅是在运行时,就像您在此练习中期望的那样。


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