1. 程式人生 > >Java 中一些好的編碼風格

Java 中一些好的編碼風格

一、前言

  在實際工作中,我們所編寫的程式碼主要是給我們自己看的,只是順便讓機器去執行。那麼,可想而知,良好的程式碼編寫風格,將給維護人員帶來很大的便利之處。這篇文章是我工作中的一些總結,也許並不適合你,但值得參考,因為我的很多同事並沒有良好的編碼風格和習慣,每次我看他們程式碼時,說句實話,我想吐。如果你有更好的建議,歡迎留言告知。(注:本文以Java語言為例)

二、這是正文

編碼方面

1) 方法名稱的意義要明確
  在提供方法時,一定要明確方法的用意,不然,他人在看到這個方法時會有歧義,以為用錯了。

例如:
  我想根據no獲取大於no的所有資料,而如下的方法其實是獲取等於no的意思,而不是大於no的,因此會帶來歧義。(我之前就犯過這樣的錯誤 :()

// 不友好的定義
public List<Book> getBookListByNo(int no);

// 友好的定義
public List<Book> getBookListByGtNo(int no);
  • 1
  • 2
  • 3
  • 4
  • 5

注意:
  Gt 是greater than 的縮寫,因此不會帶來歧義。在NoSQL資料庫,比如MongoDB的條件查詢中就是這樣命名的,如下:

  • (>) 大於 ->$gt
  • (<) 小於 -> $lt
  • (>=) 大於等於 -> $gte
  • (<= ) 小於等於 -> $lte

2) 可根據引數順序定義方法名


  在給方法提供引數時,方法的命名最好根據引數列表進行命名,這樣呼叫者通過方法即可看到引數的呼叫順序,而不必進入方法內檢視引數列表,但此規則僅僅適用於引數較少的方法。

例如:
  我想通過故事ID和使用者ID去獲取單個故事或者列表,給定故事ID和使用者ID都是long型。

// 不友好的定義
public Story getStoryId(Long uid,Long storyId);
public Story getStoryBySidAndUid(Long uid,Long sid);

// 友好的定義
public Story getStoryByUidAndSid(Long uid,Long sid);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

解析:
  如果呼叫方法時沒有注意引數的順序,很容易導致用錯,特別是對於引數型別都一樣的引數列表,編譯器不會報錯,將造成嚴重的後果。如果程式設計師們都按照這個規則對短引數列表進行這樣的命名,註釋都省得寫了,錯誤發生的概率也就小了。

3)不返回給客戶端對映資料庫的實體類
  在給客戶端做介面時,返回的資料欄位一定要是一個自定義的類,如果返回的結果直接是對映資料庫的類,那麼這將是災難。在客戶端需要增加返回欄位時,你不得不在重新開一個新的介面。雖然有註解可以忽略掉讓dao層不識別擴充套件的欄位,但對於客戶端變化如此之快的業務,這樣做也不是明智之舉,也會使得原始類變得臃腫,難以維護。(這個只有做過api介面和客戶端對接的業務才會有深深的體會)

4) 能用包裝型別時堅決不用基本型別
  在定義的bean時,基本型別最好使用包裝類,而陣列最好使用List,因為List可以使用Java的一些新特性,用起來更方便,也能給他人提供很好的呼叫,除此之外,對List的操作要比運算元組安全得多,當然,這麼做有不好之處,出現空指標的概率變大了,編碼時要時刻注意。同時,有時候資料庫的查詢結果可能是 null,因為自動拆箱,用基本資料型別接收有 NPE 風險。(好處還不止這些)

// 不友好的定義
public class TestClass{

    privete long id;

    private String name;

    private int age;

    private int[] hobbies;

    // 省略構造方法、getter、setter、toString方法
}

// 友好的定義
public class TestClass{

    privete Long id;

    private String name;

    private Int age;

    private List<Integer> hobbies;

