Java 程式設計技巧之資料結構
導讀
唐宋八大家之一歐陽修在《賣油翁》中寫道:
翁取一葫蘆置於地,以錢覆其口,徐以杓酌油瀝之,自錢孔入,而錢不溼。因曰:“我亦無他,唯手熟爾。”
編寫程式碼的"老司機"也是如此,"老司機"之所以被稱為"老司機",原因也是"無他,唯手熟爾"。編碼過程中踩過的坑多了,獲得的編碼經驗也就多了,總結的編碼技巧也就更多了。總結的編碼技巧多了,凡事又能夠舉一反三,編碼的速度自然就上來了。筆者從資料結構的角度,整理了一些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<MenuVO> childList; }/** 構建選單樹函式 */public static List<MenuVO> buildMenuTree(List<MenuDO> menuList) { // 檢查列表為空 if (CollectionUtils.isEmpty(menuList)) { return Collections.emptyList(); } // 依次處理選單 int menuSize = menuList.size(); List<MenuVO> rootList = new ArrayList<>(menuSize); Map<Long, MenuVO> 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<Page> LOCAL_PAGE = new ThreadLocal<Page>(); /** 設定分頁引數 */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** 獲取分頁引數 */ public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); } /** 開始分頁 */ public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page<E>(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; } }
使用分頁引數程式碼:
/** 虛輔助方言類 */public abstract class AbstractHelperDialect extends AbstractDialect implements Constant { /** 獲取本地分頁 */ public <T> Page<T> 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<UserDO> queryUser(UserQuery userQuery, int pageNum, int pageSize) { PageHelper.startPage(pageNum, pageSize); List<UserDO> userList = userDAO.queryUser(userQuery); PageInfo<UserDO> 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<DateFormat> LOCAL_DATE_FORMAT = new ThreadLocal<DateFormat>() { @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 < points.length; i++) { double distance = getDistance(point, point[i]); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point[i]; } } // 返回最近點和距離 return new PointAndDistance(nearestPoint, nearestDistance); }
函式使用案例:
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<Point, Double> 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 < points.length; i++) { double distance = getDistance(point, point[i]); if (distance < nearestDistance) { nearestDistance = distance; nearestPoint = point[i]; } } // 返回最近點和距離 return Pair.of(nearestPoint, nearestDistance); }
函式使用案例:
Point point = ...; Point[] points = ...; Pair<Point, Double> 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 < PAGE_COUNT; pageIndex++) { if (!handleExpiredOrder(pageIndex, minIdHolder)) { break; } } }/** 處理過期訂單 */private boolean handleExpiredOrder(int pageIndex, LongHolder minIdHolder) { // 獲取最小標識 Long minId = minIdHolder.getValue(); // 查詢過期訂單(按id從小到大排序) List<OrderDO> 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<Article> 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<Article> 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<T> { /** 通用取值 */ private T value; /** 建構函式 */ public GenericHolder() {} /** 建構函式 */ public GenericHolder(T value) { this.value = value; } }
8.2.泛型介面
定義泛型的資料提供者介面:
/** 資料提供者介面 */public interface DataProvider<T> { /** 獲取資料函式 */ public T getData(); }
8.3.泛型方法
定義泛型的淺複製函式:
/** 淺複製函式 */public static <T> T shallowCopy(Object source, Class<T> 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 <T> void printValue(GenericHolder<T> holder) { System.out.println(holder.getValue()); }
8.5.泛型上下界
在使用泛型的時候,我們還可以為傳入的泛型型別實參進行上下界的限制,如:型別實參只准傳入某種型別的父類或某種型別的子類。泛型上下界的宣告,必須與泛型的宣告放在一起 。
上界萬用字元(extends):
上界萬用字元為”extends”,可以接受其指定型別或其子類作為泛參。其還有一種特殊的形式,可以指定其不僅要是指定型別的子類,而且還要實現某些介面。例如:List<? extends A>表明這是A某個具體子類的List,儲存的物件必須是A或A的子類。對於List<? extends A>列表,不能新增A或A的子類物件,只能獲取A的物件。
下界萬用字元(super):
下界萬用字元為”super”,可以接受其指定型別或其父類作為泛參。例如:List<? super A>表明這是A某個具體父類的List,儲存的物件必須是A或A的超類。對於List<? super A>列表,能夠新增A或A的子類物件,但只能獲取Object的物件。
PECS(Producer Extends Consumer Super)原則:
作為生產者提供資料(往外讀取)時,適合用上界萬用字元(extends);
作為消費者消費資料(往裡寫入)時,適合用下界萬用字元(super)。
在日常編碼中,比較常用的是 上界萬用字元(extends),用於限定泛型型別的父類。例子程式碼如下:
/** 數字支撐類 */@Getter@Setter@ToStringpublic class NumberHolder<T extends Number> { /** 通用取值 */ private T value; /** 建構函式 */ public NumberHolder() {} /** 建構函式 */ public NumberHolder(T value) { this.value = value; } }/** 列印取值函式 */public static <T extends Number> void printValue(GenericHolder<T> holder) { System.out.println(holder.getValue()); }
後記
筆者曾在通訊行業從業十餘年,接入了各類網管和裝置的北向介面協議上百餘種,涉及到傳輸、交換、接入、電源、環境等專業,接觸了CORBA、HTTP/HTTPS、WebService、Socket TCP/UDP、串列埠RS232/485等介面,總結出一套介面協議封裝的"方法論"。其中,把介面協議文件中的資料格式轉化為Java的列舉、結構體、聯合體等資料結構,是介面協議封裝中極其重要的一步。
本文為雲棲社群原創內容,未經允許不得轉載。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69949601/viewspace-2661004/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 程式設計師內功修煉之資料結構程式設計師資料結構
- JavaScript資料結構之連結串列--設計JavaScript資料結構
- Java基本程式設計結構Java程式設計
- Java程式設計技巧Java程式設計
- Java程式設計生涯你必須要了解的資料結構Java程式設計資料結構
- JAVA資料結構之連結串列Java資料結構
- 好程式設計師Java學習進階之MySQL資料庫結構和引擎比對程式設計師JavaMySql資料庫
- Java實現資料結構之線性結構Java資料結構
- JAVA的基本程式設計結構(下)Java程式設計
- Java常用資料結構之Set之TreeSetJava資料結構
- 程式設計技巧整理:Java程式效能最佳化總結!程式設計Java
- 中級JAVA程式設計師應該掌握的資料結構知識Java程式設計師資料結構
- 資料結構之計數排序資料結構排序
- java併發資料結構之CopyOnWriteArrayListJava資料結構
- JAVA資料結構之雜湊表Java資料結構
- 好程式設計師Java培訓分享Java程式設計技巧程式設計師Java
- 選擇結構程式設計之習題程式設計
- 迴圈結構程式設計之習題程式設計
- 程式設計師修仙之路-資料結構之設計一個高效能執行緒池程式設計師資料結構執行緒
- 架構設計之資料分片架構
- Java乾貨神總結,程式設計師面試技巧Java程式設計師面試
- .NET併發程式設計-資料結構不可變性程式設計資料結構
- 程式設計師必須掌握的資料結構 2程式設計師資料結構
- 程式設計師必須掌握的資料結構 1程式設計師資料結構
- (一)Java資料結構之稀疏陣列Java資料結構陣列
- Java常用資料結構之Map-HashMapJava資料結構HashMap
- Java常用資料結構之Stack&VectorJava資料結構
- java資料結構學習之陣列Java資料結構陣列
- Java核心技術總結一:Java的基本程式設計結構Java程式設計
- Java資料結構Java資料結構
- 好程式設計師Java教程分享Java 迴圈結構程式設計師Java
- Java程式設計開發之資料圖表分析模型Java程式設計模型
- 五分鐘自學程式設計:程式設計師到底怎麼學資料結構?!程式設計師資料結構
- 軟體設計師:資料結構資料結構
- 資料結構小白系列之資料結構概述資料結構
- 資料結構 - 圖之程式碼實現資料結構
- (Java)設計模式:結構型Java設計模式
- 程式設計體系結構(06):Java物件導向程式設計Java物件