为什么这个通用代码会产生NoSuchMethodError?

时间:2013-01-13 21:20:15

标签: java generics polymorphism

假设你有一个方法

  • 采用阈值和输入
  • 如果输入小于阈值,
  • 会引发异常
  • 否则返回输入

它看起来像这样:

<N extends Number & Comparable<N>, S extends N> S ensureLessThan(N threshold, S input) {
    if (input.compareTo(threshold) >= 0) {
        throw new IllegalArgumentException("Input " + input + " is not less than " + threshold);
    }
    return input;
}

运行时,此方法会抛出NoSuchMethodError

Exception in thread "main" java.lang.NoSuchMethodError: java.lang.Number.compareTo(Ljava/lang/Object;)I

添加看起来像冗余演员的内容使其有效:

...
    if (((N) input).compareTo(threshold) >= 0) {
...

那么这里发生了什么?

更新:我的Java版本是

java version "1.6.0_37"
Java(TM) SE Runtime Environment (build 1.6.0_37-b06-434-11M3909)
Java HotSpot(TM) 64-Bit Server VM (build 20.12-b01-434, mixed mode)

这是一个可运行的例子:https://gist.github.com/4526536

3 个答案:

答案 0 :(得分:7)

我怀疑这是编译器中的一个错误。它静态绑定到Number.compareTo(Object),它不存在,应该是Comparable.compareTo(Object)

答案 1 :(得分:5)

if (threshold.compareTo(input) < 0) {

会奏效。

它肯定是编译器错误。这样做的原因是编译器生成一个已检查的强制转换为Comparable。而另一种方式却没有。

ensureLessThan(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;
  L0
        LINENUMBER 11 L0
        ALOAD 1
        CHECKCAST java/lang/Comparable
        ALOAD 2
        INVOKEINTERFACE java/lang/Comparable.compareTo (Ljava/lang/Object;)I
        IFGE L1

ensureLessThan(Ljava/lang/Number;Ljava/lang/Number;)Ljava/lang/Number;
        LINENUMBER 11 L0
        ALOAD 1
        ALOAD 2
        INVOKEVIRTUAL java/lang/Number.compareTo (Ljava/lang/Object;)I
        IFGE L1

答案 2 :(得分:2)

这很奇怪,以下代码编译并从我这边运行:

/**
 * @author Buhake Sindi
 * @since 14 January 2013
 *
 */
public class Test {

    public static <N extends Number & Comparable<N>, S extends N> S ensureLessThan(N threshold, S input) {
        if (input.compareTo(threshold) >= 0) {
            throw new IllegalArgumentException("Input " + input + " is not less than " + threshold);
        }
        return input;
    }

    public static void main(String[] args) {
        ensureLessThan(10, 5);
    }
}

使用以下JDK在Eclipse Juno(Java EE)中测试:

  1. JDK 1.6.0_21。
  2. JDK 1.7.0_09-b05