【问题标题】:Reflection Casting and Overloaded Method Dispatching in JavaJava 中的反射转换和重载方法调度
【发布时间】:2012-03-28 03:08:58
【问题描述】:

请注意,所有代码都是一个简化示例,以便仅传达我的问题的核心思想。稍作编辑后,它应该全部编译并运行。

我有几个类都实现了一个通用接口。

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() 来获得我想要的结果,但是使用强制转换会更简洁。

我意识到以前有人问过有关类似概念的问题,但是虽然所有答案都提供了丰富的信息,但没有一个让我满意。 双重调度看起来可行,但这意味着要重新编写大量代码,因为我经常使用这种类型的东西。

【问题讨论】:

    标签: java reflection overloading


    【解决方案1】:

    在我看来,我们正在谈论发生了什么事:

    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&lt;? extends Inter&gt;。因此,演员表是无用的,你只会得到第一次重载。

    【讨论】:

    • 谢谢,我希望不是这样,因为这很烦人。我只是忍不住觉得像这样的东西应该是动态的。它让一切变得如此复杂......哦,好吧,我想我只需要使用反射 API,就像它看起来一样混乱。
    【解决方案2】:

    15.12 节Method Invocation Expression 中的Java Language Specification (JLS) 详细解释了编译器选择正确调用方法所遵循的过程。

    在那里,您会注意到这是一个编译时 任务。 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);
       }
    }
    

    如果您调用 main,输出会显示 Number

    现在,向ChooseMethod 类添加第二个更具体的方法,并重新编译它(但不要重新编译其他类)。

    public void doSomething(Integer i) {
     System.out.println("Integer");
    }
    

    如果你再次运行main,输出仍然是Number

    基本上,因为它是在编译时决定的。如果你重新编译MethodChooser类(有main的那个),再次运行程序,输出将是Integer

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

    【讨论】:

      猜你喜欢
      • 2023-03-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多