请注意,所有代码都是一个简化示例,以便只传达我的问题的核心思想。经过轻微编辑后,它应该全部编译并运行。
我有几个类都实现了一个通用接口。
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()来获得我想要的结果,但是通过强制转换它会更加整洁。
我意识到之前已经提出过关于类似概念的问题,但是虽然所有答案都是提供信息的,但没有人满意我。 双重调度看起来会起作用,但这意味着要重新编写很多代码,因为我经常使用这种类型的东西。
答案 0 :(得分:7)
在我看来,我们正在谈论正在发生的事情:
doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2));
基于你的惊喜,输出的类型总是Inter
,你似乎对这里发生的事情感到有些困惑。特别是,您似乎认为in1.getClass().cast(in1)
和in2.getClass().cast(in2)
应该强制执行不同的重载,因为它们的运行时类型不同。但是,这是错误的。
方法重载解析是静态发生的。这意味着它基于方法的两个参数的声明类型而发生。由于in1
和in2
都声明为Inter
,因此选择的方法显然是void doSomething(Inter in1, Inter in2)
。
这里要说的是in1
被声明为Inter
。这意味着in1.getClass()
与Inter.class
基本相同,用于静态分析 - getClass
只返回Class<? extends Inter>
。因此,演员表是无用的,你只会得到第一次超载。
答案 1 :(得分:1)
第15.12节Java Language Specification中的Method Invocation Expression(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
。
因此,如果要强制选择其中一个重载方法,那么参数的类型必须与编译时参数的类型相对应,而不仅仅是在运行时,正如您在此期望的那样锻炼。