JAVA基礎之代碼簡潔之道

JAVA基礎之代碼簡潔之道

背景


軟件質量,不但依賴於架構及項目管理,更與代碼質量緊密相關。簡潔高效的代碼不但易於閱讀,更能避免潛在BUG與風險,提升代碼質量。近期,一位Oracle程序員在Hacker News上吐槽本身的工做,引發了熱議。 avatarjava

這個工程師的核心痛點是,Oracle經歷長期的產品線迭代,代碼異常龐大、邏輯複雜,整個代碼中充斥着神祕的宏命令。每新增一個特性或者修復BUG,該工程師都須要大量的調研,當心謹慎的進行着平常的工做。而Oracle每次的版本發佈都經歷數百萬次的測試,腦補一下,如噩夢通常。那麼咱們應該如何編寫簡潔高效的代碼呢?其實業內有過不少相關書籍,好比經典的書籍有《代碼整潔之道》、《編寫可讀代碼的藝術》、《重構:改善既有代碼的設計》,可用於修煉內功。以及咱們有嚴格的代碼規範以及方便的靜態代碼掃描工具,可用於增強研發代碼質量能力。程序員

簡潔之術


其實代碼規範和靜態代碼掃描工具可以幫助咱們完成不少代碼簡潔的工做。諸如:註釋、命名、方法、異常、單元測試等多個方面。但卻沒法總結了一些代碼簡潔最佳實踐,其實Java是面向對象語音,而面向對象的特徵是封裝、繼承、多態,巧妙的運用這三大特性、理解Java的一些關鍵字特性、語音特性、閱讀JDK源碼,就能夠寫出相對簡潔的代碼了。算法

簡化邏輯

// 修改前``編程

if(list.size()>0) {
    return true;
} else {
    return false;    
}

// 修改後設計模式

return list.size()>0;

(1) if/else 語法:if語句包含一個布爾表達式,當if語句的布爾表達式值爲false時,else語句塊會被執行;數組

(2) return 關鍵字:返回一個任意類型的值;安全

(3) list.size()>0 表達式:list.size()方法自己是一個返回int類型數值的函數,而與>0組成了一個布爾表達式;多線程

省略無心義賦值

avatar

(1)局部變量list的數據類型與該方法的返回值類型一致,而多餘的變量也將會增長JVM垃圾回收的消耗;架構

(2)局部變量list只是負責接收了mapper.queryList(params)的返回值,而並無其餘邏輯處理;app

(3)此代碼存在於service層和mapper層之間,能夠在框架層面進一步抽象,利用註解、java8 default方法等進一步改進;

最小化判斷

avatar

代碼中if else的存在只是由於sendMessage函數的第二個參數會有兩種狀況(成功/失敗),儘可能讓判斷最小化;

set方法治理

avatar

(1)大坨的set方法很影響代碼可讀性,可封裝成特定方法或者使用lombok工具簡化代碼;

(2)局部變量就近聲明,增長可讀性,局部變量聲明和使用地方距離遙遠,會致使的讀者頻繁滑動;

(3)可不聲明變量儘可能不要聲明多餘的變量,冗餘代碼;(如date、time兩段代碼);

巧用JAVA8特性-函數式編程簡化代碼

JAVA8特性「函數式編程」,使用Lambdas咱們能作到什麼?

(1)遍歷集合(List、Map等)、Sum、Max、Min、Avg、Sort、Distinct等等 (2)函數接口 (3)謂詞(Predicate)使用 (4)實現Map和Reduce (5)實現事件處理/簡化多線程

內、外部循環

avatar

上述代碼是傳統方式的遍歷一個List的寫法,簡單來講主要有3個不足:

(1)只能順序處理list中的數據(process one by one)

(2)不能充分利用多核cpu

(3)不利於編譯器優化(jit)

而使用函數式編程能規避上面的三個問題:

(1)不必定須要順序處理List中的元素,順序能夠不肯定

(2)能夠並行處理,充分利用多核CPU的優點

(3)有利於JIT編譯器對代碼進行優化

(4)代碼看起來更簡潔,徹底交給編譯器內部循環

default方法

在Java8中,接口中的方法能夠被實現,用關鍵字 default 做爲修飾符來標識,接口中被實現的方法叫作 default 方法。使用default方法,當接口發生改變的時候,實現類不須要作改動,全部的子類都會繼承 default 方法。

avatar

當一個接口擴展另一個包含默認方法的接口的時候,有如下3種處理方式。

(1)徹底無視默認方法(直接繼承上級接口的默認方法)

(2)從新申明默認方法爲抽象方法(無實現,具體子類必需再次實現該方法)

(3)從新實現默認方法(重寫了默認方法的實現,依然是一個默認方法)

日期處理

Java8中新增了LocalDate和LocalTime接口,爲何要搞一套全新的處理日期和時間的API?由於舊的java.util.Date實在是太難用了。

(1)java.util.Date月份從0開始,一月是0,十二月是11,變態吧!java.time.LocalDate月份和星期都改爲了enum,就不可能再用錯了。

(2)java.util.Date和SimpleDateFormatter都不是線程安全的,而LocalDate和LocalTime和最基本的String同樣,是不變類型,不但線程安全,並且不能修改。

