「根據方法名獲取參數類型」是 RPC 框架、動態代理、插件系統、測試工具的高頻需求。但 JDK 原生 Class.getMethods() 存在重載方法衝突、泛型擦除、橋接方法干擾、參數名缺失等問題。本文將剖析核心陷阱,提供生產級解決方案。
public class ReflectionPitfalls {
public void process(String name) {}
public void process(Integer id) {} // 重載
public void process(String name, int age) {} // 同名不同參數數量
public void genericProcess(List list) {} // 泛型擦除後參數類型為 List
}
| 問題 | 表現 | 後果 |
|---|---|---|
| 重載衝突 | getMethods() 返回多個同名方法 | 無法確定目標方法,首個匹配可能錯誤 |
| 泛型擦除 | getParameterTypes() 返回原始類型 | 無法獲取 List<String> 的 String 信息 |
| 橋接方法 | 編譯器生成橋接方法實現多態 | 干擾參數類型判斷 |
| 參數名缺失 | 未編譯 -parameters 時無法獲取名稱 | 無法按名稱匹配參數 |
// 原文代碼缺陷:
// 1. 僅匹配方法名,忽略參數數量/類型 → 重載下極易錯誤
// 2. Class.forName(params[j].getName()) 多此一舉,params[j] 已是 Class 對象
// 3. 未處理基本類型(int vs Integer)、數組類型、泛型
// 4. 異常吞沒,返回 null 導致 NPE
public static Class[] getMethodParamTypes(Object classInstance, String methodName)
throws ClassNotFoundException {
Class[] paramTypes = null;
Method[] methods = classInstance.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (methodName.equals(methods[i].getName())) {
Class[] params = methods[i].getParameterTypes();
paramTypes = new Class[params.length];
for (int j = 0; j < params.length; j++) {
paramTypes[j] = Class.forName(params[j].getName()); // 多餘且可能拋出異常
}
break; // 直接中斷,取第一個匹配 → 重載災難
}
}
return paramTypes;
}
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
public final class MethodResolver {
private MethodResolver() {}
/**
* 精確解析方法:方法名 + 參數類型列表(支援基本類型別名)
*
* @param clazz 目標類
* @param methodName 方法名
* @param paramTypeNames 參數類型全限定名或簡單名(如 "int", "java.lang.String", "String")
* @return 匹配的 Method,無則拋出異常
*/
public static Method resolveMethod(Class> clazz, String methodName, String... paramTypeNames) {
Class>[] paramTypes = parseParamTypes(paramTypeNames);
return resolveMethod(clazz, methodName, paramTypes);
}
/**
* 精確解析方法:方法名 + Class[] 參數類型
*/
public static Method resolveMethod(Class> clazz, String methodName, Class>... paramTypes) {
List candidates = Arrays.stream(clazz.getDeclaredMethods())
.filter(m -> m.getName().equals(methodName))
.filter(m -> Arrays.equals(m.getParameterTypes(), paramTypes))
.collect(Collectors.toList());
if (candidates.isEmpty()) {
throw new NoSuchMethodException(format("方法不存在: %s.%s(%s)",
clazz.getName(), methodName, Arrays.toString(paramTypes)));
}
if (candidates.size() > 1) {
// 理論上不應發生,除非存在橋接方法
throw new IllegalStateException(format("存在多個匹配方法: %s", candidates));
}
Method method = candidates.get(0);
method.setAccessible(true); // 支援 private/protected
return method;
}
/**
* 智能解析:僅提供方法名,自動推斷(僅適用於無重載場景)
* @throws IllegalStateException 若存在重載
*/
public static Method resolveUniqueMethod(Class> clazz, String methodName) {
List methods = Arrays.stream(clazz.getDeclaredMethods())
.filter(m -> m.getName().equals(methodName))
.collect(Collectors.toList());
if (methods.isEmpty()) {
throw new NoSuchMethodException("方法不存在: " + methodName);
}
if (methods.size() > 1) {
throw new IllegalStateException("存在重載,請使用 resolveMethod(clazz, name, paramTypes): " + methods);
}
Method m = methods.get(0);
m.setAccessible(true);
return m;
}
/** 解析類型名稱字串為 Class 對象(支援基本類型、數組、泛型擦除類型) */
private static Class>[] parseParamTypes(String[] typeNames) {
if (typeNames == null || typeNames.length == 0) return new Class[0];
Class>[] types = new Class>[typeNames.length];
for (int i = 0; i < typeNames.length; i++) {
types[i] = parseType(typeNames[i].trim());
}
return types;
}
private static Class> parseType(String name) {
// 基本類型映射
switch (name) {
case "boolean": case "bool": return boolean.class;
case "byte": return byte.class;
case "char": case "character": return char.class;
case "short": return short.class;
case "int": case "integer": return int.class;
case "long": return long.class;
case "float": return float.class;
case "double": return double.class;
case "void": return void.class;
}
// 數組類型:如 "int[]", "java.lang.String[]", "[Ljava.lang.String;"
if (name.endsWith("[]")) {
Class> component = parseType(name.substring(0, name.length() - 2));
return Array.newInstance(component, 0).getClass(); // 獲取數組 Class
}
if (name.startsWith("[")) { // JVM 內部表示
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("無效數組類型: " + name);
}
}
// 普通類:嘗試全限定名,失敗則補 java.lang.
try {
return Class.forName(name);
} catch (ClassNotFoundException e1) {
try {
return Class.forName("java.lang." + name);
} catch (ClassNotFoundException e2) {
throw new IllegalArgumentException("找不到類型: " + name);
}
}
}
/** 獲取方法泛型參數類型(需編譯 -parameters 且非擦除場景) */
public static Type[] getGenericParamTypes(Method method) {
return method.getGenericParameterTypes();
}
/** 獲取參數名稱(需 javac -parameters 編譯) */
public static String[] getParameterNames(Method method) {
return Arrays.stream(method.getParameters())
.map(Parameter::getName)
.toArray(String[]::new);
}
}
public class DemoService {
public void save(String name, int age) {}
public void save(User user) {} // 重載
public void process(List items) {} // 泛型
}
public class MethodResolverTest {
@Test void testResolveOverload() {
// 精確匹配重載
Method m1 = MethodResolver.resolveMethod(DemoService.class, "save", "java.lang.String", "int");
Method m2 = MethodResolver.resolveMethod(DemoService.class, "save", "com.example.User");
assertEquals(2, m1.getParameterCount());
assertEquals(1, m2.getParameterCount());
}
@Test void testResolveUnique() {
// 無重載可用簡便方法
Method m = MethodResolver.resolveUniqueMethod(DemoService.class, "process");
// 泛型擦除後參數類型為 List
assertEquals(List.class, m.getParameterTypes()[0]);
// 但可獲取泛型信息
Type genericType = m.getGenericParameterTypes()[0];
assertTrue(genericType instanceof ParameterizedType);
}
@Test void testPrimitiveArray() {
Method m = MethodResolver.resolveMethod(TestClass.class, "handle", "int[]", "[Ljava.lang.String;");
assertEquals(int[].class, m.getParameterTypes()[0]);
assertEquals(String[].class, m.getParameterTypes()[1]);
}
}
# Maven
org.apache.maven.plugins
maven-compiler-plugin
-parameters
# Gradle
tasks.withType(JavaCompile) {
options.compilerArgs << "-parameters"
}
編譯後可通過 method.getParameters()[i].getName() 獲取真實參數名(而非 arg0)。
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
String[] paramNames = discoverer.getParameterNames(method);
// Spring 會自動嘗試:-parameters -> LocalVariableTableParameterNameDiscoverer (asm) -> 拋棄
import java.util.concurrent.ConcurrentHashMap;
public class CachedMethodResolver {
private static final ConcurrentHashMap CACHE = new ConcurrentHashMap<>();
private static String key(Class> clazz, String methodName, Class>[] paramTypes) {
return clazz.getName() + "#" + methodName + "#" + Arrays.toString(paramTypes);
}
public static Method resolveCached(Class> clazz, String methodName, Class>... paramTypes) {
String k = key(clazz, methodName, paramTypes);
return CACHE.computeIfAbsent(k, __ -> MethodResolver.resolveMethod(clazz, methodName, paramTypes));
}
}
Java 反射獲取方法參數類型的核心原則:
getParameterTypes() 返回擦除後類型,getGenericParameterTypes() 保留泛型信息int.class 而非 Integer.class本文提供的 MethodResolver 已處理基本類型別名、數組類型、異常訊息友好化、緩存優化,可直接集成到 RPC、插件、測試等框架中。