我们可能会看到错误:
java.lang.NoSuchMethodError: com.nhn.user.UserAdmin.addUser(Ljava/lang/String;)V.
这表示未找到没有返回类型的方法 addUser
。这可能是因为库可能已更新,但我们应用程序的字节码仍然很旧。
但是当我们研究Java重载时,我们可以知道Java不支持仅仅改变返回类型的重载。
既然这是正确的,为什么字节码会记下被调用方法的返回类型,并在使用该方法的返回类型更新库时引发错误。
最佳答案
Java 字节码是强类型和经过验证的,这意味着调用者的代码必须与调用的方法将返回的内容兼容。因此,即使调用者的方法引用不包含预期的返回类型,代码仍然包含隐式假设,例如试图做long
结果的算术表明该方法预计返回 long
而不是 Object
或 void
.
让方法引用指示预期的返回类型可以简化验证并使整个过程更加高效。您可以使用预期的方法签名来验证方法代码的正确性,而无需执行实际的链接。方法调用指令最终链接时,无需验证可执行代码,只需签名匹配即可。
这就是 Java 字节码以这种方式设计的原因,即使 Java 源代码无法定义不同的返回类型,至少在早期版本中是这样。从 Java 5 开始,规则不再那么严格。
考虑以下接口(interface):
interface StringFunction<R> {
R apply(String input);
}
由于类型删除,它会有一个方法 Object apply(String input)
在字节码级别。
现在考虑下面的实现类:
class Length implements StringFunction<Integer> {
public Integer apply(String input) {
return input.length();
}
}
不仅允许声明更具体的返回类型,而且根据通用类型系统,Java 语言实际上要求它继承抽象方法 Integer apply(String)
来自 StringFunction<Integer>
.
在字节码层面,它会有实际的实现方法Integer apply(String)
以及一个桥方法Object apply(String input)
正式履约interface
在字节码级别上委托(delegate)给实际的实现方法。
由于泛型有效地允许缩小返回类型,因此没有理由拒绝非泛型方法的返回类型,因此,自 Java 5 以来,Java 也允许所谓的协变返回类型:
class Base {
Object getValue() {
return null;
}
}
class Sub extends Base {
@Override String getValue() {
return "now a string";
}
}
因此可以生成具有多个参数类型相同但返回类型不同的方法的类,尽管不是通过重载。
这些情况可以交替处理,例如通过定义方法仅通过参数类型来区分,并且它们的返回类型必须相同或更具体,以与协变返回类型兼容,但这意味着 JVM 在构建方法表时必须急切地解析所有返回类型类的,以验证类型是否真的更具体。而且,它仍然需要对返回类型进行编码,以在调用者和被调用者之间建立适当的契约。
关于Java字节码和没有这样的方法报错,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43876627/