翁取一葫蘆置于地,以錢覆其口,徐以杓酌油瀝之,自錢孔入,而錢不濕。因曰:“我亦無他,唯手熟爾?!? 編寫代碼的"老司機"也是如">
×

Java 編程技巧之數(shù)據(jù)結(jié)構(gòu)

分類:互聯(lián)網(wǎng)熱點 編輯:聊聊云計算 瀏覽量:263
2020-07-10 11:21:32
## 導讀 唐宋八大家之一歐陽修在《賣油翁》中寫道: > 翁取一葫蘆置于地,以錢覆其口,徐以杓酌油瀝之,自錢孔入,而錢不濕。因曰:“我亦無他,唯手熟爾?!?編寫代碼的"老司機"也是如此,"老司機"之所以被稱為"老司機",原因也是"無他,唯手熟爾"。編碼過程中踩過的坑多了,獲得的編碼經(jīng)驗也就多了,總結(jié)的編碼技巧也就更多了??偨Y(jié)的編碼技巧多了,凡事又能夠舉一反三,編碼的速度自然就上來了。筆者從數(shù)據(jù)結(jié)構(gòu)的角度,整理了一些Java編程技巧,以供大家學習參考。 ## 1.使用HashSet判斷主鍵是否存在 HashSet實現(xiàn)Set接口,由哈希表(實際上是HashMap)支持,但不保證set 的迭代順序,并允許使用null元素。HashSet的時間復雜度跟HashMap一致,如果沒有哈希沖突則時間復雜度為O(1),如果存在哈希沖突則時間復雜度不超過O(n)。所以,在日常編碼中,可以使用HashSet判斷主鍵是否存在。 **案例:**給定一個字符串(不一定全為字母),請返回第一個重復出現(xiàn)的字符。 ```java /** 查找第一個重復字符 */ public static Character findFirstRepeatedChar(String string) { // 檢查空字符串 if (Objects.isNull(string) || string.isEmpty()) { return null; } // 查找重復字符 char[] charArray = string.toCharArray(); Set charSet = new HashSet<>(charArray.length); for (char ch : charArray) { if (charSet.contains(ch)) { return ch; } charSet.add(ch); } // 默認返回為空 return null; } ``` 其中,由于Set的add函數(shù)有個特性——如果添加的元素已經(jīng)再集合中存在,則會返回false??梢院喕a為: ```java if (!charSet.add(ch)) { return ch; } ``` ## 2.使用HashMap存取鍵值映射關(guān)系 簡單來說,HashMap由數(shù)組和鏈表組成的,數(shù)組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的。如果定位到的數(shù)組位置不含鏈表,那么查找、添加等操作很快,僅需一次尋址即可,其時間復雜度為O(1);如果定位到的數(shù)組包含鏈表,對于添加操作,其時間復雜度為O(n)——首先遍歷鏈表,存在即覆蓋,不存在則新增;對于查找操作來講,仍需要遍歷鏈表,然后通過key對象的equals方法逐一對比查找。從性能上考慮,HashMap中的鏈表出現(xiàn)越少,即哈希沖突越少,性能也就越好。所以,在日常編碼中,可以使用HashMap存取鍵值映射關(guān)系。 **案例:**給定菜單記錄列表,每條菜單記錄中包含父菜單標識(根菜單的父菜單標識為null),構(gòu)建出整個菜單樹。 ```java /** 菜單DO類 */ @Setter @Getter @ToString public static class MenuDO { /** 菜單標識 */ private Long id; /** 菜單父標識 */ private Long parentId; /** 菜單名稱 */ private String name; /** 菜單鏈接 */ private String url; } /** 菜單VO類 */ @Setter @Getter @ToString public static class MenuVO { /** 菜單標識 */ private Long id; /** 菜單名稱 */ private String name; /** 菜單鏈接 */ private String url; /** 子菜單列表 */ private List childList; } /** 構(gòu)建菜單樹函數(shù) */ public static List buildMenuTree(List menuList) { // 檢查列表為空 if (CollectionUtils.isEmpty(menuList)) { return Collections.emptyList(); } // 依次處理菜單 int menuSize = menuList.size(); List rootList = new ArrayList<>(menuSize); Map menuMap = new HashMap<>(menuSize); for (MenuDO menuDO : menuList) { // 賦值菜單對象 Long menuId = menuDO.getId(); MenuVO menu = menuMap.get(menuId); if (Objects.isNull(menu)) { menu = new MenuVO(); menu.setChildList(new ArrayList<>()); menuMap.put(menuId, menu); } menu.setId(menuDO.getId()); menu.setName(menuDO.getName()); menu.setUrl(menuDO.getUrl()); // 根據(jù)父標識處理 Long parentId = menuDO.getParentId(); if (Objects.nonNull(parentId)) { // 構(gòu)建父菜單對象 MenuVO parentMenu = menuMap.get(parentId); if (Objects.isNull(parentMenu)) { parentMenu = new MenuVO(); parentMenu.setId(parentId); parentMenu.setChildList(new ArrayList<>()); menuMap.put(parentId, parentMenu); } // 添加子菜單對象 parentMenu.getChildList().add(menu); } else { // 添加根菜單對象 rootList.add(menu); } } // 返回根菜單列表 return rootList; } ``` ## 3.使用ThreadLocal存儲線程專有對象 ThreadLocal提供了線程專有對象,可以在整個線程生命周期中隨時取用,極大地方便了一些邏輯的實現(xiàn)。 常見的ThreadLocal用法主要有兩種: 1. 保存線程上下文對象,避免多層級參數(shù)傳遞; 2. 保存非線程安全對象,避免多線程并發(fā)調(diào)用。 ### 3.1.保存線程上下文對象,避免多層級參數(shù)傳遞 這里,以PageHelper插件的源代碼中的分頁參數(shù)設置與使用為例說明。 **設置分頁參數(shù)代碼:** ```java /** 分頁方法類 */ public abstract class PageMethod { /** 本地分頁 */ protected static final ThreadLocal LOCAL_PAGE = new ThreadLocal(); /** 設置分頁參數(shù) */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** 獲取分頁參數(shù) */ public static Page getLocalPage() { return LOCAL_PAGE.get(); } /** 開始分頁 */ public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page page = new Page(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; } } ``` **使用分頁參數(shù)代碼:** ```java /** 虛輔助方言類 */ public abstract class AbstractHelperDialect extends AbstractDialect implements Constant { /** 獲取本地分頁 */ public Page getLocalPage() { return PageHelper.getLocalPage(); } /** 獲取分頁SQL */ @Override public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) { String sql = boundSql.getSql(); Page page = getLocalPage(); String orderBy = page.getOrderBy(); if (StringUtil.isNotEmpty(orderBy)) { pageKey.update(orderBy); sql = OrderByParser.converToOrderBySql(sql, orderBy); } if (page.isOrderByOnly()) { return sql; } return getPageSql(sql, page, pageKey); } ... } ``` **使用分頁插件代碼:** ```java /** 查詢用戶函數(shù) */ public PageInfo queryUser(UserQuery userQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List userList = userDAO.queryUser(userQuery); PageInfo pageInfo = new PageInfo<>(userList); return pageInfo; } ``` 如果要把分頁參數(shù)通過函數(shù)參數(shù)逐級傳給查詢語句,除非修改MyBatis相關(guān)接口函數(shù),否則是不可能實現(xiàn)的。 ### 3.2.保存非線程安全對象,避免多線程并發(fā)調(diào)用 在寫日期格式化工具函數(shù)時,首先想到的寫法如下: ```java /** 日期模式 */ private static final String DATE_PATTERN = "yyyy-MM-dd"; /** 格式化日期函數(shù) */ public static String formatDate(Date date) { return new SimpleDateFormat(DATE_PATTERN).format(date); } ``` 其中,每次調(diào)用都要初始化DateFormat導致性能較低,把DateFormat定義成常量后的寫法如下: ```java /** 日期格式 */ private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); /** 格式化日期函數(shù) */ public static String formatDate(Date date) { return DATE_FORMAT.format(date); } ``` 由于SimpleDateFormat是非線程安全的,當多線程同時調(diào)用formatDate函數(shù)時,會導致返回結(jié)果與預期不一致。如果采用ThreadLocal定義線程專有對象,優(yōu)化后的代碼如下: ```java /** 本地日期格式 */ private static final ThreadLocal LOCAL_DATE_FORMAT = new ThreadLocal() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; /** 格式化日期函數(shù) */ public static String formatDate(Date date) { return LOCAL_DATE_FORMAT.get().format(date); } ``` 這是在沒有線程安全的日期格式化工具類之前的實現(xiàn)方法。在JDK8以后,建議使用DateTimeFormatter代替SimpleDateFormat,因為SimpleDateFormat是線程不安全的,而DateTimeFormatter是線程安全的。當然,也可以采用第三方提供的線程安全日期格式化函數(shù),比如apache的DateFormatUtils工具類。 **注意:**ThreadLocal有一定的內(nèi)存泄露的風險,盡量在業(yè)務代碼結(jié)束前調(diào)用remove函數(shù)進行數(shù)據(jù)清除。 ## 4.使用Pair實現(xiàn)成對結(jié)果的返回 在C/C++語言中,Pair(對)是將兩個數(shù)據(jù)類型組成一個數(shù)據(jù)類型的容器,比如std::pair。 Pair主要有兩種用途: 1. 把key和value放在一起成對處理,主要用于Map中返回名值對,比如Map中的Entry類; 2. 當一個函數(shù)需要返回兩個結(jié)果時,可以使用Pair來避免定義過多的數(shù)據(jù)模型類。 第一種用途比較常見,這里主要說明第二種用途。 ### 4.1.定義模型類實現(xiàn)成對結(jié)果的返回 **函數(shù)實現(xiàn)代碼:** ```java /** 點和距離類 */ @Setter @Getter @ToString @AllArgsConstructor public static class PointAndDistance { /** 點 */ private Point point; /** 距離 */ private Double distance; } /** 獲取最近點和距離 */ public static PointAndDistance getNearestPointAndDistance(Point point, Point[] points) { // 檢查點數(shù)組為空 if (ArrayUtils.isEmpty(points)) { return null; } // 獲取最近點和距離 Point nearestPoint = points; double nearestDistance = getDistance(point, points); for (int i = 1; i < points.length; i++) { double distance = getDistance(point, point); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point; } } // 返回最近點和距離 return new PointAndDistance(nearestPoint, nearestDistance); } ``` **函數(shù)使用案例:** ```java Point point = ...; Point[] points = ...; PointAndDistance pointAndDistance = getNearestPointAndDistance(point, points); if (Objects.nonNull(pointAndDistance)) { Point point = pointAndDistance.getPoint(); Double distance = pointAndDistance.getDistance(); ... } ``` ### 4.2.使用Pair類實現(xiàn)成對結(jié)果的返回 在JDK中,沒有提供原生的Pair數(shù)據(jù)結(jié)構(gòu),也可以使用Map::Entry代替。不過,Apache的commons-lang3包中的Pair類更為好用,下面便以Pair類進行舉例說明。 **函數(shù)實現(xiàn)代碼:** ```java /** 獲取最近點和距離 */ public static Pair getNearestPointAndDistance(Point point, Point[] points) { // 檢查點數(shù)組為空 if (ArrayUtils.isEmpty(points)) { return null; } // 獲取最近點和距離 Point nearestPoint = points; double nearestDistance = getDistance(point, points); for (int i = 1; i < points.length; i++) { double distance = getDistance(point, point); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point; } } // 返回最近點和距離 return Pair.of(nearestPoint, nearestDistance); } ``` **函數(shù)使用案例:** ```java Point point = ...; Point[] points = ...; Pair pair = getNearestPointAndDistance(point, points); if (Objects.nonNull(pair)) { Point point = pair.getLeft(); Double distance = pair.getRight(); ... } ``` ## 5.定義Enum類實現(xiàn)取值和描述 在C++、Java等計算機編程語言中,枚舉類型(Enum)是一種特殊數(shù)據(jù)類型,能夠為一個變量定義一組預定義的常量。在使用枚舉類型的時候,枚舉類型變量取值必須為其預定義的取值之一。 ### 5.1.用class關(guān)鍵字實現(xiàn)的枚舉類型 在JDK5之前,Java語言不支持枚舉類型,只能用類(class)來模擬實現(xiàn)枚舉類型。 ```java /** 訂單狀態(tài)枚舉 */ public final class OrderStatus { /** 屬性相關(guān) */ /** 狀態(tài)取值 */ private final int value; /** 狀態(tài)描述 */ private final String description; /** 常量相關(guān) */ /** 已創(chuàng)建(1) */ public static final OrderStatus CREATED = new OrderStatus(1, "已創(chuàng)建"); /** 進行中(2) */ public static final OrderStatus PROCESSING = new OrderStatus(2, "進行中"); /** 已完成(3) */ public static final OrderStatus FINISHED = new OrderStatus(3, "已完成"); /** 構(gòu)造函數(shù) */ private OrderStatus(int value, String description) { this.value = value; this.description = description; } /** 獲取狀態(tài)取值 */ public int getValue() { return value; } /** 獲取狀態(tài)描述 */ public String getDescription() { return description; } } ``` ### 5.2.用enum關(guān)鍵字實現(xiàn)的枚舉類型 JDK5提供了一種新的類型——Java的枚舉類型,關(guān)鍵字enum可以將一組具名的值的有限集合創(chuàng)建為一種新的類型,而這些具名的值可以作為常量使用,這是一種非常有用的功能。 ```java /** 訂單狀態(tài)枚舉 */ public enum OrderStatus { /** 常量相關(guān) */ /** 已創(chuàng)建(1) */ CREATED(1, "已創(chuàng)建"), /** 進行中(2) */ PROCESSING(2, "進行中"), /** 已完成(3) */ FINISHED(3, "已完成"); /** 屬性相關(guān) */ /** 狀態(tài)取值 */ private final int value; /** 狀態(tài)描述 */ private final String description; /** 構(gòu)造函數(shù) */ private OrderStatus(int value, String description) { this.value = value; this.description = description; } /** 獲取狀態(tài)取值 */ public int getValue() { return value; } /** 獲取狀態(tài)描述 */ public String getDescription() { return description; } } ``` 其實,Enum類型就是一個語法糖,編譯器幫我們做了語法的解析和編譯。通過反編譯,可以看到Java枚舉編譯后實際上是生成了一個類,該類繼承了 java.lang.Enum,并添加了values()、valueOf()等枚舉類型通用方法。 ## 6.定義Holder類實現(xiàn)參數(shù)的輸出 在很多語言中,函數(shù)的參數(shù)都有輸入(in)、輸出(out)和輸入輸出(inout)之分。在C/C++語言中,可以用對象的引用(&)來實現(xiàn)函數(shù)參數(shù)的輸出(out)和輸入輸出(inout)。但在Java語言中,雖然沒有提供對象引用類似的功能,但是可以通過修改參數(shù)的字段值來實現(xiàn)函數(shù)參數(shù)的輸出(out)和輸入輸出(inout)。這里,我們叫這種輸出參數(shù)對應的數(shù)據(jù)結(jié)構(gòu)為**Holder(支撐)類**。 **Holder類實現(xiàn)代碼:** ```java /** 長整型支撐類 */ @Getter @Setter @ToString public class LongHolder { /** 長整型取值 */ private long value; /** 構(gòu)造函數(shù) */ public LongHolder() {} /** 構(gòu)造函數(shù) */ public LongHolder(long value) { this.value = value; } } ``` **Holder類使用案例:** ```java /** 靜態(tài)常量 */ /** 頁面數(shù)量 */ private static final int PAGE_COUNT = 100; /** 最大數(shù)量 */ private static final int MAX_COUNT = 1000; /** 處理過期訂單 */ public void handleExpiredOrder() { LongHolder minIdHolder = new LongHolder(0L); for (int pageIndex = 0; pageIndex < PAGE_COUNT; pageIndex++) { if (!handleExpiredOrder(pageIndex, minIdHolder)) { break; } } } /** 處理過期訂單 */ private boolean handleExpiredOrder(int pageIndex, LongHolder minIdHolder) { // 獲取最小標識 Long minId = minIdHolder.getValue(); // 查詢過期訂單(按id從小到大排序) List orderList = orderDAO.queryExpired(minId, MAX_COUNT); if (CollectionUtils.isEmpty(taskTagList)) { return false; } // 設置最小標識 int orderSize = orderList.size(); minId = orderList.get(orderSize - 1).getId(); minIdHolder.setValue(minId); // 依次處理訂單 for (OrderDO order : orderList) { ... } // 判斷還有訂單 return orderSize >= PAGE_SIZE; } ``` 其實,可以實現(xiàn)一個泛型支撐類,適用于更多的數(shù)據(jù)類型。 ## 7.定義Union類實現(xiàn)數(shù)據(jù)體的共存 在C/C++語言中,聯(lián)合體(union),又稱共用體,類似結(jié)構(gòu)體(struct)的一種數(shù)據(jù)結(jié)構(gòu)。聯(lián)合體(union)和結(jié)構(gòu)體(struct)一樣,可以包含很多種數(shù)據(jù)類型和變量,兩者區(qū)別如下: 1. **結(jié)構(gòu)體(struct)**中所有變量是“共存”的,同時所有變量都生效,各個變量占據(jù)不同的內(nèi)存空間; 2. **聯(lián)合體(union)**中是各變量是“互斥”的,同時只有一個變量生效,所有變量占據(jù)同一塊內(nèi)存空間。 當多個數(shù)據(jù)需要共享內(nèi)存或者多個數(shù)據(jù)每次只取其一時,可以采用聯(lián)合體(union)。 在Java語言中,沒有聯(lián)合體(union)和結(jié)構(gòu)體(struct)概念,只有類(class)的概念。眾所眾知,結(jié)構(gòu)體(struct)可以用類(class)來實現(xiàn)。其實,聯(lián)合體(union)也可以用類(class)來實現(xiàn)。但是,這個類不具備“多個數(shù)據(jù)需要共享內(nèi)存”的功能,只具備“多個數(shù)據(jù)每次只取其一”的功能。 這里,以微信協(xié)議的客戶消息為例說明。根據(jù)我多年來的接口協(xié)議封裝經(jīng)驗,主要有以下兩種實現(xiàn)方式。 ### 7.1.使用函數(shù)方式實現(xiàn)Union **Union類實現(xiàn):** ```java /** 客戶消息類 */ @ToString public class CustomerMessage { /** 屬性相關(guān) */ /** 消息類型 */ private String msgType; /** 目標用戶 */ private String toUser; /** 共用體相關(guān) */ /** 新聞內(nèi)容 */ private News news; ... /** 常量相關(guān) */ /** 新聞消息 */ public static final String MSG_TYPE_NEWS = "news"; ... /** 構(gòu)造函數(shù) */ public CustomerMessage() {} /** 構(gòu)造函數(shù) */ public CustomerMessage(String toUser) { this.toUser = toUser; } /** 構(gòu)造函數(shù) */ public CustomerMessage(String toUser, News news) { this.toUser = toUser; this.msgType = MSG_TYPE_NEWS; this.news = news; } /** 清除消息內(nèi)容 */ private void removeMsgContent() { // 檢查消息類型 if (Objects.isNull(msgType)) { return; } // 清除消息內(nèi)容 if (MSG_TYPE_NEWS.equals(msgType)) { news = null; } else if (...) { ... } msgType = null; } /** 檢查消息類型 */ private void checkMsgType(String msgType) { // 檢查消息類型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息類型為空"); } // 比較消息類型 if (!Objects.equals(msgType, this.msgType)) { throw new IllegalArgumentException("消息類型不匹配"); } } /** 設置消息類型函數(shù) */ public void setMsgType(String msgType) { // 清除消息內(nèi)容 removeMsgContent(); // 檢查消息類型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息類型為空"); } // 賦值消息內(nèi)容 this.msgType = msgType; if (MSG_TYPE_NEWS.equals(msgType)) { news = new News(); } else if (...) { ... } else { throw new IllegalArgumentException("消息類型不支持"); } } /** 獲取消息類型 */ public String getMsgType() { // 檢查消息類型 if (Objects.isNull(msgType)) { throw new IllegalArgumentException("消息類型無效"); } // 返回消息類型 return this.msgType; } /** 設置新聞 */ public void setNews(News news) { // 清除消息內(nèi)容 removeMsgContent(); // 賦值消息內(nèi)容 this.msgType = MSG_TYPE_NEWS; this.news = news; } /** 獲取新聞 */ public News getNews() { // 檢查消息類型 checkMsgType(MSG_TYPE_NEWS); // 返回消息內(nèi)容 return this.news; } ... } ``` **Union類使用:** ```java String accessToken = ...; String toUser = ...; List articleList = ...; News news = new News(articleList); CustomerMessage customerMessage = new CustomerMessage(toUser, news); wechatApi.sendCustomerMessage(accessToken, customerMessage); ``` **主要優(yōu)缺點:** - 優(yōu)點:更貼近C/C++語言的聯(lián)合體(union); - 缺點:實現(xiàn)邏輯較為復雜,參數(shù)類型驗證較多。 ### 7.2.使用繼承方式實現(xiàn)Union **Union類實現(xiàn):** ```java /** 客戶消息類 */ @Getter @Setter @ToString public abstract class CustomerMessage { /** 屬性相關(guān) */ /** 消息類型 */ private String msgType; /** 目標用戶 */ private String toUser; /** 常量相關(guān) */ /** 新聞消息 */ public static final String MSG_TYPE_NEWS = "news"; ... /** 構(gòu)造函數(shù) */ public CustomerMessage(String msgType) { this.msgType = msgType; } /** 構(gòu)造函數(shù) */ public CustomerMessage(String msgType, String toUser) { this.msgType = msgType; this.toUser = toUser; } } /** 新聞客戶消息類 */ @Getter @Setter @ToString(callSuper = true) public class NewsCustomerMessage extends CustomerMessage { /** 屬性相關(guān) */ /** 新聞內(nèi)容 */ private News news; /** 構(gòu)造函數(shù) */ public NewsCustomerMessage() { super(MSG_TYPE_NEWS); } /** 構(gòu)造函數(shù) */ public NewsCustomerMessage(String toUser, News news) { super(MSG_TYPE_NEWS, toUser); this.news = news; } } ``` **Union類使用:** ```java String accessToken = ...; String toUser = ...; List articleList = ...; News news = new News(articleList); CustomerMessage customerMessage = new NewsCustomerMessage(toUser, news); wechatApi.sendCustomerMessage(accessToken, customerMessage); ``` **主要優(yōu)缺點:** - 優(yōu)點:使用虛基類和子類進行拆分,各個子類對象的概念明確; - 缺點:與C/C++語言的聯(lián)合體(union)差別大,但是功能上大體一致。 在C/C++語言中,聯(lián)合體并不包括聯(lián)合體當前的數(shù)據(jù)類型。但在上面實現(xiàn)的Java聯(lián)合體中,已經(jīng)包含了聯(lián)合體對應的數(shù)據(jù)類型。所以,從嚴格意義上說,Java聯(lián)合體并不是真正的聯(lián)合體,只是一個具備“多個數(shù)據(jù)每次只取其一”功能的類。 ## 8.使用泛型屏蔽類型的差異性 在C++語言中,有個很好用的**模板(template)**功能,可以編寫帶有參數(shù)化類型的通用版本,讓編譯器自動生成針對不同類型的具體版本。而在Java語言中,也有一個類似的功能叫**泛型(generic)**。在編寫類和方法的時候,一般使用的是具體的類型,而用泛型可以使類型參數(shù)化,這樣就可以編寫更通用的代碼。 許多人都認為,C++模板(template)和Java泛型(generic)兩個概念是等價的,其實實現(xiàn)機制是完全不同的。C++模板是一套宏指令集,編譯器會針對每一種類型創(chuàng)建一份模板代碼副本;Java泛型的實現(xiàn)基于"類型擦除"概念,本質(zhì)上是一種進行類型限制的語法糖。 ### 8.1.泛型類 以支撐類為例,定義泛型的通用支撐類: ```java /** 通用支撐類 */ @Getter @Setter @ToString public class GenericHolder { /** 通用取值 */ private T value; /** 構(gòu)造函數(shù) */ public GenericHolder() {} /** 構(gòu)造函數(shù) */ public GenericHolder(T value) { this.value = value; } } ``` ### 8.2.泛型接口 定義泛型的數(shù)據(jù)提供者接口: ```java /** 數(shù)據(jù)提供者接口 */ public interface DataProvider { /** 獲取數(shù)據(jù)函數(shù) */ public T getData(); } ``` ### 8.3.泛型方法 定義泛型的淺拷貝函數(shù): ```java /** 淺拷貝函數(shù) */ public static T shallowCopy(Object source, Class clazz) throws BeansException { // 判斷源對象 if (Objects.isNull(source)) { return null; } // 新建目標對象 T target; try { target = clazz.newInstance(); } catch (Exception e) { throw new BeansException("新建類實例異常", e); } // 拷貝對象屬性 BeanUtils.copyProperties(source, target); // 返回目標對象 return target; } ``` ### 8.4.泛型通配符 泛型通配符一般是使用"?"代替具體的類型實參,可以把"?"看成所有類型的父類。當具體類型不確定的時候,可以使用泛型通配符 "?";當不需要使用類型的具體功能,只使用Object類中的功能時,可以使用泛型通配符 "?"。 ```java /** 打印取值函數(shù) */ public static void printValue(GenericHolder holder) { System.out.println(holder.getValue()); } /** 主函數(shù) */ public static void main(String[] args) { printValue(new GenericHolder<>(12345)); printValue(new GenericHolder<>("abcde")); } ``` 在Java規(guī)范中,不建議使用泛型通配符"?",上面函數(shù)可以改為: ```java /** 打印取值函數(shù) */ public static void printValue(GenericHolder holder) { System.out.println(holder.getValue()); } ``` ### 8.5.泛型上下界 在使用泛型的時候,我們還可以為傳入的泛型類型實參進行上下界的限制,如:類型實參只準傳入某種類型的父類或某種類型的子類。泛型上下界的聲明,必須與泛型的聲明放在一起 。 **上界通配符(extends):** 上界通配符為”extends”,可以接受其指定類型或其子類作為泛參。其還有一種特殊的形式,可以指定其不僅要是指定類型的子類,而且還要實現(xiàn)某些接口。例如:List表明這是A某個具體子類的List,保存的對象必須是A或A的子類。對于List列表,不能添加A或A的子類對象,只能獲取A的對象。 **下界通配符(super):** 下界通配符為”super”,可以接受其指定類型或其父類作為泛參。例如:List表明這是A某個具體父類的List,保存的對象必須是A或A的超類。對于List列表,能夠添加A或A的子類對象,但只能獲取Object的對象。 **PECS(Producer Extends Consumer Super)原則:** 作為生產(chǎn)者提供數(shù)據(jù)(往外讀?。r,適合用上界通配符(extends); 作為消費者消費數(shù)據(jù)(往里寫入)時,適合用下界通配符(super)。 在日常編碼中,比較常用的是**上界通配符(extends)**,用于限定泛型類型的父類。例子代碼如下: ```java /** 數(shù)字支撐類 */ @Getter @Setter @ToString public class NumberHolder { /** 通用取值 */ private T value; /** 構(gòu)造函數(shù) */ public NumberHolder() {} /** 構(gòu)造函數(shù) */ public NumberHolder(T value) { this.value = value; } } /** 打印取值函數(shù) */ public static void printValue(GenericHolder holder) { System.out.println(holder.getValue()); } ``` ## 后記 筆者曾在通信行業(yè)從業(yè)十余年,接入了各類網(wǎng)管和設備的北向接口協(xié)議上百余種,涉及到傳輸、交換、接入、電源、環(huán)境等專業(yè),接觸了CORBA、HTTP/HTTPS、WebService、Socket TCP/UDP、串口RS232/485等接口,總結(jié)出一套接口協(xié)議封裝的"方法論"。其中,把接口協(xié)議文檔中的數(shù)據(jù)格式轉(zhuǎn)化為Java的枚舉、結(jié)構(gòu)體、聯(lián)合體等數(shù)據(jù)結(jié)構(gòu),是接口協(xié)議封裝中極其重要的一步。 **本文作者:**陳昌毅,花名常意,高德地圖技術(shù)專家,2018年加入阿里巴巴,一直從事地圖數(shù)據(jù)采集的相關(guān)工作。

聲明:免責聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻自行上傳,本網(wǎng)站不擁有所有權(quán),也不承認相關(guān)法律責任。如果您發(fā)現(xiàn)本社區(qū)中有涉嫌抄襲的內(nèi)容,請發(fā)

送郵件至:operations@xinnet.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,本站將立刻刪除涉嫌侵權(quán)內(nèi)容。本站原創(chuàng)內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時

需注明出處:新網(wǎng)idc知識百科

免費咨詢獲取折扣

Loading