#推薦(免費):java基礎教學
反射是Java 的進階技巧,大量地用在各種開源專案上。例如,Spring、Tomcat、Jetty 等等專案中,都大量地用到了反射。
身為 Java 程式設計師,我們如果用好反射,不但能提升自己的技術水平,還能開發出更好的專案。
然而,雖然很多人聽過反射,但卻不知道該用在哪裡。
那麼,我們就從實際工作出發,使用反射,把物件轉換成 MongoDb 的資料結構。當你在搞懂這個例子後,你就能明白反射是怎麼個用法。
在電商系統中,有些資料要儲存到 MongoDb 中,以此來提高查詢的效能。但在此之前,我們必須先把資料轉換成 MongoDb 的結構,也就是把 Java 物件轉換成 Document。
例如,訂單資訊要存到 MongoDb 中,就得把訂單物件轉換成 Document。
可這樣一來,每個實體類別都得發展一個 2Doc() 方法。這個方法毫無技術含量,就是把各種欄位 put 到 Document 裡面。而且一旦字段多了,一不留神就會寫錯代碼,你感受一下。
public class Order { private Long id; private Long userId; private String orderNo; private BigDecimal amount; private String createTime; private String updateTime; // 省略无数字段 // 转换方法:订单转doc public Document order2Doc(Order order) { Document doc = new Document(); doc.put("id", order.getId()); doc.put("userId", order.getUserId()); doc.put("orderNo", order.getOrderNo()); doc.put("amount", order.getAmount()); doc.put("createTime", order.getCreateTime()); doc.put("updateTime", order.getUpdateTime()); // 省略无数put... return doc; } }
除此之外,我們還得從 MongoDb 中取數據,把 Document 轉換回 Java 對象,你再感受一下。
public class Order { private Long id; private Long userId; private String orderNo; private BigDecimal amount; private String createTime; private String updateTime; // 省略无数字段 // 转换方法:doc转订单 public Order doc2Order(Document doc) { Order order = new Order(); order.setId((Long) doc.get("id")); order.setUserId((Long) doc.get("userId")); order.setOrderNo((String) doc.get("orderNo")); order.setAmount((BigDecimal) doc.get("amount")); order.setCreateTime((String) doc.get("createTime")); order.setUpdateTime((String) doc.get("updateTime")); // 省略无数set... return order; } }
光是一個訂單類別都這麼麻煩了,何況這樣的類別不只一個,而且專案總有新需求,如果一個字段改了,那你麻煩大了,說不定要把整個專案翻一遍。
因此,為了少出錯,必須優化這兩個轉換方法,而這次優化用到了 Java 的兩個高階特性:反射、泛型。為了讓大家更直覺的了解,我將分成兩個版本迭代。
第一版,利用反射,簡化實體類別的轉換方法;第二版,利用泛型、反射,提取MongoDb 工具類別;
接下來,我們就一步步迭代吧~
在第一版的迭代中,我們要簡化實體類別的兩個轉換方法。
我們先從 Java 物件轉 Document 開始,還是以 Order 類別為例。
首先,我們透過反射,取得到訂單類別的所有欄位資訊;然後,使用循環遍歷這些欄位;最後,在循環中,我們放開欄位的存取權限,把欄位 put 到 Document 裡面。
public class Order { // ...省略无数字段 public Document order2Doc(Order order) throws Exception { Document doc = new Document(); // 获取所有字段:通过 getClass() 方法获取 Class 对象,然后获取这个类所有字段 Field[] fields = order.getClass().getDeclaredFields(); for (Field field : fields) { // 开放字段操作权限 field.setAccessible(true); // 设置值 doc.put(field.getName(), field.get(order)); } return doc; } }
你可以看到,經過反射改造後,程式碼簡單了許多。一個物件無論有多少個字段,要寫多少 put 操作,只要這幾行程式碼就能搞定。 Java 物件轉換成 MongoDb 的結構,看起來也不那麼麻煩了。
照著這個思路,我們再來改造第二個方法,Document 轉 Java 物件。
public class Order { // ...省略无数字段 public Order doc2Order(Document doc) throws Exception { Order order = new Order(); for (String key : doc.keySet()) { // 获取字段 Field field = order.getClass().getDeclaredField(key); // 开放字段操作权限 field.setAccessible(true); // 设置值 field.set(order, doc.get(key)); } return order; } }
首先,我們使用循環遍歷 Document;在循環中,使用反射來取得對應的字段,再放開字段的存取權限,把 Document 的值設定到物件的字段裡。
到了這兒,我們利用反射,簡化了兩個實體類別的轉換方法,第一版的迭代基本上完成了。剩下的工作,就是複製貼上,把各個類別重新改造一遍。
然而,經過這一版迭代,雖然減少了很多工作,但仍然有很多不合理的地方。
首先,重複程式碼還是很多。每個實體類別都有兩個轉換方法,但這兩個方法的核心邏輯是一樣的,完全沒必要到處複製。
然後,這不是實體類別應該承擔的功能。實體類別只負責短暫保留數據,不負責任何持久化功能。你把資料存到哪裡,該轉換成什麼資料結構,這跟實體類別沒什麼關係。
換句話說,我們還得做第二次迭代。
簡單來說,泛型是一種風格或範式,你不用一開始就指明具體的參數類型,而是在使用的時候再確定參數型別。
如果把泛型、反射結合在一起,能幫我們減少很多重複程式碼。
我們來看看,該怎麼做第二次迭代?
先從 Java 物件轉 Document 開始。我們先宣告一個泛型方法;然後,透過反射,取得泛型類別的所有欄位信息,再使用迴圈遍歷這些欄位;最後,在迴圈中,把欄位 put 到 Document 裡面。
public class MongoDbUtils { // 定义泛型方法: // 1. 在返回值前,声明泛型参数 <参数名>; // 2. 传入参数时,指定一个泛型参数 public static <T> Document obj2Doc(T obj) throws Exception { Document doc = new Document(); // 获取所有字段:通过 getClass() 方法获取 Class 对象,然后获取这个类所有字段 Field[] fields = obj.getClass().getDeclaredFields(); for (Field field : fields) { // 开放字段操作权限 field.setAccessible(true); // 设置值 doc.put(field.getName(), field.get(obj)); } return doc; } }
加入泛型後,重複程式碼大量減少了,實體類別不用再單獨寫 2Doc()
方法了。使用的時候,只要呼叫 MongoDbUtils.obj2Doc()
就行。
依照同樣的思路,我們繼續來改造第二個方法,Document 轉換 Java 物件。
public class MongoDbUtils { // 定义泛型方法: // 1. 在返回值前,声明泛型参数 <参数名>; // 2. 传入参数必须是 Class,但这个 Class 是泛型参数,不限制类型 public static <T> T doc2Obj(Document doc, Class<T> clazz) throws Exception { // 实例化泛型对象 T obj = clazz.newInstance(); for (String key : doc.keySet()) { // 获取字段 Field field = clazz.getDeclaredField(key); // 开放字段操作权限 field.setAccessible(true); // 设置值 field.set(obj, doc.get(key)); } return obj; } }
首先,我们定义实例化一个泛型对象;然后,我们使用循环遍历 Document;最后,在循环中,使用反射获取相应的字段,把 Document 的值设置到泛型对象的字段里。
第二版的迭代就基本完成了。我们在第一版迭代的基础上,加入了泛型,得到了一个工具类 MongoDbUtils
,这个工具类得到结果和以前完全一样,你可以看下测试代码。
public static void main(String[] args) throws Exception { Order order = new Order(); order.setId(0L); order.setUserId(0L); order.setOrderNo("1"); order.setAmount(new BigDecimal("0")); order.setCreateTime("2"); order.setUpdateTime("3"); System.out.println("原始数据:" + order); Document document = MongoDbUtils.obj2Doc(order); System.out.println("转换doc数据:" + document); Order order1 = MongoDbUtils.doc2Obj(document, Order.class); System.out.println("转换java数据:" + order1); } 运行结果: 原始数据:Order(id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3) 转换doc数据:Document{{id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3}} 转换java数据:Order(id=0, userId=0, orderNo=1, amount=0, createTime=2, updateTime=3)
这样一来,我们就不用保留实体类上的转换方法了,剩下的工作就是删代码。
MongoDb 和 Java 对象的互相转换就完成了。我们做了两次迭代,第一次迭代利用了反射,把大量手动 set/get 操作给去掉了;第二次迭代在原来的基础上,加入了泛型的应用,又去掉了一堆重复代码。
以上是Java使用反射,把物件轉換成 MongoDb 的結構的詳細內容。更多資訊請關注PHP中文網其他相關文章!