    // 省略構造方法、getter、setter、toString方法
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

5)杜絕if else的層層巢狀
  在編碼過程中,如果能夠使用反向條件過濾的邏輯,就先過濾掉,這樣可以避免if else結構的層層巢狀,使得程式碼看起來層次結構非常的清晰易懂,也便於維護。如下程式碼塊,你更喜歡看哪塊程式碼呢?

// 不友好的程式碼
public StringBuilder testMethod(HttpServletRequest request, Long commentsId){

    StringBuilder rst;                
    if (commentsId == null || commentsId <= 0) {
        rst = ApiResBuilder.json(ActionStatus.PARAMAS_ERROR.inValue(), "invalid-comments_id");
    } else {
        CommentsBean comment = commentsService.get(commentsId);
        if (comment == null) {
            rst = ApiResBuilder.json(CommunityActionStatus.COMMENTS_NOT_EXISTS);
        } else {
            long uid = (Long) request.getAttribute(APIkey.uid);
            if (commentsService.praise(uid, commentsId, comment.getReply2thread())) {
                rst = ApiResBuilder.json(ActionStatus.NORMAL_RETURNED);
            } else {
                rst = ApiResBuilder.json(ActionStatus.UNKNOWN);
            }
        }
    }        
    return rst;
}

// 友好的程式碼
public StringBuilder testMethod(HttpServletRequest request, Long commentsId) {

    if (Objects.isNull(commentsId) || commentsId <= 0) {
        return ApiResBuilder.json(ActionStatus.PARAMAS_ERROR.inValue(), "invalid-comments_id");
    }

    CommentsBean comment = commentsService.get(commentsId);
    if (comment == null) {
        return ApiResBuilder.json(CommunityActionStatus.COMMENTS_NOT_EXISTS);
    }

    long uid = (Long) request.getAttribute(APIkey.uid);
    if (commentsService.praise(uid, commentsId, comment.getReply2thread())) {
        return ApiResBuilder.json(ActionStatus.NORMAL_RETURNED);
    } 

    return ApiResBuilder.json(ActionStatus.UNKNOWN);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

6) 不要為了省事,編寫較多的內部類
  在編寫程式碼時,如果你的類需要一些框架或者別人用無慘構造方法去構造物件的時候,儘量不要使用內部類的形式,如果非得要使用內部類,一定要將類定義為靜態的,否則會導致這個內部類無法被構造。如果你忘記寫這個static了(除非你非要這麼做),那麼將會給他人帶來無數的“坑”。

7) 不要吝嗇空行
  編寫程式碼時,可以根據業務將程式碼分行,用空行告知程式碼下一步是做什麼的,在你覺得該留空行的地方的留上空行,而不是整個方法一行空行都沒有,那樣的程式碼真的很糟糕。(例子可能給得不恰當)

// 不友好的程式碼
public void testMethod(int day) {
    MoneyDayStatisticsBean moneyDayStatisticsBean = new MoneyDayStatisticsBean();
    DateTime time = DateTime.now().minusDays(day);
    String date = time.toString("yyyyMMdd");
    Long start = getMinMillis(time);
    Long end = getMaxMillis(time);
    StatsWithdrawCostResultDto statsWithdrawCostResultDto = withdrawStatsJob.statsWithdrawCost(start, end);
    moneyDayStatisticsBean.setId(redisIDService.generate(MoneyDayStatisticsBean.class));
    MoneyDayStatisticsBean lastMoneyDayStatisticsBean = moneyDayStatisticsDao.findLastBean();
    if (!Objects.isNull(lastMoneyDayStatisticsBean)) {
        moneyDayStatisticsBean.setTotalCost(lastMoneyDayStatisticsBean.getTotalCost().add(moneyDayStatisticsBean.getPlatformCost()));
    } else {
        moneyDayStatisticsBean.setTotalCost(moneyDayStatisticsBean.getPlatformCost());
    }
    moneyDayStatisticsBean.setCreateTime(DateTime.now().getMillis());
    WlTaskIncomePay incomePayByDate = businessStatService.getIncomePayByDate(date);
    long totalIncome = businessStatService.getTotalIncome(date);
    Double totalIncomeMoney = NumUtil.dividedBy100(totalIncome);
    moneyDayStatisticsBean.setTotalIncome(totalIncomeMoney);
    moneyDayStatisticsBean.setIncome(Objects.isNull(incomePayByDate) ?
            0 : NumUtil.dividedBy100(incomePayByDate.getIncomeTotal()));
    double totalCost = moneyDayStatisticsBean.getTotalCost().doubleValue();
    long res = totalIncome - NumUtil.multiplyBy100(totalCost);
    moneyDayStatisticsBean.setTotalSurplus(NumUtil.dividedBy100(res));
    moneyDayStatisticsDao.save(moneyDayStatisticsBean);
}