(3)java.util.Date是一個「萬能接口」,它包含日期、時間,還有毫秒數,若是你只想用java.util.Date存儲日期,或者只存儲時間,那麼,只有你知道哪些部分的數據是有用的,哪些部分的數據是不能用的。在新的Java8中,日期和時間被明確劃分爲LocalDate和LocalTime,LocalDate沒法包含時間,LocalTime沒法包含日期。

固然,LocalDateTime才能同時包含日期和時間。

新接口更好用的緣由是考慮到了日期時間的操做,常常發生往前推或日後推幾天的狀況。用java.util.Date配合Calendar要寫好多代碼,並且通常的開發人員還不必定能寫對。

一、Clock時鐘。Clock類提供了訪問當前日期和時間的方法,Clock是時區敏感的,能夠用來取代System.currentTimeMillis(),來獲取當前的微秒數。某一個特定的時間點也可使用Instant類(爲Final類)來表示,Instant類也能夠用來建立老的java.util.Date對象。

avatar

二、LocalDate和LocalTime、LocalDateTime(均爲Final類,不帶時區)的一系列計算。LocalDateTime和Instant二者很像都是不帶時區的日期和時間,Instant中是不帶時區的即時時間點。好比:兩我的都是2018年4月14日出生的,一個出生在北京,一個出生在紐約;看上去他們是一塊兒出生的(LocalDateTime的語義),其實他們是有時間差的(Instant的語義)

avatar

Streams與集合

Stream是對集合的包裝,一般和lambda一塊兒使用。使用lambdas能夠支持許多操做。如 map,filter,limit,sorted,count,min,max,sum,collect等等。 一樣,Stream使用懶運算,他們並不會真正地讀取全部數據。遇到像getFirst()這樣的方法就會結束鏈式語法,經過下面一系列例子介紹:好比我有個Person類,就是一個簡單的pojo, 針對這個對象,咱們可能有這樣一系列的運算需求。

avatar avatar

傳遞行爲,而不只僅是傳值

//sumAll算法很簡單,完成的是將List中全部元素相加。

public static int sumAll(List<Integer> numbers) {

    int total = 0;
    
    for (int number : numbers) {
    
        total += number;
        
    }
    
    return total;
    
}

sumAll算法很簡單,完成的是將List中全部元素相加。某一天若是咱們須要增長一個對List中全部偶數求和的方法sumAllEven,那麼就產生了sumAll2,以下:

public static int sumAll2(List<Integer> numbers) {

    int total = 0;
    
    for (int number : numbers) {
    
        if (number % 2 == 0) {
        
            total += number;  
            
        }  
        
    }
    
    return total;
    
}

又有一天,咱們須要增長第三個方法:對List中全部大於3的元素求和,那是否是繼續加下面的方法呢?sumAll3

public static int sumAll3(List<Integer> numbers) {

    int total = 0;
    
    for (int number : numbers) {
    
        if (number > 3) {
        
            total += number;  
            
        }  
        
    }
    
    return total;
    
}

觀察這三個方法咱們發現,有不少重複內容,惟一不一樣的是方法中的if條件不同(第一個能夠當作if(true)),若是讓咱們優化,可能想到的第一種重構就是策略模式吧,代碼以下:

avatar

這無疑使用設計模式的方式優化了冗餘代碼,可是可能要額外增長几個類,之後擴展也要新增,下面看看使用lambda如何實現,聲明方法:第一個參數仍是咱們以前傳遞的List數組,第二個看起來可能有點陌生,經過查看jdk能夠知道,這個類是一個謂詞(布爾值的函數)

public static int sumAllByPredicate(List<Integer> numbers, Predicate<Integer> p) {  

        int total = 0;  
        
        for (int number : numbers) {  
        
            if (p.test(number)) {  
            
                total += number;  
                
            }  
            
        }  
        
        return total;  
        
    }  
    
//調用:

sumAllByPredicate(numbers, n -> true);

sumAllByPredicate(numbers, n -> n % 2 == 0);

sumAllByPredicate(numbers, n -> n > 3);

代碼是否是比上面簡潔了不少?語義也很明確,重要的是無論之後怎麼變,均可以一行代碼就修改了。。。萬金油啊。

其餘

JAVA8 還推出了不少特性,來簡化代碼。好比String.join函數、Objects類、Base64編碼類。

字符串拼接

avatar

Objects類

avatar

Base64編碼

avatar

總結

好的代碼須要不停的打磨,做爲一個優秀的工程師,咱們應該嚴格遵照,每次提交的代碼要比遷出的時候更好。常常有人說,做爲工程師必定要有團隊精神,但這種精神並非說說而已的,須要實際的行動來體現的。設計模式、JDK的新特性都是咱們能夠藉助的經驗,編碼完成後思考一下,還可不能夠在簡化、優化,不要成爲一個「做惡」的工程師。

做者簡介

馬鐵利,隨行付架構部負責人 & TGO鯤鵬會北京分會會員,10年全棧工程師,擅長微服務分佈式架構設計。主要負責隨行付架構部平常管理;參與構建微服務平臺周邊基礎設施及中間件;負責隨行付對外開源等事宜。

相關文章
相關標籤/搜索