前几天笔者在写了一个比较简陋的底层数据库封装类, 但测试时出现NoSuchMethodException.下面是部分源代码..
/** * 对数据库的查询<br/> * 注意:使用时,表的列名必须与实体对应的属性名一致. * * @param sql * SQL语句 * @param params * 注入参数 * @param clazz * 对象类的模板 * @return 对象类的集合,没有结果时size()为0. */ public static <T> List<T> executeQuery(String sql, Object[] params,Class<T> clazz) { // 结果集合 ArrayList<T> result = new ArrayList<T>(); // 获取Connection Connection con = getConnection(); // 声明PreparedStatement PreparedStatement ps = null; // 声明结果集 ResultSet rs = null; // 如果连接不为空 if (con != null) { try { // 注入SQL ps = con.prepareStatement(sql); // 注入参数 insertParams(ps, params); // 获取结果集 rs = ps.executeQuery(); // 获取结果集元数据 ResultSetMetaData rsmd = rs.getMetaData(); // 获取列的信息 HashMap<String, Integer> columnInfo = getColumnInfo(rsmd); // 获取列名 Set<String> columnNames = columnInfo.keySet(); // 循环结果集 while (rs.next()) { // 实例化一个泛型对象 T t = clazz.newInstance(); // 定义为Object类型,以至于可以接收多种数据类型 Object value = null; for (String columnName : columnNames) { // 获取列的类型,调用ResultSet对应的set方法来获取value int type = columnInfo.get(columnName); switch (type) { case Types.INTEGER: value = rs.getInt(columnName); break; case Types.DOUBLE:// SQL中的Float与Types.Double对应 value = rs.getDouble(columnName); break; case Types.VARCHAR: value = rs.getString(columnName); break; case Types.TIMESTAMP: value = rs.getTimestamp(columnName); default: value = rs.getObject(columnName); break; } String name = "set" + UpperFirstCase(columnName); // 获取泛型实例对象的对应方法并执行 Method method = null; method = clazz.getDeclaredMethod(name, new Class<?>[] { value.getClass() }); method.invoke(t, value); } // 将此泛型实例添加到结果集合中 result.add(t); } } catch (SQLException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { closeAll(con, ps, rs); } } return result; }
经过调试后发现问题出现在65行的getDeclaredMethod(String,Class<?>[])方法中, 在调试过程中还发现该方法可以反射并执行getXXX()方法. 但在反射setXXX(xxx,xxxx)方法时,反射的方法名是正确的,于是把错误圈定在参数类型上.
在一系列测试后发现, 基本数据类型存放到Object后再取出来会变成其对应的封装类, 而getDeclaredMethod(String,Class<?>[])方法又区分基本类型和其对应的封装类.
经过一翻探究后想出了三种解决方法:
使用getDeclaredMethods()方法, 通过遍历的方式对方法名进行匹配, 可在循环内部添加条件来提高匹配精度. 但效率比较低.
约定类的方法的参数类型不能使用基本类型,只能用其对应的封装类(别笑...这种方法很有效...其实我当时忍不住要笑...)
-
(也就是我最后采取的方法). 通过以下方法, 传入Object类, 尝试获得基本类型.
/** * 由于反射的getDeclaredMethod()方法不支持基本类型,所以用这个方法进行转换 * * @param o * @return */ private static Class<?> changeToBasicClass(Object o) { Class<?> clazz = o.getClass(); if (clazz == Integer.class) { clazz = Integer.TYPE; } else if (clazz == Long.class) { clazz = Long.TYPE; } else if (clazz == Float.class) { clazz = Float.TYPE; } else if (clazz == Double.class) { clazz = Double.TYPE; } else if (clazz == Short.class) { clazz = Short.TYPE; } else if (clazz == Boolean.class) { clazz = Boolean.TYPE; } else if (clazz == Character.class) { clazz = Character.TYPE; } else if (clazz == Byte.class) { clazz = Byte.TYPE; } return clazz; }
下面贴上笔者修改后的代码.
/** * 对数据库的查询<br/> * 注意:使用时,表的列名必须与实体对应的属性名一致. * * @param sql * SQL语句 * @param params * 注入参数 * @param clazz * 对象类的模板 * @return 对象类的集合,没有结果时size()为0. */ public static <T> List<T> executeQuery(String sql, Object[] params, Class<T> clazz) { // 结果集合 ArrayList<T> result = new ArrayList<T>(); // 获取Connection Connection con = getConnection(); // 声明PreparedStatement PreparedStatement ps = null; // 声明结果集 ResultSet rs = null; // 如果连接不为空 if (con != null) { try { // 注入SQL ps = con.prepareStatement(sql); // 注入参数 insertParams(ps, params); // 获取结果集 rs = ps.executeQuery(); // 获取结果集元数据 ResultSetMetaData rsmd = rs.getMetaData(); // 获取列的信息 HashMap<String, Integer> columnInfo = getColumnInfo(rsmd); // 获取列名 Set<String> columnNames = columnInfo.keySet(); // 循环结果集 while (rs.next()) { // 实例化一个泛型对象 T t = clazz.newInstance(); // 定义为Object类型,以至于可以接收多种数据类型 Object value = null; for (String columnName : columnNames) { // 获取列的类型,调用ResultSet对应的set方法来获取value int type = columnInfo.get(columnName); switch (type) { case Types.INTEGER: value = rs.getInt(columnName); break; case Types.DOUBLE:// SQL中的Float与Types.Double对应 value = rs.getDouble(columnName); break; case Types.VARCHAR: value = rs.getString(columnName); break; case Types.TIMESTAMP: value = rs.getTimestamp(columnName); default: value = rs.getObject(columnName); break; } String name = "set" + UpperFirstCase(columnName); // 获取泛型实例对象的对应方法并执行 Method method = null; try { method = clazz.getDeclaredMethod(name, new Class<?>[] { value.getClass() }); } catch (NoSuchMethodException e) { // 如果无法获取方法,可能是因为参数是基本类型,调用changeToBasicClass()方法 try { method = clazz .getDeclaredMethod( name, new Class<?>[] { changeToBasicClass(value) }); } catch (NoSuchMethodException e1) { e1.printStackTrace(); } } method.invoke(t, value); } // 将此泛型实例添加到结果集合中 result.add(t); } } catch (SQLException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { closeAll(con, ps, rs); } } return result; }
不过有一点东西很蛋疼, 那就是
public void a(int b){} public void a(Integer b){}
在Java看来,这是两个方法不同的方法, 它们之间是重载关系....
还有一点小发现...自从在编程这方面发展后...记忆力越来越差了...
本文出自 “异性恋恐惧症” 博客,请务必保留此出处http://twoxzi.blog.51cto.com/8613587/1384768
Java 反射中的getDeclaredMethod(String,Class<?>[])与NoSuchMethodExecption,布布扣,bubuko.com
Java 反射中的getDeclaredMethod(String,Class<?>[])与NoSuchMethodExecption