Java 反射中的getDeclaredMethod(String,Class<?>[])與NoSuchMethodExecption

前幾天筆者在寫了一個比較簡陋的底層數據庫封裝類, 但測試時出現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<?>[])方法又區分基本類型和其對應的封裝類.

經過一翻探究後想出了三種解決方法:

  1. 使用getDeclaredMethods()方法, 通過遍歷的方式對方法名進行匹配, 可在循環內部添加條件來提高匹配精度. 但效率比較低.

  2. 約定類的方法的參數類型不能使用基本類型,只能用其對應的封裝類(別笑...這種方法很有效...其實我當時忍不住要笑...)

  3. (也就是我最後採取的方法). 通過以下方法, 傳入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看來,這是兩個方法不同的方法, 它們之間是重載關係....

還有一點小發現...自從在編程這方面發展後...記憶力越來越差了...

        

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章