自动转型带来的NoSuchMethodError

前言

更新了core包里的通用方法, 结果本地代码没问题, 线上代码500, 看了下localhost.log, 发现报NoSuchMethodError. 特此记录下。

重现步骤

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Main.java
public class Main {
public static void main(String[] args) throws Exception {
int page = 1;
int pageSize = 10;
Utils.test(page,pageSize);
}
}
// Utils.java
public class Utils {
public static void test(int page, int pageSize){
System.out.println("page:"+page+", pageSize:"+pageSize);
}
}

编译javac Main.java Utils.java
运行java Main后输出page:1, pageSize:10
修改Utils代码, 将int改为long, Main不用改动

1
2
3
4
5
6
// Utils.java
public class Utils {
public static void test(long page, int pageSize){
System.out.println("第"+page+"页, 分页大小为"+pageSize);
}
}

只编译Utils, 执行javac Utils.java
然后运行java Main, 抛出NoSuchMethodError.

1
2
Exception in thread "main" java.lang.NoSuchMethodError: Utils.test(II)V
at Main.main(Main.java:5)

重新同时编译两个文件javac Main.java Utils.java
运行java Main后又成功输出page:1, pageSize:10

分析

先把修改前的class文件反编译, 得到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Main.class
public class Main {
public Main() {
}
public static void main(String[] var0) throws Exception {
int var1 = 1;
int var2 = 10;
Utils.test(var1, var2);
}
}
// Utils.class
public class Utils {
public Utils() {
}
public static void test(int var0, int var1) {
System.out.println("page:" + var0 + ", pageSize:" + var1);
}
}

Utilsint转为long之后进行反编译得到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Main.class
public class Main {
public Main() {
}
public static void main(String[] var0) throws Exception {
int var1 = 1;
int var2 = 10;
Utils.test(var1, var2);
}
}
// Utils.class
public class Utils {
public Utils() {
}

public static void test(long var0, int var2) {
System.out.println("page:" + var0 + ", pageSize:" + var2);
}
}

同时编译Main.javaUtils.java之后进行反编译得到如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Main.class
public class Main {
public Main() {
}

public static void main(String[] var0) throws Exception {
int var1 = 1;
int var2 = 10;
Utils.test((long)var1, var2);
}
}
// Utils.class
public class Utils {
public Utils() {
}
public static void test(long var0, int var2) {
System.out.println("page:" + var0 + ", pageSize:" + var2);
}
}

对比可以看到, 正常执行的代码, page参数的类型是对应的, 自动转型其实是在编译的时候对其进行了强制转换.
当我们修改了方法参数类型后, 只编译部署Utils.class后, Main就找不到符合test(int, int)的方法, 因为Utils.class只有test(long, int)方法。
而当我们两个类都进行编译时, Main检测到Utills.class没有test(int, int), 就会自动在调用的方法里加上强制类型转换(long) var1.

发生的情景

一般发生在增量更新的项目里面, 本地编译了class文件, 只替换服务器上对应的文件, 如果是全量更新的项目, 则不会出现这个问题。

如何避免

有两种方法

  1. 全量更新
  2. 保留旧的代码, 添加@Deprecated注解
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Utils.java
    public class Utils {
    @Deprecated
    public static void test(int page, int pageSize){
    System.out.println("page:"+page+", pageSize:"+pageSize);
    }
    public static void test(long page, int pageSize){
    System.out.println("page:"+page+", pageSize:"+pageSize);
    }
    }