Java 編程技巧之數據結構

      網友投稿 699 2022-05-30

      唐宋八大家之一歐陽修在《賣油翁》中寫道:

      翁取一葫蘆置于地,以錢覆其口,徐以杓酌油瀝之,自錢孔入,而錢不濕。因曰:“我亦無他,唯手熟爾。”

      編寫代碼的"老司機"也是如此,"老司機"之所以被稱為"老司機",原因也是"無他,唯手熟爾"。編碼過程中踩過的坑多了,獲得的編碼經驗也就多了,總結的編碼技巧也就更多了。總結的編碼技巧多了,凡事又能夠舉一反三,編碼的速度自然就上來了。筆者從數據結構的角度,整理了一些Java編程技巧,以供大家學習參考。(服務器與雙十一)

      1.使用HashSet判斷主鍵是否存在

      HashSet實現Set接口,由哈希表(實際上是HashMap)支持,但不保證set 的迭代順序,并允許使用null元素。HashSet的時間復雜度跟HashMap一致,如果沒有哈希沖突則時間復雜度為O(1),如果存在哈希沖突則時間復雜度不超過O(n)。所以,在日常編碼中,可以使用HashSet判斷主鍵是否存在。

      案例:給定一個字符串(不一定全為字母),請返回第一個重復出現的字符。

      /**?查找第一個重復字符?*/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函數有個特性——如果添加的元素已經再集合中存在,則會返回false。可以簡化代碼為:

      if?(!charSet.add(ch))?{????return?ch; }

      2.使用HashMap存取鍵值映射關系(服務器與雙十一)

      簡單來說,HashMap由數組和鏈表組成的,數組是HashMap的主體,鏈表則是主要為了解決哈希沖突而存在的。如果定位到的數組位置不含鏈表,那么查找、添加等操作很快,僅需一次尋址即可,其時間復雜度為O(1);如果定位到的數組包含鏈表,對于添加操作,其時間復雜度為O(n)——首先遍歷鏈表,存在即覆蓋,不存在則新增;對于查找操作來講,仍需要遍歷鏈表,然后通過key對象的equals方法逐一對比查找。從性能上考慮,HashMap中的鏈表出現越少,即哈希沖突越少,性能也就越好。所以,在日常編碼中,可以使用HashMap存取鍵值映射關系。

      案例:給定菜單記錄列表,每條菜單記錄中包含父菜單標識(根菜單的父菜單標識為null),構建出整個菜單樹。

      /**?菜單DO類?*/@Setter@Getter@ToStringpublic?static?class?MenuDO?{????/**?菜單標識?*/ ????private?Long?id;????/**?菜單父標識?*/ ????private?Long?parentId;????/**?菜單名稱?*/ ????private?String?name;????/**?菜單鏈接?*/ ????private?String?url; }/**?菜單VO類?*/@Setter@Getter@ToStringpublic?static?class?MenuVO?{????/**?菜單標識?*/ ????private?Long?id;????/**?菜單名稱?*/ ????private?String?name;????/**?菜單鏈接?*/ ????private?String?url;????/**?子菜單列表?*/ ????private?List?childList; }/**?構建菜單樹函數?*/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());????????//?根據父標識處理 ????????Long?parentId?=?menuDO.getParentId();????????if?(Objects.nonNull(parentId))?{????????????//?構建父菜單對象 ????????????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提供了線程專有對象,可以在整個線程生命周期中隨時取用,極大地方便了一些邏輯的實現。

      常見的ThreadLocal用法主要有兩種:

      保存線程上下文對象,避免多層級參數傳遞;

      保存非線程安全對象,避免多線程并發調用。

      3.1.保存線程上下文對象,避免多層級參數傳遞(服務器與雙十一)

      這里,以PageHelper插件的源代碼中的分頁參數設置與使用為例說明。

      設置分頁參數代碼:

      /**?分頁方法類?*/public?abstract?class?PageMethod?{????/**?本地分頁?*/ ????protected?static?final?ThreadLocal?LOCAL_PAGE?=?new?ThreadLocal();????/**?設置分頁參數?*/ ????protected?static?void?setLocalPage(Page?page)?{ ????????LOCAL_PAGE.set(page); ????}????/**?獲取分頁參數?*/ ????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; ????} }

      使用分頁參數代碼:

      /**?虛輔助方言類?*/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); ????} ????... }

      使用分頁插件代碼:

      /**?查詢用戶函數?*/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; }

      如果要把分頁參數通過函數參數逐級傳給查詢語句,除非修改MyBatis相關接口函數,否則是不可能實現的。

      3.2.保存非線程安全對象,避免多線程并發調用(服務器與雙十一)

      在寫日期格式化工具函數時,首先想到的寫法如下:

      /**?日期模式?*/private?static?final?String?DATE_PATTERN?=?"yyyy-MM-dd";/**?格式化日期函數?*/public?static?String?formatDate(Date?date)?{????return?new?SimpleDateFormat(DATE_PATTERN).format(date); }

      其中,每次調用都要初始化DateFormat導致性能較低,把DateFormat定義成常量后的寫法如下:

      /**?日期格式?*/private?static?final?DateFormat?DATE_FORMAT?=?new?SimpleDateFormat("yyyy-MM-dd");/**?格式化日期函數?*/public?static?String?formatDate(Date?date)?{????return?DATE_FORMAT.format(date); }

      由于SimpleDateFormat是非線程安全的,當多線程同時調用formatDate函數時,會導致返回結果與預期不一致。如果采用ThreadLocal定義線程專有對象,優化后的代碼如下:

      /**?本地日期格式?*/private?static?final?ThreadLocal?LOCAL_DATE_FORMAT?=?new?ThreadLocal()?{????@Override ????protected?DateFormat?initialValue()?{????????return?new?SimpleDateFormat("yyyy-MM-dd"); ????} };/**?格式化日期函數?*/public?static?String?formatDate(Date?date)?{????return?LOCAL_DATE_FORMAT.get().format(date); }

      這是在沒有線程安全的日期格式化工具類之前的實現方法。在JDK8以后,建議使用DateTimeFormatter代替SimpleDateFormat,因為SimpleDateFormat是線程不安全的,而DateTimeFormatter是線程安全的。當然,也可以采用第三方提供的線程安全日期格式化函數,比如apache的DateFormatUtils工具類。

      注意:ThreadLocal有一定的內存泄露的風險,盡量在業務代碼結束前調用remove函數進行數據清除。

      4.使用Pair實現成對結果的返回(服務器與雙十一)

      在C/C++語言中,Pair(對)是將兩個數據類型組成一個數據類型的容器,比如std::pair。

      Pair主要有兩種用途:

      把key和value放在一起成對處理,主要用于Map中返回名值對,比如Map中的Entry類;

      當一個函數需要返回兩個結果時,可以使用Pair來避免定義過多的數據模型類。

      第一種用途比較常見,這里主要說明第二種用途。

      4.1.定義模型類實現成對結果的返回(服務器與雙十一)

      函數實現代碼:

      /**?點和距離類?*/@Setter@Getter@ToString@AllArgsConstructorpublic?static?class?PointAndDistance?{????/**?點?*/ ????private?Point?point;????/**?距離?*/ ????private?Double?distance; }/**?獲取最近點和距離?*/public?static?PointAndDistance?getNearestPointAndDistance(Point?point,?Point[]?points)?{????//?檢查點數組為空 ????if?(ArrayUtils.isEmpty(points))?{????????return?null; ????}????//?獲取最近點和距離 ????Point?nearestPoint?=?points[0];????double?nearestDistance?=?getDistance(point,?points[0]);????for?(int?i?=?1;?i?

      函數使用案例:

      Point?point?=?...; Point[]?points?=?...; PointAndDistance?pointAndDistance?=?getNearestPointAndDistance(point,?points);if?(Objects.nonNull(pointAndDistance))?{ ????Point?point?=?pointAndDistance.getPoint(); ????Double?distance?=?pointAndDistance.getDistance(); ????... }

      4.2.使用Pair類實現成對結果的返回(服務器與雙十一)

      在JDK中,沒有提供原生的Pair數據結構,也可以使用Map::Entry代替。不過,Apache的commons-lang3包中的Pair類更為好用,下面便以Pair類進行舉例說明。

      函數實現代碼:

      /**?獲取最近點和距離?*/public?static?Pair?getNearestPointAndDistance(Point?point,?Point[]?points)?{????//?檢查點數組為空 ????if?(ArrayUtils.isEmpty(points))?{????????return?null; ????}????//?獲取最近點和距離 ????Point?nearestPoint?=?points[0];????double?nearestDistance?=?getDistance(point,?points[0]);????for?(int?i?=?1;?i?

      函數使用案例:

      Point?point?=?...; Point[]?points?=?...; Pair?pair?=?getNearestPointAndDistance(point,?points);if?(Objects.nonNull(pair))?{ ????Point?point?=?pair.getLeft(); ????Double?distance?=?pair.getRight(); ????... }

      5.定義Enum類實現取值和描述(服務器與雙十一)

      在C++、Java等計算機編程語言中,枚舉類型(Enum)是一種特殊數據類型,能夠為一個變量定義一組預定義的常量。在使用枚舉類型的時候,枚舉類型變量取值必須為其預定義的取值之一。

      5.1.用class關鍵字實現的枚舉類型(服務器與雙十一)

      在JDK5之前,Java語言不支持枚舉類型,只能用類(class)來模擬實現枚舉類型。

      /**?訂單狀態枚舉?*/public?final?class?OrderStatus?{????/**?屬性相關?*/ ????/**?狀態取值?*/ ????private?final?int?value;????/**?狀態描述?*/ ????private?final?String?description;????/**?常量相關?*/ ????/**?已創建(1)?*/ ????public?static?final?OrderStatus?CREATED?=?new?OrderStatus(1,?"已創建");????/**?進行中(2)?*/ ????public?static?final?OrderStatus?PROCESSING?=?new?OrderStatus(2,?"進行中");????/**?已完成(3)?*/ ????public?static?final?OrderStatus?FINISHED?=?new?OrderStatus(3,?"已完成");????/**?構造函數?*/ ????private?OrderStatus(int?value,?String?description)?{????????this.value?=?value;????????this.description?=?description; ????}????/**?獲取狀態取值?*/ ????public?int?getValue()?{????????return?value; ????}????/**?獲取狀態描述?*/ ????public?String?getDescription()?{????????return?description; ????} }

      5.2.用enum關鍵字實現的枚舉類型(服務器與雙十一)

      JDK5提供了一種新的類型——Java的枚舉類型,關鍵字enum可以將一組具名的值的有限集合創建為一種新的類型,而這些具名的值可以作為常量使用,這是一種非常有用的功能。

      /**?訂單狀態枚舉?*/public?enum?OrderStatus?{????/**?常量相關?*/ ????/**?已創建(1)?*/ ????CREATED(1,?"已創建"),????/**?進行中(2)?*/ ????PROCESSING(2,?"進行中"),????/**?已完成(3)?*/ ????FINISHED(3,?"已完成");????/**?屬性相關?*/ ????/**?狀態取值?*/ ????private?final?int?value;????/**?狀態描述?*/ ????private?final?String?description;????/**?構造函數?*/ ????private?OrderStatus(int?value,?String?description)?{????????this.value?=?value;????????this.description?=?description; ????}????/**?獲取狀態取值?*/ ????public?int?getValue()?{????????return?value; ????}????/**?獲取狀態描述?*/ ????public?String?getDescription()?{????????return?description; ????} }

      其實,Enum類型就是一個語法糖,編譯器幫我們做了語法的解析和編譯。通過反編譯,可以看到Java枚舉編譯后實際上是生成了一個類,該類繼承了 java.lang.Enum,并添加了values()、valueOf()等枚舉類型通用方法。

      6.定義Holder類實現參數的輸出(服務器與雙十一)

      在很多語言中,函數的參數都有輸入(in)、輸出(out)和輸入輸出(inout)之分。在C/C++語言中,可以用對象的引用(&)來實現函數參數的輸出(out)和輸入輸出(inout)。但在Java語言中,雖然沒有提供對象引用類似的功能,但是可以通過修改參數的字段值來實現函數參數的輸出(out)和輸入輸出(inout)。這里,我們叫這種輸出參數對應的數據結構為Holder(支撐)類。

      Holder類實現代碼:

      /**?長整型支撐類?*/@Getter@Setter@ToStringpublic?class?LongHolder?{????/**?長整型取值?*/ ????private?long?value;????/**?構造函數?*/ ????public?LongHolder()?{}????/**?構造函數?*/ ????public?LongHolder(long?value)?{????????this.value?=?value; ????} }

      Holder類使用案例:

      /**?靜態常量?*//**?頁面數量?*/private?static?final?int?PAGE_COUNT?=?100;/**?最大數量?*/private?static?final?int?MAX_COUNT?=?1000;/**?處理過期訂單?*/public?void?handleExpiredOrder()?{ ????LongHolder?minIdHolder?=?new?LongHolder(0L);????for?(int?pageIndex?=?0;?pageIndex??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; }

      其實,可以實現一個泛型支撐類,適用于更多的數據類型。

      7.定義Union類實現數據體的共存(服務器與雙十一)

      在C/C++語言中,聯合體(union),又稱共用體,類似結構體(struct)的一種數據結構。聯合體(union)和結構體(struct)一樣,可以包含很多種數據類型和變量,兩者區別如下:

      結構體(struct)中所有變量是“共存”的,同時所有變量都生效,各個變量占據不同的內存空間;

      聯合體(union)中是各變量是“互斥”的,同時只有一個變量生效,所有變量占據同一塊內存空間。

      當多個數據需要共享內存或者多個數據每次只取其一時,可以采用聯合體(union)。

      在Java語言中,沒有聯合體(union)和結構體(struct)概念,只有類(class)的概念。眾所眾知,結構體(struct)可以用類(class)來實現。其實,聯合體(union)也可以用類(class)來實現。但是,這個類不具備“多個數據需要共享內存”的功能,只具備“多個數據每次只取其一”的功能。

      這里,以微信協議的客戶消息為例說明。根據我多年來的接口協議封裝經驗,主要有以下兩種實現方式。

      7.1.使用函數方式實現Union

      Union類實現:(服務器與雙十一)

      /**?客戶消息類?*/@ToStringpublic?class?CustomerMessage?{????/**?屬性相關?*/ ????/**?消息類型?*/ ????private?String?msgType;????/**?目標用戶?*/ ????private?String?toUser;????/**?共用體相關?*/ ????/**?新聞內容?*/ ????private?News?news; ????...????/**?常量相關?*/ ????/**?新聞消息?*/ ????public?static?final?String?MSG_TYPE_NEWS?=?"news"; ????...????/**?構造函數?*/ ????public?CustomerMessage()?{}????/**?構造函數?*/ ????public?CustomerMessage(String?toUser)?{????????this.toUser?=?toUser; ????}????/**?構造函數?*/ ????public?CustomerMessage(String?toUser,?News?news)?{????????this.toUser?=?toUser;????????this.msgType?=?MSG_TYPE_NEWS;????????this.news?=?news; ????}????/**?清除消息內容?*/ ????private?void?removeMsgContent()?{????????//?檢查消息類型 ????????if?(Objects.isNull(msgType))?{????????????return; ????????}????????//?清除消息內容 ????????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("消息類型不匹配"); ????????} ????}????/**?設置消息類型函數?*/ ????public?void?setMsgType(String?msgType)?{????????//?清除消息內容 ????????removeMsgContent();????????//?檢查消息類型 ????????if?(Objects.isNull(msgType))?{????????????throw?new?IllegalArgumentException("消息類型為空"); ????????}????????//?賦值消息內容 ????????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)?{????????//?清除消息內容 ????????removeMsgContent();????????//?賦值消息內容 ????????this.msgType?=?MSG_TYPE_NEWS;????????this.news?=?news; ????}????/**?獲取新聞?*/ ????public?News?getNews()?{????????//?檢查消息類型 ????????checkMsgType(MSG_TYPE_NEWS);????????//?返回消息內容 ????????return?this.news; ????} ???? ????... }

      Union類使用:

      String?accessToken?=?...; String?toUser?=?...; List

      ?articleList?=?...; News?news?=?new?News(articleList); CustomerMessage?customerMessage?=?new?CustomerMessage(toUser,?news); wechatApi.sendCustomerMessage(accessToken,?customerMessage);

      主要優缺點:

      優點:更貼近C/C++語言的聯合體(union);

      缺點:實現邏輯較為復雜,參數類型驗證較多。

      7.2.使用繼承方式實現Union(服務器與雙十一)

      Union類實現:

      /**?客戶消息類?*/@Getter@Setter@ToStringpublic?abstract?class?CustomerMessage?{????/**?屬性相關?*/ ????/**?消息類型?*/ ????private?String?msgType;????/**?目標用戶?*/ ????private?String?toUser;????/**?常量相關?*/ ????/**?新聞消息?*/ ????public?static?final?String?MSG_TYPE_NEWS?=?"news"; ????...????/**?構造函數?*/ ????public?CustomerMessage(String?msgType)?{????????this.msgType?=?msgType; ????}????/**?構造函數?*/ ????public?CustomerMessage(String?msgType,?String?toUser)?{????????this.msgType?=?msgType;????????this.toUser?=?toUser; ????} }/**?新聞客戶消息類?*/@Getter@Setter@ToString(callSuper?=?true)public?class?NewsCustomerMessage?extends?CustomerMessage?{????/**?屬性相關?*/ ????/**?新聞內容?*/ ????private?News?news;????/**?構造函數?*/ ????public?NewsCustomerMessage()?{????????super(MSG_TYPE_NEWS); ????}????/**?構造函數?*/ ????public?NewsCustomerMessage(String?toUser,?News?news)?{????????super(MSG_TYPE_NEWS,?toUser);????????this.news?=?news; ????} }

      Union類使用:

      String?accessToken?=?...; String?toUser?=?...; List

      ?articleList?=?...; News?news?=?new?News(articleList); CustomerMessage?customerMessage?=?new?NewsCustomerMessage(toUser,?news); wechatApi.sendCustomerMessage(accessToken,?customerMessage);

      主要優缺點:

      優點:使用虛基類和子類進行拆分,各個子類對象的概念明確;

      缺點:與C/C++語言的聯合體(union)差別大,但是功能上大體一致。

      在C/C++語言中,聯合體并不包括聯合體當前的數據類型。但在上面實現的Java聯合體中,已經包含了聯合體對應的數據類型。所以,從嚴格意義上說,Java聯合體并不是真正的聯合體,只是一個具備“多個數據每次只取其一”功能的類。

      8.使用泛型屏蔽類型的差異性

      在C++語言中,有個很好用的模板(template)功能,可以編寫帶有參數化類型的通用版本,讓編譯器自動生成針對不同類型的具體版本。而在Java語言中,也有一個類似的功能叫泛型(generic)。在編寫類和方法的時候,一般使用的是具體的類型,而用泛型可以使類型參數化,這樣就可以編寫更通用的代碼。

      許多人都認為,C++模板(template)和Java泛型(generic)兩個概念是等價的,其實實現機制是完全不同的。C++模板是一套宏指令集,編譯器會針對每一種類型創建一份模板代碼副本;Java泛型的實現基于"類型擦除"概念,本質上是一種進行類型限制的語法糖。

      8.1.泛型類

      以支撐類為例,定義泛型的通用支撐類:

      /**?通用支撐類?*/@Getter@Setter@ToStringpublic?class?GenericHolder?{????/**?通用取值?*/ ????private?T?value;????/**?構造函數?*/ ????public?GenericHolder()?{}????/**?構造函數?*/ ????public?GenericHolder(T?value)?{????????this.value?=?value; ????} }

      8.2.泛型接口(服務器與雙十一)

      定義泛型的數據提供者接口:

      /**?數據提供者接口?*/public?interface?DataProvider?{????/**?獲取數據函數?*/ ????public?T?getData(); }

      8.3.泛型方法(服務器與雙十一)

      定義泛型的淺拷貝函數:

      /**?淺拷貝函數?*/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類中的功能時,可以使用泛型通配符 "?"。

      /**?打印取值函數?*/public?static?void?printValue(GenericHolder?holder)?{ ????System.out.println(holder.getValue()); }/**?主函數?*/public?static?void?main(String[]?args)?{ ????printValue(new?GenericHolder<>(12345)); ????printValue(new?GenericHolder<>("abcde")); }

      在Java規范中,不建議使用泛型通配符"?",上面函數可以改為:

      /**?打印取值函數?*/public?static??void?printValue(GenericHolder?holder)?{ ????System.out.println(holder.getValue()); }

      8.5.泛型上下界

      在使用泛型的時候,我們還可以為傳入的泛型類型實參進行上下界的限制,如:類型實參只準傳入某種類型的父類或某種類型的子類。泛型上下界的聲明,必須與泛型的聲明放在一起 。

      上界通配符(extends):

      上界通配符為”extends”,可以接受其指定類型或其子類作為泛參。其還有一種特殊的形式,可以指定其不僅要是指定類型的子類,而且還要實現某些接口。例如:List表明這是A某個具體子類的List,保存的對象必須是A或A的子類。對于List列表,不能添加A或A的子類對象,只能獲取A的對象。

      下界通配符(super):

      下界通配符為”super”,可以接受其指定類型或其父類作為泛參。例如:List表明這是A某個具體父類的List,保存的對象必須是A或A的超類。對于List列表,能夠添加A或A的子類對象,但只能獲取Object的對象。

      Java 編程技巧之數據結構

      PECS(Producer Extends Consumer Super)原則:

      作為生產者提供數據(往外讀取)時,適合用上界通配符(extends);

      作為消費者消費數據(往里寫入)時,適合用下界通配符(super)。

      在日常編碼中,比較常用的是上界通配符(extends),用于限定泛型類型的父類。例子代碼如下:

      /**?數字支撐類?*/@Getter@Setter@ToStringpublic?class?NumberHolder?{????/**?通用取值?*/ ????private?T?value;????/**?構造函數?*/ ????public?NumberHolder()?{}????/**?構造函數?*/ ????public?NumberHolder(T?value)?{????????this.value?=?value; ????} }/**?打印取值函數?*/public?static??void?printValue(GenericHolder?holder)?{ ????System.out.println(holder.getValue()); }

      后記

      筆者曾在通信行業從業十余年,接入了各類網管和設備的北向接口協議上百余種,涉及到傳輸、交換、接入、電源、環境等專業,接觸了CORBA、HTTP/HTTPS、WebService、Socket TCP/UDP、串口RS232/485等接口,總結出一套接口協議封裝的"方法論"。其中,把接口協議文檔中的數據格式轉化為Java的枚舉、結構體、聯合體等數據結構,是接口協議封裝中極其重要的一步。

      云市場 云計算

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:2017年7月ROS學習資料小結
      下一篇:手把手教你創建第一個Davinci應用
      相關文章
      亚洲老熟女五十路老熟女bbw| 亚洲一区精品无码| 精品久久久久久亚洲| 亚洲国产成人久久精品99 | 亚洲国产日韩在线视频| 亚洲欧洲一区二区三区| 四虎亚洲国产成人久久精品 | 亚洲黄色片在线观看| 亚洲高清在线播放| 亚洲一本综合久久| 亚洲成人动漫在线| 亚洲AV无码成人专区片在线观看 | 色天使亚洲综合一区二区| 亚洲人成色777777老人头| 亚洲国产午夜精品理论片在线播放| 亚洲熟妇无码八V在线播放| 亚洲日本天堂在线| 亚洲av无码av在线播放| www国产亚洲精品久久久| 亚洲成?Ⅴ人在线观看无码| 亚洲精品偷拍视频免费观看| 综合亚洲伊人午夜网| 精品亚洲综合在线第一区| 中文字幕亚洲无线码| 亚洲女同成av人片在线观看| 亚洲AV无码乱码国产麻豆| 亚洲一区二区影院| 亚洲午夜电影在线观看高清| 亚洲色精品VR一区区三区| www亚洲精品久久久乳| 亚洲一区二区三区在线播放| 亚洲精品~无码抽插| 亚洲一区二区三区首页| 久久精品国产亚洲AV忘忧草18| 亚洲欧美日韩中文字幕在线一区 | 亚洲AV美女一区二区三区| 亚洲日本在线播放| 亚洲一区AV无码少妇电影| 亚洲va中文字幕无码| 亚洲精品美女久久久久99| 亚洲黄色中文字幕|