// 友好的程式碼

public void testMethod(int day) {

    MoneyDayStatisticsBean moneyDayStatisticsBean = new MoneyDayStatisticsBean();

    DateTime time = DateTime.now().minusDays(day);
    String date = time.toString("yyyyMMdd");
    Long start = getMinMillis(time);
    Long end = getMaxMillis(time);

    StatsWithdrawCostResultDto statsWithdrawCostResultDto = withdrawStatsJob.statsWithdrawCost(start, end);
    MoneyDayStatisticsBean lastMoneyDayStatisticsBean = moneyDayStatisticsDao.findLastBean();

    moneyDayStatisticsBean.setId(redisIDService.generate(MoneyDayStatisticsBean.class));
    if (!Objects.isNull(lastMoneyDayStatisticsBean)) {
        moneyDayStatisticsBean.setTotalCost(lastMoneyDayStatisticsBean.getTotalCost().add(moneyDayStatisticsBean.getPlatformCost()));
    } else {
        moneyDayStatisticsBean.setTotalCost(moneyDayStatisticsBean.getPlatformCost());
    }

    moneyDayStatisticsBean.setCreateTime(DateTime.now().getMillis());

    WlTaskIncomePay incomePayByDate = businessStatService.getIncomePayByDate(date);

    long totalIncome = businessStatService.getTotalIncome(date);
    Double totalIncomeMoney = NumUtil.dividedBy100(totalIncome);

    moneyDayStatisticsBean.setTotalIncome(totalIncomeMoney);
    moneyDayStatisticsBean.setIncome(Objects.isNull(incomePayByDate) ?
            0 : NumUtil.dividedBy100(incomePayByDate.getIncomeTotal()));

    double totalCost = moneyDayStatisticsBean.getTotalCost().doubleValue();
    long res = totalIncome - NumUtil.multiplyBy100(totalCost);
    moneyDayStatisticsBean.setTotalSurplus(NumUtil.dividedBy100(res));

    moneyDayStatisticsDao.save(moneyDayStatisticsBean);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66

8) 不讓程式碼連火車
  編寫程式碼時,一行程式碼不要太長,如果太長,可在和合適的地方斷行。我使用的工具是IDEA,我的建議是,程式碼的長度不要超過IDEA設定的兩條豎線,多那麼一丁點也是可以的。同時,方法的引數也應該在合適的地方斷行,方便在檢視方法時能夠快速找到。
  如下圖,超級長的程式碼,已經遠遠的超出IDEA設定的豎線。合適的做法是在appkey變數名後斷行,這樣更加優雅和美觀。
超級長的程式碼

9) 定義API介面時,一個引數佔據一行
  定義介面時,最好的做法是一個引數佔據一行,不要因為引數短,讓幾個引數佔據一行,這使得在檢視介面時,不能很好的識別引數的型別、名稱等其他屬性。

