導讀
翁取一葫蘆置於地,以錢覆其口,徐以杓酌油瀝之,自錢孔入,而錢不溼。因曰:「我亦無他,惟手熟爾。」
編寫代碼的"老司機"也是如此,"老司機"之因此被稱爲"老司機",緣由也是"無他,惟手熟爾"。編碼過程當中踩過的坑多了,得到的編碼經驗也就多了,總結的編碼技巧也就更多了。總結的編碼技巧多了,凡事又可以觸類旁通,編碼的速度天然就上來了。筆者從數據結構的角度,整理了一些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
@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<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提供了線程專有對象,能夠在整個線程生命週期中隨時取用,極大地方便了一些邏輯的實現。
-
保存線程上下文對象,避免多層級參數傳遞;
-
保存非線程安全對象,避免多線程併發調用。
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。
-
把key和value放在一塊兒成對處理,主要用於Map中返回名值對,好比Map中的Entry類;
-
當一個函數須要返回兩個結果時,可使用Pair來避免定義過多的數據模型類。
4.1.定義模型類實現成對結果的返回
/** 點和距離類 */
@Setter
@Getter
@ToString
@AllArgsConstructor
public 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(支撐)類。
/** 長整型支撐類 */
@Getter
@Setter
@ToString
public class LongHolder {
/** 長整型取值 */
private long value;
/** 構造函數 */
public LongHolder() {}
/** 構造函數 */
public LongHolder(long value) {
this.value = value;
}
}複製代碼
/** 靜態常量 */
/** 頁面數量 */
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
/** 客戶消息類 */
@ToString
public 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;
}
...
}複製代碼
String accessToken = ...;
String toUser = ...;
List<Article> articleList = ...;
News news = new News(articleList);
CustomerMessage customerMessage = new CustomerMessage(toUser, news);
wechatApi.sendCustomerMessage(accessToken, customerMessage);複製代碼
7.2.使用繼承方式實現Union
/** 客戶消息類 */
@Getter
@Setter
@ToString
public 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;
}
}複製代碼
String accessToken = ...;
String toUser = ...;
List<Article> articleList = ...;
News news = new News(articleList);
CustomerMessage customerMessage = new NewsCustomerMessage(toUser, news);
wechatApi.sendCustomerMessage(accessToken, customerMessage);複製代碼
在C/C++語言中,聯合體並不包括聯合體當前的數據類型。但在上面實現的Java聯合體中,已經包含了聯合體對應的數據類型。因此,從嚴格意義上說,Java聯合體並非真正的聯合體,只是一個具有「多個數據每次只取其一」功能的類。
8.使用泛型屏蔽類型的差別性
在C++語言中,有個很好用的模板(template)功能,能夠編寫帶有參數化類型的通用版本,讓編譯器自動生成針對不一樣類型的具體版本。而在Java語言中,也有一個相似的功能叫泛型(generic)。在編寫類和方法的時候,通常使用的是具體的類型,而用泛型可使類型參數化,這樣就能夠編寫更通用的代碼。
許多人都認爲,C++模板(template)和Java泛型(generic)兩個概念是等價的,其實實現機制是徹底不一樣的。C++模板是一套宏指令集,編譯器會針對每一種類型建立一份模板代碼副本;Java泛型的實現基於"類型擦除"概念,本質上是一種進行類型限制的語法糖。
8.1.泛型類
/** 通用支撐類 */
@Getter
@Setter
@ToString
public 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」,能夠接受其指定類型或其子類做爲泛參。其還有一種特殊的形式,能夠指定其不只要是指定類型的子類,並且還要實現某些接口。例如:List<? extends A>代表這是A某個具體子類的List,保存的對象必須是A或A的子類。對於List<? extends A>列表,不能添加A或A的子類對象,只能獲取A的對象。
下界通配符爲」super」,能夠接受其指定類型或其父類做爲泛參。例如:List<? super A>代表這是A某個具體父類的List,保存的對象必須是A或A的超類。對於List<? super A>列表,可以添加A或A的子類對象,但只能獲取Object的對象。
PECS(Producer Extends Consumer Super)原則: 做爲生產者提供數據(往外讀取)時,適合用上界通配符(extends); 做爲消費者消費數據(往裏寫入)時,適合用下界通配符(super)。
在平常編碼中,比較經常使用的是上界通配符(extends),用於限定泛型類型的父類。例子代碼以下:
/** 數字支撐類 */
@Getter
@Setter
@ToString
public 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的枚舉、結構體、聯合體等數據結構,是接口協議封裝中極其重要的一步。