Effective Java 在工作中的應用總結(jié)
一 創(chuàng)建和銷毀對象篇
1 若有多個構(gòu)造器參數(shù)時,優(yōu)先考慮構(gòu)造器
當類構(gòu)造包含多個參數(shù)時,同學們會選擇 JavaBeans
模式。在這種模式下,可以調(diào)用一個無參構(gòu)造器來創(chuàng)建對象,然后調(diào)用 setter
方法來設(shè)置必要和可選的參數(shù)。目前較受歡迎的方法之一如在類上加入 Lombok
提供的@Data注解,來自動生成getter/setter
、equals
等方法。但是JavaBeans
模式無法將類做成不可變(immutable
,詳見“使可變形最小化”章節(jié))。這就需要開發(fā)者自己掌控值的更新情況,確保線程安全等。
推薦:Builder模式
Builder
模式通過 builder
對象上,調(diào)用類似 setter
的方法,設(shè)置相關(guān)的參數(shù)(類似 Proto Buffers
)。最后,通過調(diào)用 build 方法來生成不可變的對象(immutable object
)。使用 Builder
模式的方法之一包括在類上加入 Lombok
提供的 @Builder
注解。
應用:API Request & Response
在微服務架構(gòu)中,服務的請求(request
)和響應(response
)往往包含較多參數(shù)。在處理請求的過程中,筆者也常常會擔心誤操作修改了請求的內(nèi)容。所以,筆者傾向使用Builder模式。
我們可使用Builder
模式來構(gòu)建該類型對象。在構(gòu)建過程中,若需要引入額外邏輯(e.g. if-else
),可先返回Builder對象,最后再調(diào)用build
方法。
import lombok.Builder; /** 請求類 */ @Builder public class SampleRequest { private String paramOne; private int paramTwo; private boolean paramThree; } /** 響應類 */ @Builder public class SampleResponse { private boolean success; } /** 服務接口 */ public interface SampleFacade { Result<SampleResponse> rpcOne(RequestParam<SampleRequest>); } /** 調(diào)用 */ public void testRpcOne() { SampleRequest request = SampleRequest.builder().paramOne("one").paramTwo(2).paramThree(true).build(); Result<SampleResponse> response = sampleFacade.rpcOne(request); }
2 通過私有構(gòu)造器強化不可實例化的能力
有些類,例如工具類(utility class
),只包含靜態(tài)字段和靜態(tài)方法。這些類應盡量確保不被實例化,防止用戶誤用。
推薦:私有化類構(gòu)造器
為了防止誤導用戶,認為該類是專門為了繼承而設(shè)計的,我們可以將構(gòu)造器私有化。
public class SampleUtility { public static String getXXX() { return "test"; } /** 私有化構(gòu)造器 */ private SampleUtility() {} } /** 直接調(diào)用方法 */ public static void main(String[] args) { System.out.println(SampleUtility.getXXX()); }
二 類和接口篇
1 最小化類和成員的可訪問性
盡可能地使每個類或者成員不被外界訪問。
推薦:有的時候,為了測試,我們不得不將某些私有的(private
)類、接口或者成員變成包級私有的(package-private
)。這里,筆者推薦大家使用 Guava
提供的 @VisiableForTesting
注解,來提示這是為了測試而使可訪問級別變?yōu)榘壦接?,放寬了限制?/p>
import com.google.common.annotations.VisibleForTesting; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) String getXXX() { return "test"; }
此外,也有小伙伴推薦 PowerMock
單元測試框架。PowerMock
是 Mockito
的加強版,可以實現(xiàn)完成對private/static/final
方法的Mock
(模擬)。通過加入 @PrepareForTest
注解來實現(xiàn)。
public class Utility { private static boolean isGreaterThan(int a, int b) { return a > b; } private Utility() {} } /** 測試類 */ import org.junit.Test; import org.junit.jupiter.api.Assertions; import org.junit.runner.RunWith; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; import org.powermock.reflect.Whitebox; @RunWith(PowerMockRunner.class) @PrepareForTest({Utility.class}) public class UtilityTest { @Test public void test_privateIsGreaterThan_success() throws Exception { /** 測試私有的 isGreaterThan 方法 */ boolean result = Whitebox.invokeMethod(Utility.class, "isGreaterThan", 3, 2); Assertions.assertTrue(result); } }
2 使可變形最小化
不可變類(immutable class
)是指類對應的實例被創(chuàng)建后,就無法改變其成員變量值。即實例中包含的所有信息都必須在創(chuàng)建該實例的時候提供,并在對象的生命周期內(nèi)固定不變。
不可變類一般采用函數(shù)(
functional
)模式,即對應的方法返回一個函數(shù)的結(jié)果,函數(shù)對操作數(shù)進行運算但并不修改它。與之相對應的更常見的是過程的(procedure
)或者命令式的(imperative
)做法。使用這些方法時,將一個過程作用在它們的操作數(shù)上,會導致它的狀態(tài)發(fā)生改變。
如在“若有多個構(gòu)造器參數(shù)時,優(yōu)先考慮構(gòu)造器”一節(jié)中提到,不可變對象比較簡單,線程安全,只有一種狀態(tài)。使用該類的開發(fā)者無需再做額外的工作來維護約束關(guān)系。另外,可變的對象可以有任意復雜的狀態(tài)。若 mutator
方法(e.g. update
)無詳細的描述,開發(fā)者需要自行閱讀方法內(nèi)容。筆者經(jīng)常會花費較多時間弄清楚在某方法內(nèi),可變對象的哪些字段被更改,方法結(jié)束后會不會影響后續(xù)的對象操作。筆者推薦傳入不可變對象,基于此用更新的參數(shù)創(chuàng)建新的不可變對象返回。雖然會創(chuàng)建更多的對象,但是保證了不可變形,以及更可讀性。
推薦:Guava Collection之Immutable類
筆者在日常開發(fā)中傾向?qū)?Immutable
類(ImmutableList
,ImmutableSet
,ImmuableMap
)和上文提到的函數(shù)模式集合,實現(xiàn)mutator
類方法。
import static com.google.common.collect.ImmutableList.toImmutableList; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; /** 推薦 */ private static final ImmutableMap<String, Integer> SAMPLE_MAP = ImmutableMap.of("One", 1, "Two", 2); /** 推薦:確保原input列表不會變化 */ public ImmutableList<TestObj> updateXXX(ImmutableList<TestObj> input) { return input.stream() .map(obj -> obj.setXXX(true)) .collect(toImmutableList()); } /** 不推薦:改變input的信息 */ public void filterXXX(List<TestObj> input) { input.forEach(obj -> obj.setXXX(true)); }
三 泛型篇
1 列表優(yōu)先于數(shù)組
數(shù)組是協(xié)變的(covariant
),即Sub
為Super
的子類型,那么數(shù)組類型Sub[]
就是Super[]
的子類型;數(shù)組是具體化的,在運行時才知道并檢查它們的元素類型約束。而泛型是不可變的和可擦除的(即編譯時強化它們的類型信息,并在運行時丟棄)。
需要警惕 public static final
數(shù)組的出現(xiàn)。很有可能是個安全漏洞!
四 方法篇
1 校驗參數(shù)的有效性
若傳遞無效的參數(shù)值給方法,這個方法在執(zhí)行復雜、耗時邏輯之前先對參數(shù)進行了校驗(validation
),便很快就會失敗,并且可清楚地拋出適當?shù)漠惓?。若沒有校驗它的參數(shù),就可能會在后續(xù)發(fā)生各種奇怪的異常,有時難以排查定位原因。
筆者認為,微服務提供的API request
也應沿用這一思想。即在API 請求被服務處理之前,先進行參數(shù)校驗。每個request
應與對應的request validator
綁定。若參數(shù)值無效,則拋出特定的ClientException(e.g. IllegalArgumentException
)。
2 謹慎設(shè)計方法簽名
謹慎地選擇方法的名稱:
- 執(zhí)行某個動作的方法通常用動詞或者動詞短語命名:
createXXX
,updateXXX
,removeXXX
,convertXXX
,generateXXX
- 對于返回
boolean
值的方法,一般以is
開頭:isValid
,isLive
,isEnabled
避免過長的參數(shù)列表:目標是四個參數(shù),或者更少。
- 當參數(shù)過多時,筆者會使用Pair,Triple或輔助類(e.g. 靜態(tài)成員類)
public class SampleListener { public ConsumeConcurrentlyStatus consumeMessage(String input) { SampleResult result = generateResult(input); ... } private static SampleResult generateResult(String input) { ... } /** 輔助類 */ private static class SampleResult { private boolean success; private List<String> xxxList; private int count; } }
3 返回零長度的數(shù)組或者集合,而不是null
若一個方法返回 null
而不是零長度的數(shù)組或者集合,開發(fā)者需要加入 != null
的檢查,有時容易忘記出錯,報NullpointerException
。
說到此,筆者想額外提一下 Optional
。網(wǎng)絡上有很多關(guān)于 Optional
和 null
的使用討論。Optional
允許調(diào)用者繼續(xù)一系列流暢的方法調(diào)用(e.g. stream.getFirst().orElseThrow(() -> new MyFancyException()))。
以下為筆者整理的觀點。
/** 推薦:提示返回值可能為空。*/ public Optional<Foo> findFoo(String id); /** * 中立:稍顯笨重 * 可考慮 doSomething("bar", null); * 或者重載 doSomething("bar"); 和 doSomething("bar", "baz"); **/ public Foo doSomething(String id, Optional<Bar> barOptional); /** * 不推薦:違背 Optional 設(shè)計的目的。 * 當 Optional 值缺省時,一般有3種處理方法:1)提供代替的值;2)調(diào)用方法提供代替的值;3)拋出異常 * 這些處理方法可以在字段初始或賦值的時候處理。 **/ public class Book { private List<Pages> pages; private Optional<Index> index; } /** * 不推薦:違背 Optional 設(shè)計的目的。 * 若為缺省值,可直接不放入列表中。 **/ List<Optional<Foo>>
五 通用程序設(shè)計篇
1 如果需要精確的答案,請避免使用float和double
float
和 double
類型主要用于科學工程計算。它們執(zhí)行二進制浮點運算,為了在數(shù)值范圍上提供較為精準的快速近似計算。但是,它們并不能提供完全精確的結(jié)果,尤其不適合用于貨幣計算。float 或者 double 精確地表示0.1 是不可行的。
若需系統(tǒng)來記錄十進制小數(shù)點,可使用BigDecimal
。
2 基本類型優(yōu)先于裝箱基本類型
基本類型(primitive
)例如 int
、double
、long
和 boolean
。每個基本類型都有一個對應的引用類型,稱作裝箱基本類型(boxed primitive
),對應為Integer
、Double
、Long
和 Boolean
。如書中提到,它們的區(qū)別如下:
/** 推薦 */ public int sum(int a, int b) { return a + b; } /** 不推薦:不必要的裝箱 */ public Integer sum(Integer a, Integer b) { return a + b; }
若無特殊的使用場景,推薦總是使用基本類型。若不得不使用裝箱基本類型,注意 == 操作和 NullPointerException
異常。裝箱基本類型的使用場景:
- 作為集合中的元素(e.g. Set<Long>)
- 參數(shù)化類型(e.g. ThreadLocal<Long>)
- 反射的方法調(diào)用
六 異常
1 每個方法拋出的異常都要有文檔
始終要單獨地聲明受檢的異常,并且利用
Javadoc
的@throws
標記,準確地記錄下拋出每個異常的條件。
在日常工作中,筆者調(diào)用其他組的 API 時,有時會發(fā)現(xiàn)一些意料之外的異常。良好的文檔記錄,可以幫助 API 調(diào)用者更好得處理相關(guān)的異常。文檔記錄可包括:異常的類型,異常的 error code
,和描述。
2 其他
一些公司將 API 產(chǎn)生的異常分成 ClientException
和 ServerException
。一般 ClientException
(e.g. 無效的服務 request ) 是由調(diào)用方非常規(guī)調(diào)用 API 導致的異常處理,可不在服務端主要的異常監(jiān)測范圍中。而 ServerException
(e.g. 數(shù)據(jù)庫查詢超時)是由服務端自身原因?qū)е碌膯栴},平時需要著重監(jiān)測。
引用:
Bloch, Joshua. 2018. Effective Java, 3rd Edition
NPM鏡像站全新上線:
阿里云開源鏡像站是由阿里云提供的開源組件、開源操作系統(tǒng)等工具鏡像站。NPM鏡像站全新上線,提高開發(fā)效率,讓您的構(gòu)建更加迅速。
到此這篇關(guān)于Effective Java
在工作中的應用總結(jié)的文章就介紹到這了,更多相關(guān)Effective Java
的應用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java中l(wèi)ambda表達式實現(xiàn)aop切面功能
本文主要介紹了Java中l(wèi)ambda表達式實現(xiàn)aop切面功能,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-02-02解決idea中Terminal終端無法執(zhí)行GIT命令+Terminal 中文亂碼問題
這篇文章主要介紹了解決idea中Terminal終端無法執(zhí)行GIT命令+Terminal 中文亂碼問題,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07Mybatis查不到數(shù)據(jù)查詢返回Null問題
mybatis突然查不到數(shù)據(jù),查詢返回的都是Null,但是 select count(*) from xxx查詢數(shù)量,返回卻是正常的。好多朋友遇到這樣的問題不知所措,下面小編通過本教程簡單給大家說明下2016-08-08使用mybatis-plus-generator進行代碼自動生成的方法
這篇文章主要介紹了使用mybatis-plus-generator進行代碼自動生成的方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-06-06