// 不友好的定義
@RequestMapping(value = "task/stats/golds", method = RequestMethod.GET, headers = "Accept=application/json")
@ApiOperation(value = "獲取任務金幣發放量", response = TaskGoldStat.class)
@ResponseBody
public JSONResult getUserMessage(@ApiParam("查詢時間") @RequestParam(value = "start_time", required = false) Long startTime,@ApiParam("查詢時間") @RequestParam(value = "end_time", required = false) Long endTime) {
    try {
        return JSONResult.okResult(userDataStatService.getTaskGoldFlow(startTime, endTime));
    } catch (Exception e) {
        SLogger.error(e, e);
        return JSONResult.failureResult(e.getMessage());
    }
}

// 友好的定義
@RequestMapping(value = "task/stats/golds", method = RequestMethod.GET, headers = "Accept=application/json")
@ApiOperation(value = "獲取任務金幣發放量", response = TaskGoldStat.class)
@ResponseBody
public JSONResult getUserMessage(@ApiParam("查詢時間") @RequestParam(value = "start_time", required = false) Long startTime,
                                 @ApiParam("查詢時間") @RequestParam(value = "end_time", required = false) Long endTime) {
    try {
        return JSONResult.okResult(userDataStatService.getTaskGoldFlow(startTime, endTime));
    } catch (Exception e) {
        SLogger.error(e, e);
        return JSONResult.failureResult(e.getMessage());
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

注意:
  這裡有個問題,就是引數可能過長,導致超出了IDEA設定的豎線,這可能違背了上條規則。我的建議是,介面引數定義時,不管這一行程式碼多長,都要忽略上條規則,如果這個時候將程式碼斷行,識別這個介面的引數就存在一定的障礙。介面一旦定義了,改變和檢視的概率遠小於業務層程式碼,因此,可忽略上條規則。

10) 定義列舉變數,以及一些常量時,名稱使用大寫
  看過很多前輩程式碼,有大寫的,有小寫的,讓人感覺很亂。我的建議是將這些變數定義成大寫,藉助開發工具,能夠快速識別這些變數時說明型別。

11)利用IDEA的功能對程式碼按照一定的規則分塊
  有時候一個類中,程式碼可能比較多,業務也比較複雜,在他人檢視程式碼時,如果這些業務方法沒有進行分類排版,而是錯中複雜的穿插在各行,這樣的程式碼是很糟糕的。IDEA提供瞭如下命令:

//region 業務模組名稱
//endregion 業務模組名稱
  • 1
  • 2

在這個標籤內的程式碼,可以靈活的展開和隱藏。這樣,我們在編碼時,就可以將相同業務的方法放入到此程式碼中,那麼,在review程式碼時,就變得輕鬆多了。

12)一定要過載bean類的toString()方法
  構建bean物件時, 一定要過載toString()方法,在方法執行丟擲異常時,可以直接呼叫 類的 toString()方法列印其屬性值,便於排查問題,而不是需要debug才能知道類的屬性值。

13)Object的equals方法最正確的用法
  Object 的 equals 方法容易拋空指標異常,如果明確的知道某個變數不可能為空指標,應使用常量或確定有值的物件來呼叫equals。
例如:

// 不友好的程式碼
object.equals("test");

// 友好的程式碼
"test".equals(object);
  • 1
  • 2
  • 3
  • 4
  • 5

14)使用private隱藏不想讓外界訪問的方法
  現在開發專案都是基於IDE,IDE的程式碼提示功能方便了我們的開發。因此,在平時的開發中,如果某些方法不需要讓外部所呼叫而引起錯誤,那麼應該將其修飾為private的,這樣可以減少錯誤呼叫導致的程式錯誤。編碼時腦袋裡不要理所當然的都是public。

15)利用集合運算提高程式的效率以及程式碼簡潔度
  做專案開發時會經常遇到這樣的情況,從使用者表得到了一批使用者id集合,根據業務需求需要過濾掉一些不符合業務需求的使用者id,將餘下的使用者id在進行別的也操作。那麼,經常看到有同事是通過如下程式碼來處理的:

