如何在未知类型的情况下实例化Java对象?附ResultSet转JSON需求
你这个需求很务实啊——既要通用化处理ResultSet转JSON,又要考虑后续对象实例化的问题,我刚好有过类似的实践,给你一步步拆解:
一、用JsonSimple 1.1实现通用ResultSet转JSON
核心思路就是利用ResultSetMetaData获取结果集的列信息(列名、类型),然后遍历每一行数据,动态把列名和对应值塞进JSON对象里,最后把所有行打包成JSON数组。这样不管是什么表的结果集,都能直接复用这个方法,不用写重复代码。
下面是完整的实现代码:
import org.json.simple.JSONArray; import org.json.simple.JSONObject; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.Date; public class ResultSetToJsonConverter { @SuppressWarnings("unchecked") public static JSONArray convertResultSetToJson(ResultSet rs) throws SQLException { JSONArray jsonArray = new JSONArray(); ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount(); // 遍历结果集的每一行 while (rs.next()) { JSONObject jsonObject = new JSONObject(); // 遍历每一列 for (int i = 1; i <= columnCount; i++) { String columnName = metaData.getColumnLabel(i); // 用getColumnLabel避免别名问题 Object columnValue = rs.getObject(i); // 处理不同数据类型的转换,适配JsonSimple的类型支持 if (columnValue instanceof Date) { // 日期类型可以转成字符串,或者时间戳,根据需求调整 jsonObject.put(columnName, ((Date) columnValue).toString()); } else if (columnValue != null) { // 其他非空类型直接放入,JsonSimple会自动处理基本类型的包装类 jsonObject.put(columnName, columnValue); } else { // 空值可以放入null,或者根据业务需求设为默认值 jsonObject.put(columnName, null); } } jsonArray.add(jsonObject); } return jsonArray; } }
代码说明:
- 用
getColumnLabel而不是getColumnName,这样如果SQL里用了列别名,也能正确获取到显示的名称; - 单独处理
Date类型是因为JsonSimple对日期的序列化需要手动调整(默认可能转成奇怪的格式),你也可以改成转成时间戳或者自定义日期格式; - 空值处理:如果数据库字段为null,直接放入
null,也可以根据业务需求换成空字符串之类的默认值; - 这个方法是完全通用的,不管你的结果集来自哪个表,只要传进去就能得到结构正确的JSON数组。
二、未知类型下实例化Java对象的方案
你问的“未知类型下实例化Java对象”,其实核心是通过反射根据类型信息动态创建对象,结合数据库场景的话,一般是根据ResultSet的列类型映射到对应的Java类型,然后实例化对象并赋值。
两种常见场景的实现:
1. 已知目标对象的Class,但需要动态创建实例
比如你知道要转成某个实体类,但不想硬编码构造方法,可以用反射:
public static <T> T instantiateObject(Class<T> clazz) throws Exception { // 优先调用无参构造方法 return clazz.getDeclaredConstructor().newInstance(); }
如果类没有无参构造,那你需要获取对应的Constructor,比如带参数的:
// 假设知道构造参数的类型和值 Constructor<T> constructor = clazz.getDeclaredConstructor(String.class, Integer.class); T instance = constructor.newInstance("张三", 25);
2. 完全不知道类型,从ResultSet元数据推导Java类型并实例化
这种情况需要先把SQL类型映射到Java类型,比如SQL的VARCHAR对应String,INT对应Integer,DATE对应Date等,然后动态创建对象(一般是Map或者通用Bean)。如果要转成自定义实体类,还是需要提前知道Class,不然没法确定对象结构。
比如可以先做一个SQL类型到Java类型的映射:
private static final Map<Integer, Class<?>> SQL_TYPE_TO_JAVA_TYPE = new HashMap<>(); static { SQL_TYPE_TO_JAVA_TYPE.put(java.sql.Types.VARCHAR, String.class); SQL_TYPE_TO_JAVA_TYPE.put(java.sql.Types.INTEGER, Integer.class); SQL_TYPE_TO_JAVA_TYPE.put(java.sql.Types.DATE, Date.class); // 可以继续添加其他类型映射,比如BIGINT对应Long,BOOLEAN对应Boolean等 }
然后根据元数据获取列对应的Java类型,创建对象并赋值:
public static Object createObjectFromResultSetColumn(ResultSetMetaData metaData, int columnIndex) throws Exception { int sqlType = metaData.getColumnType(columnIndex); Class<?> javaType = SQL_TYPE_TO_JAVA_TYPE.get(sqlType); if (javaType != null) { // 如果是基本类型的包装类,直接实例化(比如Integer可以用默认值0,或者根据需求调整) if (javaType == String.class) { return ""; } else if (Number.class.isAssignableFrom(javaType)) { return javaType.getConstructor(String.class).newInstance("0"); } else if (javaType == Date.class) { return new Date(); } } // 默认返回null或者其他默认值 return null; }
补充说明:
如果你的需求是把ResultSet直接转成Java实体类的列表,其实可以考虑用ORM框架(比如MyBatis),但既然你现在用JsonSimple,那上面的反射方案足够应付通用场景。如果只是为了给同事对接服务器,其实转成JSON已经足够了,同事那边直接解析JSON就行,不一定需要实例化Java对象~
内容的提问来源于stack exchange,提问作者Dave C

