利用 Java 反射機制,遞迴遍歷物件屬性,支援基本類型、集合、Map、嵌套物件、日期等,格式化輸出至控制台或日誌。
package utils;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
public class MyTestUtil {
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
// 公開靜態方法:預設開啟遞迴
public static String objToStr(Object obj) {
return objToStr(obj, true);
}
// 核心遞迴方法
private static String objToStr(Object object, boolean recursion) {
if (object == null) return "null";
if (object instanceof Date) {
return new SimpleDateFormat(DATE_FORMAT).format((Date) object);
}
Class> clazz = object.getClass();
// 基本類型與 String 直接輸出
if (isSimpleType(clazz)) return String.valueOf(object);
// 陣列/集合
if (clazz.isArray() || object instanceof Collection) {
return collectionToStr(object, recursion);
}
// Map
if (object instanceof Map) {
return mapToStr((Map, ?>) object, recursion);
}
// 普通物件 (Bean)
return beanToStr(object, recursion);
}
// 基本類型判斷
private static boolean isSimpleType(Class> clazz) {
return clazz.isPrimitive() ||
clazz == String.class ||
Number.class.isAssignableFrom(clazz) ||
clazz == Boolean.class || clazz == Character.class;
}
// 集合/陣列處理
private static String collectionToStr(Object object, boolean recursion) {
Object[] arr = clazz.isArray() ? (Object[]) object : ((Collection>) object).toArray();
if (!recursion || isSimpleArray(arr)) return Arrays.toString(arr);
return complexArrayToStr(arr);
}
private static String complexArrayToStr(Object[] arr) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < arr.length; i++) {
sb.append("[").append(i).append("] -> ")
.append(objToStr(arr[i], false)).append(",\n");
}
return sb.toString();
}
// Map 處理
private static String mapToStr(Map, ?> map, boolean recursion) {
if (!recursion || isSimpleMap(map)) return simpleMapToStr(map);
return complexMapToStr(map);
}
// Bean 物件處理:反射獲取所有宣告欄位
private static String beanToStr(Object object, boolean recursion) {
StringBuilder sb = new StringBuilder();
sb.append(object.getClass().getSimpleName()).append("[\n");
Field[] fields = object.getClass().getDeclaredFields();
// 簡單類型優先顯示 (可選排序)
fields = sortFieldsByType(fields);
for (int i = 0; i < fields.length; i++) {
Field f = fields[i];
f.setAccessible(true); // 破壞封裝,訪問 private
if (f.getName().equals("serialVersionUID")) continue;
try {
Object val = f.get(object);
sb.append(" ").append(f.getName()).append(" = ");
if (isSimpleType(val) || !recursion) {
sb.append(val);
} else {
sb.append("\n ").append(objToStr(val, false));
}
} catch (IllegalAccessException e) {
sb.append("");
}
if (i < fields.length - 1) sb.append(",\n");
}
sb.append("\n]");
return sb.toString();
}
// 控制台美化列印
public static void print(Object obj) {
print(obj, "DEBUG", objToStr(obj));
}
public static void printWithSign(String sign, Object obj) {
print(obj, sign, objToStr(obj));
}
private static void print(Object obj, String sign, String content) {
String line = "=".repeat(50);
System.out.println(line + " " + obj.getClass().getSimpleName() + " >> " + sign + " " + line);
System.out.println(content);
System.out.println(line.repeat(2));
}
}
private static Element loadRoot() {
InputStream dtd = ConfigFactory.class.getResourceAsStream("/mycat.dtd");
InputStream xml = ConfigFactory.class.getResourceAsStream("/mycat.xml");
return ConfigUtil.getDocument(dtd, xml).getDocumentElement();
}
// 多個業務方法反覆呼叫 loadRoot(),每次都重新讀取檔案
// 靜態初始化塊:啟動時讀一次,緩存到 byte[]
private static byte[] xmlBuffer, dtdBuffer;
private static ByteArrayOutputStream xmlBaos, dtdBaos;
static {
bufferFile(ConfigFactory.class.getResourceAsStream("/mycat.dtd"), dtdBuffer, dtdBaos);
bufferFile(ConfigFactory.class.getResourceAsStream("/mycat.xml"), xmlBuffer, xmlBaos);
}
private static void bufferFile(InputStream in, byte[] buf, ByteArrayOutputStream baos) {
try {
int len;
while ((len = in.read(buf)) > -1) baos.write(buf, 0, len);
baos.flush();
} catch (IOException e) { /* log error */ }
}
// 後續使用:從 byte[] 重建 InputStream,無需重讀磁碟
private static Element loadRoot() {
InputStream xml = new ByteArrayInputStream(xmlBaos.toByteArray());
InputStream dtd = new ByteArrayInputStream(dtdBaos.toByteArray());
return ConfigUtil.getDocument(dtd, xml).getDocumentElement();
}
原理:ByteArrayOutputStream 將輸入流寫入記憶體緩衝區 (byte[]);ByteArrayInputStream 從 byte[] 建立新輸入流。同一 byte[] 可無限次建立獨立 InputStream。
private static Document document; // 緩存解析後的 DOM 樹
private static Element loadRoot() {
if (document == null) {
InputStream dtd = getResource("/mycat.dtd");
InputStream xml = getResource("/mycat.xml");
document = ConfigUtil.getDocument(dtd, xml); // 只解析一次
}
return document.getDocumentElement();
}
優勢:避免重複建立 InputStream、重複解析 XML,效能最佳,語義最清晰。
| 類別 | 建構子 | 關鍵方法 |
|---|---|---|
| ByteArrayOutputStream | () (32 bytes)、(int size) |
write(byte[], offset, len)、toByteArray()、toString()、reset() |
| ByteArrayInputStream | (byte[] buf)、(byte[] buf, offset, length) |
繼承 InputStream:read()、available()、reset() (回到起始) |
new ByteArrayInputStream("test data".getBytes())objectMapper.writeValueAsString(obj) 產出 JSON,結構化、可攜帶、前端友善%msg、%ex、JSON Layout