List<Long> uidList = new ArrayList<>();
uidList.add(2122346211L);
uidList.add(2126554121L);
uidList.add(2126562651L);
uidList.add(2265621171L);
uidList.add(4126216721L);

List<Long> removeList = new ArrayList<>();
removeList .add(2265621171L);
removeList .add(4126216721L);

uidList.removeAll(removeList);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

  還有一種方法是通過iterator去移除集合內的物件。基礎好點的都知道,在對原集合邊遍歷邊移除是會拋異常的,要想邊遍歷邊移除只能用迭代器(iterator)才能夠實現。而我推薦的做法是對兩集合求差集,而不是呼叫集合的removeAll()方法,具體程式碼如下:

// 集合差集
List resultList = (ArrayList) CollectionUtils.subtract(listA, listB);

// 集合並集
List resultList = (ArrayList) CollectionUtils.union(listA, listB);

// 集合交集
List resultList = (ArrayList) CollectionUtils.intersection(listA, listB);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

可能有人會疑惑,將Collection強制轉化為list會不會拋異常,其實不必擔心,看看原始碼(原始碼如下)就是知道,CollectionUtils在做差集時將結果集儲存到了ArrayList 中,所以不會出現強制轉化異常。

 public static Collection subtract(Collection a, Collection b) {
        ArrayList list = new ArrayList(a);
        Iterator it = b.iterator();

        while(it.hasNext()) {
            list.remove(it.next());
        }

        return list;
   }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

  細心的人會發現,CollectionUtils.subtract()方法就是採用迭代器移除的方法實現的,那麼你還有什麼理由自己去寫一遍這樣的程式碼呢?

16)更優雅的寫if的條件
  在編碼過程中,經常會對一個物件、集合、字串判斷是否是合法的引數,在沒有第三方庫的時候,我們的條件表示式可能是以下這樣寫的:

// 傳統的條件表示式
Object object = null;
if(null == object){
    // TODO
}

String string = "";
if(null == string || string.length() <= 0){
    // TODO
}

