你應該遠離的6個Java特性
我曾花費了無數個小時為各種不同的應用排錯。根據過往的經驗我可以得出這樣一個結論,那就是對于大多數開發者來說,你應該遠離幾個Java SE特性或是APIs。這里所說的大多數開發者指的是一般的Java EE開發者而不是庫設計者或是基礎設施開發者。
</blockquote>坦白地說,從長遠來看,大多數團隊都應該遠離如下的Java特性。不過凡事總有例外的情況。如果你有一個強大的團隊,總是能夠清楚地意識到自己在做 什么,那就按照你的想法去做就行。但對于大多數情況來說,如果你在項目的開發中使用了下面這幾個Java特性,那么從長遠來看你是會后悔的。
這些應該遠離的Java特性有:
- 反射
- 字節碼操縱
- ThreadLocal
- 類加載器
- 弱引用與軟引用
- Sockets
</ul>下面對這些特性進行逐個分析,看看為什么普通的Java開發者應該遠離他們:
反射
在流行的庫如Spring和Hibernate中,反射自然有其用武之地。不過內省業務代碼在很多時候都不是一件好事,原因有很多,一般情況下我總是建議大家不要使用反射。
首先是代碼可讀性與工具支持。打開熟悉的IDE,尋找你的Java代碼的內部依賴,很容易吧。現在,使用反射來替換掉你的代碼然后再試一下,結果如何呢?如果通過反射來修改已經封裝好的對象狀態,那么結果將會變得更加不可控。請看看如下示例代碼:
![]()
如果這樣做就無法得到編譯期的安全保證。就像上面這個示例一樣,你會發現如果getDeclaredField()方法調用的參數輸錯了,那么只有在運行期才能發現。要知道的是,尋找運行期Bug的難度要遠遠超過編譯期的Bug。
最后還要談談代價問題。JIT對反射的優化程度是不同的,有些優化時間會更長一些,而有些甚至是無法應用優化。因此,有時反射的性能損失可以達到幾個數量級的差別。不過在典型的業務應用中,你可能不會注意到這個代價。
總結一下,我覺得在業務代碼中唯一合理(直接)使用反射的場景是通過AOP。除此之外,你最好遠離反射這一特性。
字節碼操縱
如果在Java EE應用代碼中直接使用了CGLIB或是ASM庫,那么我建議你好好審視一下。就像方才我提到的反射帶來的消極影響,使用字節碼操縱所帶來的痛苦可能是反射的好幾倍之多。
更糟糕的是在編譯期你根本就看不到可執行的代碼。從本質上來說,你不知道產品中實際運行的是什么代碼。因此在面對運行期的問題以及調試時,你要花費更多的時間。
ThreadLocal
看到業務代碼中如果出現ThreadLocal會讓我感到顫抖,原因有二。首先,借助于 ThreadLocal,你可以不必顯式通過方法調用就可以傳遞變量,而且會對這種做法上癮。在某些情況下這么做可能是合理的,不過如果不小心,那么我可 以保證最后代碼中會出現大量意想不到的依賴。
第二個原因與我每天的工作有關。將數據存儲在ThreadLocal中很容易造成內存泄漏,至少我所看到的十個永久代泄漏中就有一個是由過量使用 ThreadLocal導致的。連同類加載器及線程池的使用,“java.lang.OutOfMemoryError:Permgen space”就在不遠處等著你呢。
類加載器
首先,類加載器是個很復雜的東西。你必須首先理解他們,包括層次關系、委托機制以及類緩存等等。即便你覺得自己已經精通了類加載器,一開始使用時還是會出現各種各樣的問題,很可能會導致類加載器泄漏問題。因此,我建議大家還是將類加載器留給應用服務器使用吧。
弱引用與軟引用
關于弱引用與軟引用,你是不是只知道他們是什么以及簡單的使用方式而已?現在的你對Java內核有了更好的理解,那會不會使用軟引用重寫所有的緩存呢?這么做可不太好,可不能手里有錘子就到處找鼓敲吧。
你可能很想知道我為什么說緩存不太適用使用軟引用吧。畢竟,使用軟引用來構建緩存可以很好地說明將某些復雜性委托給GC來完成而不是自己去實現這一準則。
下面來舉個例子吧。你使用軟引用構建了一個緩存,這樣當內存行將耗盡時,GC會介入并開始清理。但現在你根本就無法控制哪些對象會從緩存中刪除,很 有可能在下一次緩存中不再有這個對象時重新創建一次。如果內存還是很緊張,又觸發GC執行了一次清理,那么很有可能會出現一個死循環,應用會占用大量 CPU時間,Full GC也會不斷執行。
Sockets
java.net.Socket簡直太難使用了。我認為它的缺陷歸根結底源自其阻塞的本質。在編寫具有Web前端的典型的Java EE應用時,你需要高度的并發性來支持大量的用戶訪問。這時你最不想發生的事情就是讓可伸縮性不那么好的線程池呆在那兒,等待著阻塞的Sockets。
現在已經出現了非常棒的第三方庫來解決這些問題,別自己寫了,嘗試一下Netty吧。
Java出現至今經歷了多次版本更迭,每次也都會有諸多新特性的加入。在日常的Java開發中,你認為存在哪些Java特性是很容易導致問題的呢? 作者提到不建議在普通的應用開發中使用反射,不過對于一些框架或庫的開發,離開反射實際上是無法實現的,例如Spring、Struts2等框架,那么在 一般的Java項目開發中,你覺得哪些地方有使用反射的必要呢?換句話說,如果不使用反射就實現不了功能或是需求。文中作者也不建議使用字節碼操縱,實際 上一些框架在實現某些功能時是必須要使用的,比如說Spring在實現AOP時就使用了Java的動態代理與CGLib庫兩種方式來達成的。那么對于一般 的Java項目來說,哪些地方需要用到字節碼操縱呢?歡迎各位讀者暢所欲言,一起討論這些有趣的話題。
來源: InfoQ中國