java微妙_编码Java时的10个微妙的最佳实践
java微妙
這是10條最佳實(shí)踐的列表,這些最佳實(shí)踐比您的平均Josh Bloch有效Java規(guī)則要微妙得多。 盡管Josh Bloch的列表很容易學(xué)習(xí),并且涉及日常情況,但此處的列表包含了涉及API / SPI設(shè)計的較不常見的情況,盡管這些情況可能會產(chǎn)生很大的影響。
我在編寫和維護(hù)jOOQ時遇到了這些問題, jOOQ是Java中的內(nèi)部DSL建模SQL。 作為內(nèi)部DSL,jOOQ最大限度地挑戰(zhàn)了Java編譯器和泛型, 將泛型,可變參數(shù)和重載組合在一起,這是Josh Bloch可能不推薦使用的“平均API”。
讓我與您分享編碼Java時的10個微妙的最佳實(shí)踐:
1.記住C ++析構(gòu)函數(shù)
還記得C ++析構(gòu)函數(shù)嗎? 沒有? 然后,您可能會很幸運(yùn),因為您無需再調(diào)試任何代碼,因為在刪除對象后未釋放分配的內(nèi)存,從而不會導(dǎo)致內(nèi)存泄漏。 感謝Sun / Oracle實(shí)現(xiàn)垃圾回收!
但是,銷毀者對他們具有一個有趣的特征。 通常以相反的順序釋放內(nèi)存是有意義的。 在使用類似析構(gòu)函數(shù)的語義進(jìn)行操作時,也要在Java中記住這一點(diǎn):
- 當(dāng)使用@Before和@After JUnit批注時
- 分配時,釋放JDBC資源
- 調(diào)用超級方法時
還有各種其他用例。 這是一個具體示例,顯示了如何實(shí)現(xiàn)某些事件偵聽器SPI:
@Override public void beforeEvent(EventContext e) {super.beforeEvent(e);// Super code before my code }@Override public void afterEvent(EventContext e) {// Super code after my codesuper.afterEvent(e); }另一個臭名昭著的餐飲哲學(xué)家問題就是一個很好的例子,說明了為什么這很重要。
餐飲哲學(xué)家。 在這里看到: http : //adit.io/posts/2013-05-11-The-Dining-Philosophers-Problem-With-Ron-Swanson.html
規(guī)則 :無論何時使用before / after,allocate / free,take / return語義實(shí)現(xiàn)邏輯,請考慮after / free / return操作是否應(yīng)按相反的順序執(zhí)行操作。
2.不要相信您早期的SPI發(fā)展判斷
向消費(fèi)者提供SPI是允許他們將自定義行為注入您的庫/代碼中的簡便方法。 不過請注意,您的SPI演變判斷可能會欺騙您,使您認(rèn)為您(不需要)該附加參數(shù) 。 確實(shí), 不應(yīng)及早添加任何功能。 但是一旦發(fā)布了SPI,并決定遵循語義版本控制 ,當(dāng)您意識到在某些情況下可能還需要另一個參數(shù)時,您會后悔自己在SPI中添加了一個愚蠢的單參數(shù)方法:
interface EventListener {// Badvoid message(String message); }如果還需要消息ID和消息源怎么辦? API的發(fā)展將阻止您輕松地將該參數(shù)添加到上述類型。 使用Java 8,您可以添加防御者方法來“捍衛(wèi)”您不良的早期設(shè)計決策:
interface EventListener {// Baddefault void message(String message) {message(message, null, null);}// Better?void message(String message,Integer id,MessageSource source); }注意,不幸的是,防御者方法不能定為final 。
但是,比使用數(shù)十種方法污染SPI更好的方法是,僅為此目的使用上下文對象(或參數(shù)對象) 。
interface MessageContext {String message();Integer id();MessageSource source(); }interface EventListener {// Awesome!void message(MessageContext context); }與EventListener SPI相比,您可以更輕松地開發(fā)MessageContext API,因為實(shí)施該應(yīng)用程序的用戶將更少。
規(guī)則 :無論何時指定SPI,都應(yīng)考慮使用上下文/參數(shù)對象,而不要編寫帶有固定數(shù)量參數(shù)的方法。
備注 :通常也可以通過專用的MessageResult類型(可以通過構(gòu)建器API構(gòu)造)來傳遞結(jié)果,這是一個好主意。 這將為您的SPI增加更多的SPI演進(jìn)靈活性。
3.避免返回匿名,本地或內(nèi)部類
Swing程序員可能有幾個鍵盤快捷鍵可以為其數(shù)百個匿名類生成代碼。 在許多情況下,創(chuàng)建它們很不錯,因為您可以本地遵守接口,而無需經(jīng)歷思考完整SPI子類型生命周期的“麻煩”。
但是,您不應(yīng)該過于頻繁地使用匿名,局部或內(nèi)部類,原因很簡單:它們保留對外部實(shí)例的引用。 并且,如果您不小心,它們會將外部實(shí)例拖到任何地方,例如,拖到本地類之外的某個范圍。 這可能是內(nèi)存泄漏的主要來源,因為整個對象圖會突然以微妙的方式糾纏在一起。
規(guī)則 :每當(dāng)您編寫匿名,本地或內(nèi)部類時,請檢查是否可以使其成為靜態(tài)類,甚至是常規(guī)頂級類。 避免將匿名,本地或內(nèi)部類實(shí)例從方法返回到外部作用域。
備注 :對于簡單對象實(shí)例化,圍繞雙花括號有一些聰明的做法:
new HashMap<String, String>() {{put("1", "a");put("2", "b"); }}這利用了JLS§8.6中指定的 Java實(shí)例初始化程序 。 看起來不錯(也許有點(diǎn)奇怪),但確實(shí)是個壞主意。 原來是完全獨(dú)立的HashMap實(shí)例現(xiàn)在將保留對外部實(shí)例的引用,無論發(fā)生什么情況。 此外,您將創(chuàng)建一個額外的類供類加載器管理。
4.立即開始編寫SAM!
Java 8正在敲門。 隨Java 8一起提供lambda ,無論您是否喜歡。 不過,您的API使用者可能會喜歡它們,因此您最好確保他們可以盡可能多地使用它們。 因此,除非您的API接受簡單的“標(biāo)量”類型(例如int , long , String , Date ,否則您的API應(yīng)盡可能多地接受SAM。
什么是SAM? SAM是單一抽象方法[Type]。 也稱為功能接口 ,很快將使用@FunctionalInterface注釋進(jìn)行注釋 。 這與規(guī)則2配合得很好,其中EventListener實(shí)際上是SAM。 最好的SAM是具有單個參數(shù)的SAM,因為它們將進(jìn)一步簡化lambda的編寫。 想象寫作
listeners.add(c -> System.out.println(c.message()));代替
listeners.add(new EventListener() {@Overridepublic void message(MessageContext c) {System.out.println(c.message()));} });想象一下通過jOOX進(jìn)行的 XML處理,它具有幾個SAM:
$(document)// Find elements with an ID.find(c -> $(c).id() != null)// Find their child elements.children(c -> $(c).tag().equals("order"))// Print all matches.each(c -> System.out.println($(c)))規(guī)則 :與您的API使用者保持友好, 現(xiàn)在已經(jīng)編寫SAM /功能接口。
備注 :有關(guān)Java 8 Lambda和改進(jìn)的Collections API的一些有趣的博客文章可以在這里找到:
- http://blog.informatech.cr/2013/04/10/java-optional-objects/
- http://blog.informatech.cr/2013/03/25/java-streams-api-preview/
- http://blog.informatech.cr/2013/03/24/java-streams-preview-vs-net-linq/
- http://blog.informatech.cr/2013/03/11/java-infinite-streams/
5.避免從API方法返回null
我曾經(jīng)寫過一兩次關(guān)于Java的NULL的博客。 我還寫了關(guān)于Java 8的Optional簡介的博客。 從學(xué)術(shù)和實(shí)踐的角度來看,這些都是有趣的話題。
雖然NULL和NullPointerExceptions可能會在Java中困擾一段時間,但是您仍然可以以不會讓用戶遇到任何問題的方式設(shè)計API。 盡可能避免從API方法返回null。 您的API使用者應(yīng)能夠在適用的情況下鏈接方法:
initialise(someArgument).calculate(data).dispatch();在以上代碼段中,所有方法均不應(yīng)返回null。 實(shí)際上,通常使用null的語義(缺少值)應(yīng)該是非常例外的。 在諸如jQuery (或jOOX ,其Java端口)之類的庫中,由于始終對可迭代對象進(jìn)行操作 ,因此完全避免了null。 是否匹配某項與下一個方法調(diào)用無關(guān)。
由于延遲初始化,通常還會出現(xiàn)空值。 在許多情況下,也可以避免延遲初始化,而不會對性能產(chǎn)生任何重大影響。 實(shí)際上,僅應(yīng)謹(jǐn)慎使用惰性初始化。 如果涉及大型數(shù)據(jù)結(jié)構(gòu)。
規(guī)則 :盡可能避免從方法返回null。 僅對“未初始化”或“缺少”的語義使用null。
6.切勿從API方法返回空數(shù)組或列表
雖然在某些情況下從方法返回null可以,但絕對沒有用過返回null數(shù)組或null集合的用例! 讓我們考慮一下丑陋的java.io.File.list()方法。 它返回:
在此抽象路徑名表示的目錄中命名文件和目錄的字符串?dāng)?shù)組。 如果目錄為空,則數(shù)組為空。 如果此抽象路徑名不表示目錄,或者發(fā)生I / O錯誤,則返回null。
因此,處理此方法的正確方法是
File directory = // ...if (directory.isDirectory()) {String[] list = directory.list();if (list != null) {for (String file : list) {// ...}} }空檢查真的必要嗎? 大多數(shù)I / O操作都會產(chǎn)生IOException,但是此操作將返回null。 Null無法保存任何指示為什么發(fā)生I / O錯誤的錯誤消息。 因此,這在三種方式上是錯誤的:
- 空無助于發(fā)現(xiàn)錯誤
- Null不允許將I / O錯誤與不是目錄的File實(shí)例區(qū)分開
- 每個人都會忘記空值
在集合上下文中,“空缺”的概念最好通過空數(shù)組或集合來實(shí)現(xiàn)。 除了再次進(jìn)行延遲初始化外,幾乎沒有有用的數(shù)組或集合。
規(guī)則 :數(shù)組或集合絕不能為空。
7.避免狀態(tài),發(fā)揮作用
HTTP的優(yōu)點(diǎn)在于它是無狀態(tài)的。 所有相關(guān)狀態(tài)都在每個請求和每個響應(yīng)中傳遞。 這對于REST的命名至關(guān)重要: 代表性狀態(tài)轉(zhuǎn)移 。 當(dāng)用Java完成時,這也很棒。 當(dāng)方法接收有狀態(tài)參數(shù)對象時,可以根據(jù)規(guī)則2來考慮它。 如果狀態(tài)是在這樣的對象中傳遞的,而不是從外部操縱的,那么事情會變得非常簡單。 以JDBC為例。 下面的示例從存儲過程中獲取游標(biāo):
CallableStatement s =connection.prepareCall("{ ? = ... }");// Verbose manipulation of statement state: s.registerOutParameter(1, cursor); s.setString(2, "abc"); s.execute(); ResultSet rs = s.getObject(1);// Verbose manipulation of result set state: rs.next(); rs.next();這些使JDBC成為難以處理的API。 每個對象都是難以置信的有狀態(tài)且難以操縱。 具體來說,有兩個主要問題:
- 在多線程環(huán)境中正確處理有狀態(tài)的API非常困難
- 由于沒有記錄狀態(tài),因此很難使全局狀態(tài)資源可用
阿甘正傳的戲劇海報,版權(quán)所有?1994, 派拉蒙影業(yè) 。 版權(quán)所有。 可以相信上述用法滿足了所謂的合理使用
規(guī)則 :實(shí)施更多的功能樣式。 通過方法參數(shù)傳遞狀態(tài)。 操縱較少的對象狀態(tài)。
8.短路equals()
這是一個低落的果實(shí)。 在大型對象圖中,如果所有對象的equals()方法首先便宜地比較身份,則可以顯著提高性能:
@Override public boolean equals(Object other) {if (this == other) return true;// Rest of equality logic... }請注意,其他短路檢查可能還涉及空檢查,該檢查也應(yīng)該存在:
@Override public boolean equals(Object other) {if (this == other) return true;if (other == null) return false;// Rest of equality logic... }規(guī)則 :短路所有equals()方法以獲得性能。
9.嘗試使方法默認(rèn)為final
有些人對此持不同意見,因為默認(rèn)情況下使事情最終完成與Java開發(fā)人員所習(xí)慣的相反。 但是,如果您完全控制所有源代碼,則默認(rèn)情況下將方法設(shè)為final絕對沒有問題,因為:
- 如果確實(shí)需要重寫方法(確實(shí)嗎?),仍然可以刪除final關(guān)鍵字
- 您再也不會意外覆蓋任何方法
這特別適用于靜態(tài)方法,在這些方法中,“覆蓋”(實(shí)際上是陰影)幾乎沒有任何意義。 最近,我在Apache Tika中遇到了一個非常糟糕的陰影靜態(tài)方法示例。 考慮:
- TaggedInputStream.get(InputStream)
- TikaInputStream.get(InputStream)
TikaInputStream擴(kuò)展了TaggedInputStream并使用完全不同的實(shí)現(xiàn)來隱藏其靜態(tài)get()方法。
與常規(guī)方法不同,靜態(tài)方法不會互相覆蓋,因為調(diào)用站點(diǎn)在編譯時會綁定靜態(tài)方法調(diào)用。 如果您不走運(yùn),您可能會偶然得到錯誤的方法。
規(guī)則 :如果您完全控制自己的API,請嘗試在默認(rèn)情況下盡可能多地使用final方法。
10.避免方法(T…)簽名
偶爾接受一個Object...參數(shù)的“ accept-all” varargs方法沒有任何問題:
void acceptAll(Object... all);編寫這樣的方法給Java生態(tài)系統(tǒng)帶來一點(diǎn)JavaScript的感覺。 當(dāng)然,您可能希望將實(shí)際類型限制為在實(shí)際情況下更受限的類型,例如String... 而且由于您不想限制太多,您可能會認(rèn)為用通用T代替Object是一個好主意:
void acceptAll(T... all);但事實(shí)并非如此。 T總是可以推斷為Object。 實(shí)際上,您最好不要將泛型與上述方法一起使用。 更重要的是,您可能認(rèn)為可以重載上述方法,但是您不能:
void acceptAll(T... all); void acceptAll(String message, T... all);看起來您可以選擇將String消息傳遞給該方法。 但是這里的電話怎么辦?
acceptAll("Message", 123, "abc");編譯器會推斷<? extends Serializable & Comparable<?>> 為T <? extends Serializable & Comparable<?>> ,這使調(diào)用變得模棱兩可!
因此,每當(dāng)您擁有“所有人都接受”的簽名(即使它是通用的)時,您將永遠(yuǎn)無法再次安全地重載它。 API使用者可能只是幸運(yùn)地“偶然地”選擇了編譯器選擇“正確的”最具體的方法。 但是他們也可能被欺騙使用“ accept-all”方法,或者根本無法調(diào)用任何方法。
規(guī)則 :如果可以,請避免“全部接受”簽名。 如果不能,則不要重載這種方法。
結(jié)論
Java是野獸。 與其他更高級的語言不同,它已經(jīng)發(fā)展到今天。 那可能是一件好事,因為在Java的發(fā)展速度下,已經(jīng)有數(shù)百項警告,這些警告只能通過多年的經(jīng)驗來掌握。
翻譯自: https://www.javacodegeeks.com/2013/08/10-subtle-best-practices-when-coding-java.html
java微妙
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的java微妙_编码Java时的10个微妙的最佳实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 雪人英语怎么读 雪人词语的英文
- 下一篇: GraphQL在Wildfly群上