List<Long> datas = new ArrayList<>();
if(null == datas || datas.size() <= 0){
    // TODO
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  這麼寫,並沒有什麼不妥,只是,我們如果檢視這個條件時,可能需要一點時間來解讀這個if條件到底要做什麼,這給我們解讀程式碼帶來了一定的障礙,特別是對於集合類或者字串的判空時。而我建議的寫法如下:

// 更優雅條件表示式
Object object = null;
if(Objects.isNull(object){
    // TODO
}

String string = "";
if(StringUtils.isBlank(string){
    // TODO
}

List<Long> datas = new ArrayList<>();
if(CollectionUtils.isEmpty(datas){
    // TODO
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

這些方法還有反向條件的方法:Objects.nonNull(obj),StringUtils.isNotBlank(obj),CollectionUtils.isNotEmpty(obj),這樣的程式碼更加直觀的表達了開發者的用意,且使得程式碼更加的優雅。

17)定義類的成員變數時,Boolean型別不要以is開頭
  定義為基本資料型別 boolean isSuccess; 的屬性,它的方法也是 isSuccess(), 框架在反向解析的時候, “以為”對應的屬性名稱是 success,導致屬性獲取不到,進而丟擲異常。

18)能不用else堅決不用
  話不多說,直接上程式碼,你更喜歡哪塊程式碼呢?

// 不優雅的程式碼
public void testMethod(){
    if(...){
        return obj;
    } else {
        return obj;
    }
}

// 優雅的程式碼
public void testMethod(){
    if(...){
        return obj;
    }
    return obj;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

19)建立類時一定要註明作者資訊
  在新建立類時,一定要標明這個類是誰建立的,最好連建立時間日期都帶上,這麼做的用意在於,在維護時能更好的找到責任人(嘻嘻),當然,作用可不止這些。如下給出我在工作中的類註釋頭。

/**
 * @Author: YaoQianShu
 * @Date: 2018/2/28
 * @Time: 15:27
 * Copyright © SSY All Rights Reserved.
 */
@Service
public class TestService {
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

20)使用Java8 lambda簡化程式碼,使程式碼不僅優雅還高大上
  一段普通的方法與Java8 lambda語法的示例。

// 普通的程式碼
List<Integer> datas = new ArrayList<>();
// 省略初始化
for(Integer num : datas){
    System.out.println(num);
}

// Java8 lambda
List<Integer> datas = new ArrayList<>();
// 省略初始化
datas.stream().forEach(System.out::println);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

21)行註釋不要放在程式碼之後
  很多人在寫程式碼是為了方便,在寫完程式碼時,將註釋直接寫在程式碼之後,而不換行,當代碼本身就比較長時,這樣的註釋可能就超出螢幕的可見範圍,這麼做是很不可取的。

// 不友好的行註釋

if(....){ // 這是註釋
}

boolean result = StringUtils.isBlank(varA) || Objects.isNull(varB); // 這是註釋註釋註釋註釋註釋註釋註釋

// 友好的行註釋

// 這是註釋
if(....){ 
}

// 這是註釋註釋註釋註釋註釋註釋註釋
boolean result = StringUtils.isBlank(varA) || Objects.isNull(varB); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

22)介面類中的方法和屬性不要加任何修飾符號

// 不友好的定義
public interface Test{

    /**
     * This is method describe!
     **/
    public void testMethod();
}

// 友好的定義
public interface Test{

    /**
     * This is method describe!
     **/
    void testMethod();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

23)不吝嗇“{}”
  在 if/else/for/while/do 語句中必須使用大括號,即使只有一行程式碼。

// 不友好的程式碼
if(...) //TODO

// 友好的程式碼
if(...){
    // TODO
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

24)容易被遺忘的關鍵字 - switch
  在某些控制語句中,如果能用switch解決的,推薦使用switch,例如條件表示式是列舉常量或者其他可使用switch完成的,應都用switch來控制,switch不僅可以使程式碼簡潔,且分支跳轉效率要比if else效率高。同時需要注意,即使default分支什麼都不處理,我們也應該寫上。

// 不友好的程式碼
if(var == ONE){
    // TODO
} else if(var == TWO){
    // TODO
} else if(....){
}else{
}

// 友好的程式碼
switch(var){
    case ONE:
        // TODO
    break;
    case TWO:
        // TODO
    break;
    .......
    default:
    break;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

25)集合初始化時儘量指定集合初始值
  ArrayList 儘量使用 ArrayList( int initialCapacity) 初始化,Map等其他集合類類似。特別是對於一些明確知道集合大小的情況下最為妥當,可以防止集合擴容而犧牲不必要的空間和時間。

// 不友好的程式碼(例子為接收Redis的響應結果,外部傳入taskKeyList)
Map<String, Response<String>> responseMap = new HashMap<>();
Pipeline pipeline = jedis.pipelined();

for (String taskKey : taskKeyList) {
    String key = createKey(currentTime, uid, taskKey);
    responseMap.put(taskKey, pipeline.get(key));
}

// 友好的程式碼(此處對HashMap進行了初始化)
Map<String, Response<String>> responseMap = new HashMap<>(taskKeyList.size());
Pipeline pipeline = jedis.pipelined();

for (String taskKey : taskKeyList) {
    String key = createKey(currentTime, uid, taskKey);
    responseMap.put(taskKey, pipeline.get(key));
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

26)獲取系統時間戳用System. currentTimeMillis()
  獲取當前毫秒數 System. currentTimeMillis(); 而不是 new Date(). getTime(),或者JodaTime的 new DataTime()。如果想獲取更加精確的納秒級時間值,用 System. nanoTime()
  Reason:System. currentTimeMillis()是一個native方法,並且被static修飾,效率更高,而new開闢的物件不僅消耗空間,還浪費時間。

27)迴圈體中的語句要考量try-catch效能
  迴圈體中的語句要考量效能,以下操作儘量移至迴圈體外處理,如定義物件、變數、
獲取資料庫連線,進行不必要的 try-catch 操作(這個 try-catch 是否可以移至迴圈體外) ,並且,將try-catch移至外部後,程式碼看起來會更優雅。

// 不友好的程式碼
public void tryCatchTest1() {

    int divider = 100;

    List<Integer> list = new ArrayList<>(3);
    list.add(12);
    list.add(13);
    list.add(0);

    List<Double> result = new ArrayList<>(3);

    list.stream().forEach(var -> {
        try {
            result.add((double) (divider / var));
        } catch (ArithmeticException e) {
            e.printStackTrace();
        }
    });

    result.stream().forEach(System.out::println);
}

// 友好的程式碼
public void tryCatchTest() {

    int divider = 100;

    List<Integer> list = new ArrayList<>(3);
    list.add(12);
    list.add(11);
    list.add(0);

    List<Double> result = new ArrayList<>(3);
    try {
        list.stream().forEach(var -> result.add((double) (divider / var)));
    } catch (ArithmeticException e) {
        e.printStackTrace();
    }

    result.stream().forEach(System.out::println);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

別踩坑

1) 使用Redis的有序集合時,一定要注意如果想要實現分頁時,Redis不會將分頁的大小減1,而是從0到分頁大小的下標取資料,所有如果分頁取20,那麼最終結果是21個。因為它是按照陣列的下標取值的。如果想要取第二頁,那麼,如果按照mysql的分頁規則,那麼,redis的size需要加上offset的值,否則分頁將不正確。(以上僅限Redis的集合類)

2)在從資料庫中獲取帶狀態的資料時,一定要加上狀態。比如,現在要獲取一個任務,這個任務的狀態型別有下線、刪除和有效狀態,那麼,在獲取某個任務時,一定要帶狀態,否則當有相同資料但狀態不同時,就會產生錯誤。

3) 在使用Map時,一定要注意資料型別要一致。
例如:

Map<String,String> datas = new HashMap<>();
datas.put("10001","haha");
datas.put("10002","oo");

Long testKey = 10001L;
String res = datas.get(testKey);

//不要以為res的值為“haha”,那你就錯了,res的值為null.
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

  檢視原始碼變可以知道,Map的方法:V get(Object key); 其實是將key鍵當做物件來處理的,因此,會導致取不到結果,正確的做法是將Long型別的key轉化為String型或者儲存和取值儲存一樣的方式,如下:

String res = datas.get(String.valueOf(testKey));
  • 1

這樣就可以得到正確的結果。

注意

1) 自定義物件比較記得過載hashCode和equals方法
  在使用CollectionUtils時,一定要小心使用者自定義物件類集合的比較,因為這些自定義類如果沒有過載hashCode和equals方法時,將得不到你想要的結果。

2)編寫MySQL語句時,對每個欄位都加‘`field_name`’
  MySQL環境對sql語句中出現關鍵字時,如果沒有加“` `”的話,會報語法錯誤,導致專案出現Server Error,因此,以後專案編碼過程中最好都帶“` `”,防止不必要的bug,增加不必要的Bug修復操作。

3) 小心整形數相除得不到小數部分
  兩個整形相除會得不到小數部分,因此需要轉化除數。
例如:
double res = 10 / 3.0;
必須將3寫成3.0,否則結果將是3.0,而不是3.333

4)基本資料型別的包裝類物件之間值的比較,全部都用equals方法比較
  對於 Integer var=?在-128 至 127 之間的賦值, Integer 物件是在
IntegerCache. cache 產生,會複用已有物件,這個區間內的 Integer 值可以直接使用==進行
判斷,但是這個區間之外的所有資料,都會在堆上產生,並不會複用已有物件,這是一個大坑,
推薦使用 equals 方法進行判斷。

未完,待續…..

由於小編能力有限,如果更好的建議,歡迎告知,謝謝!