FindBugs錯(cuò)誤修改指南 1. EC_UNRELATED_TYPESBug: Call to equals() comparing different types Pattern id: EC_UNRELATED_TYPES, type: EC, category: CORRECTNESS解釋:兩個(gè)不同類型的對象調(diào)用equals方法,如果equals方法沒有被重寫,那么調(diào)用object的==,永遠(yuǎn)不會(huì)相等;如果equals方法被重寫,而且含有instanceof邏輯,那么還是不會(huì)相等。解決方法:應(yīng)該改為str.toString() 2. IM_BAD_CHECK_FOR_ODDBug: Check for oddness that won't work for negative numbers Pattern id: IM_BAD_CHECK_FOR_ODD, type: IM, category: STYLE解釋:如果row是負(fù)奇數(shù),那么row % 2 == -1,解決方法:考慮使用x & 1 == 1或者x % 2 != 0 3. NP_ALWAYS_NULLPattern: Null pointer dereference id: NP_ALWAYS_NULL, type: NP, category: CORRECTNESSA null pointer is dereferenced here. This will lead to a NullPointerException when the code is executed. 4. RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUEBug: Redundant nullcheck of bean1, which is known to be non-null Pattern id: RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE, type: RCN, category: STYLEThis method contains a redundant check of a known non-null value against the constant null.這種方法包含了一個(gè)稱為非空對空值的不斷重復(fù)檢查。修改為: 5. SS_SHOULD_BE_STATICBug: Unread field: ADDRESS_KEY; should this field be static? Pattern id: SS_SHOULD_BE_STATIC, type: SS, category: PERFORMANCEThis class contains an instance final field that is initialized to a compile-time static value. Consider making the field static.解釋:final成員變量表示常量,只能被賦值一次,賦值后值不再改變。這個(gè)類包含的一個(gè)final變量初始化為編譯時(shí)靜態(tài)值。考慮變成靜態(tài)常量解決方法:增加static關(guān)鍵字 6. EQ_COMPARETO_USE_OBJECT_EQUALSBug: RsInterface defines compareTo(Object) and uses Object.equals() Pattern id: EQ_COMPARETO_USE_OBJECT_EQUALS, type: Eq, category: BAD_PRACTICE解釋:第一段代碼,沒有使用instanceof判斷就直接轉(zhuǎn)型,有拋出classcastexception異常的可能。這個(gè)BUG主題是,遵守約定(x.compareTo(y)==0) == (x.equals(y)),強(qiáng)烈建議,但不嚴(yán)格要求。在return 0的時(shí)候,調(diào)用equals方法返回true,因?yàn)樵赑riorityQueue.remove方法中,1.5使用的是compareTo方法,而1.6使用的是equals方法,保證環(huán)境升級的時(shí)候,受影響最小。解決方法:在return 0的時(shí)候,調(diào)用equals方法返回true 7. NM_METHOD_NAMING_CONVENTIONBug: The method name MsmPlanDAOTest.TestViewMsmPlanList() doesn't start with a lower case letter Pattern id: NM_METHOD_NAMING_CONVENTION, type: Nm, category: BAD_PRACTICEMethods should be verbs, in mixed case with the first letter lowercase, with the first letter of each internal word capitalized.解釋:方法應(yīng)該是動(dòng)詞,與第一個(gè)字母小寫混合的情況下,與每個(gè)單詞的首字母大寫的內(nèi)部。解決方法:方法名稱小寫就通過了。8. HE_EQUALS_USE_HASHCODEBug: PerfmSingleGraphPanel$RSCategory defines equals and uses Object.hashCode() Pattern id: HE_EQUALS_USE_HASHCODE, type: HE, category: BAD_PRACTICE解釋:重載了equals方法,卻沒有重載hashCode方法,如果使用object自己的hashCode,我們可以從JDK源代碼可以看到object的hashCode方法是native的,它的值由虛擬機(jī)分配(某種情況下代表了在虛擬機(jī)中的地址或者唯一標(biāo)識),每個(gè)對象都不一樣。所以這很可能違反“Equals相等,hashcode一定相等;hashcode相等,equals不一定相等?!背悄惚WC不運(yùn)用到HashMap/HashTable等運(yùn)用散列表查找值的數(shù)據(jù)結(jié)構(gòu)中。否則,發(fā)生任何事情都是有可能的。關(guān)于何時(shí)改寫hashcode,請參考:在重寫了對象的equals方法后,還需要重寫hashCode方法嗎?關(guān)于編寫高質(zhì)量的equals方法:1.先使用==操作符檢查是否是同一個(gè)對象,==都相等,那么邏輯相等肯定成立;2.然后使用instanceof操作符檢查“參數(shù)是否為正確的類型”;3.把參數(shù)轉(zhuǎn)換成正確的類型;4.對于該類中的非基本類型變量,遞歸調(diào)用equals方法;5.變量的比較順序可能會(huì)影響到equals方法的性能,應(yīng)該最先比較最有可能不一致的變量,或者是開銷最低的變量。當(dāng)你編寫完成equals方法之后,應(yīng)該問自己三個(gè)問題:它是否是對稱的、傳遞的、一致的?解決方法:除非你保證不運(yùn)用到HashMap/HashTable等運(yùn)用散列表查找值的數(shù)據(jù)結(jié)構(gòu)中,請重寫hashcode方法。9. NM_CONFUSINGBug: Confusing to have methods xxx.SellerBrandServiceImpl.getAllGrantSellerBrandsByBrandId(long) and xxx.DefaultSellerBrandManager.getALLGrantSellerBrandsByBrandId(long) Pattern id: NM_CONFUSING, type: Nm, category: BAD_PRACTICEThe referenced methods have names that differ only by capitalization.解釋:同一個(gè)包兩個(gè)類中有一模一樣的兩個(gè)方法(包括參數(shù))解決方法:最好可以修改為不一樣的方法名稱10. MF_CLASS_MASKS_FIELDBug: Field PDHSubCardInstanceDialogCommand.m_instance masks field in superclass ViewNEProperity Pattern id: MF_CLASS_MASKS_FIELD, type: MF, category: CORRECTNESSThis class defines a field with the same name as a visible instance field in a superclass. This is confusing, and may indicate an error if methods update or access one of the fields when they wanted the other.解釋:這是什么意思呢?想要字段也能夠具有多態(tài)性嗎?太迷惑了。當(dāng)你想要更新一個(gè)m_instance時(shí),你要更新哪個(gè)?你用到它時(shí),你知道哪個(gè)又被更新了?解決方法:要么去掉其中一個(gè)字段,要么重新命名。11. NM_CLASS_NAMING_CONVENTIONBug: The class name crossConnectIndexCollecter doesn't start with an upper case letter解釋: Pattern id: NM_CLASS_NAMING_CONVENTION, type: Nm, category: BAD_PRACTICE看到這樣的命名方式,我第一個(gè)反映就是有點(diǎn)暈車!解決方法:類名第一個(gè)字符請大寫。12. RE_POSSIBLE_UNINTENDED_PATTERNBug: "." used for regular expression Pattern id: RE_POSSIBLE_UNINTENDED_PATTERN, type: RE, category: CORRECTNESS解釋:String的split方法傳遞的參數(shù)是正則表達(dá)式,正則表達(dá)式本身用到的字符需要轉(zhuǎn)義,如:句點(diǎn)符號“.”,美元符號“$”,乘方符號“^”,大括號“{}”,方括號“[]”,圓括號“()” ,豎線“|”,星號“*”,加號“+”,問號“?”等等,這些需要在前面加上“\\”轉(zhuǎn)義符。解決方法:在前面加上“\\”轉(zhuǎn)義符。13.IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD外部類:內(nèi)部類:……Bug: Ambiguous invocation of either an outer or inherited method JExtendDialog.onOK() Pattern id: IA_AMBIGUOUS_INVOCATION_OF_INHERITED_OR_OUTER_METHOD, type: IA, category: STYLE解釋:TargetSetupDialog是JExtendDialog的子類,JExtendDialog有一個(gè)onOK方法,但是JExtendDialog的外部類也有一個(gè)onOK方法,到底這個(gè)onOK方法調(diào)用的是它父類onOK方法還是調(diào)用它外部類onOK方法呢,這不免讓人誤解。當(dāng)然這并沒有編譯錯(cuò)誤,實(shí)際上優(yōu)先調(diào)用的是父類JExtendDialog的onOK方法,如果把JExtendDialog的onOK方法去掉,它調(diào)用的就是外部類onOK方法,這個(gè)時(shí)候不能寫成this.onOK,因?yàn)榇藭r(shí)的this并不代表外部類對象。解決方法:如果要引用外部類對象,可以加上“outclass.this”。如果要引用父類的onOK方法,請使用super.onOK()。14. DM_FP_NUMBER_CTORBug: Method OnlineLicenseDAOTest.testUpdateOnlineLicenseByOnlineMerchantId() invokes inefficient Double.valueOf(double) constructor; use OnlineLicenseDAOTest.java:[line 81] instead Pattern id: DM_FP_NUMBER_CTOR, type: Bx, category: PERFORMANCEUsing new Double(double) is guaranteed to always result in a new object whereas Double.valueOf(double) allows caching of values to be done by the compiler, class library, or JVM. Using of cached values avoids object allocation and the code will be faster.Unless the class must be compatible with JVMs predating Java 1.5, use either autoboxing or the valueOf() method when creating instances of Double and Float.解釋:采用new Ddouble(double)會(huì)產(chǎn)生一個(gè)新的對象,采用Ddouble.valueOf(double)在編譯的時(shí)候可能通過緩存經(jīng)常請求的值來顯著提高空間和時(shí)間性能。解決方法:采用Ddouble.valueOf方法類似的案例15. CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLEBug:AlarmSoundManager$SoundProperty defines clone() but doesn't implement Cloneable Pattern id: CN_IMPLEMENTS_CLONE_BUT_NOT_CLONEABLE, type: CN, category: BAD_PRACTICE解釋:SoundProperty類實(shí)現(xiàn)了clone方法,但是沒有實(shí)現(xiàn)Cloneable接口,當(dāng)然這沒有任何問題,但是你應(yīng)該知道你為什么這么做。解決方法:最好實(shí)現(xiàn)Cloneable接口16. STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCEBug: Call to method of static java.text.DateFormat Pattern id: STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCE, type: STCAL, category: MT_CORRECTNESS解釋:TIME_FORMAT是一個(gè)DateFormat靜態(tài)變量,文檔中DateFormat不是線程安全(多個(gè)線程訪問一個(gè)類時(shí),這些線程執(zhí)行順序沒有統(tǒng)一的調(diào)度和約定,如果這個(gè)類的行為仍然是正確的,那么這個(gè)類就是線程安全的??紤]vector的實(shí)現(xiàn))的,如果多個(gè)線程同時(shí)訪問,會(huì)出現(xiàn)意料不到的情況,詳情參見Sun Bug #6231579和Sun Bug #6178997。因此對于DateFormat、SimpleDateFormat、Calendar類對象不建議定義成靜態(tài)成員字段使用,同時(shí)對它們在多線程環(huán)境下的使用請一定要保證同步。另外,多說一句,java為我們提供了很多的封裝手段,比如private關(guān)鍵字、內(nèi)部類、全限定包名等等,我們要充分利用這些手段封裝信息,對外盡量提供最小集。關(guān)于靜態(tài)變量也是如此,就算是vector這種線程安全的類,在無狀態(tài)類中也可能存在并發(fā)的問題,參見:無狀態(tài)類在并發(fā)環(huán)境中絕對安全嗎?解決方法:修改類字段為對象字段,然后改為private,同時(shí)提供get方法,最后對get方法實(shí)現(xiàn)同步機(jī)制。最好連對象字段也去掉,直接在方法里使用,就不存在同步的問題了(不必考慮性能問題,而且DateFormat本身就不必作為對象的字段,我想這也是sun為什么不把它實(shí)現(xiàn)為線程安全的了)。17. SE_NO_SERIALVERSIONIDBug: WindowHandlerManager$MySingleSelectionModel is Serializable; consider declaring a serialVersionUID Pattern id: SE_NO_SERIALVERSIONID, type: SnVI, category: BAD_PRACTICEThis class implements the Serializable interface, but does not define a serialVersionUID field. A change as simple as adding a reference to a .class object will add synthetic fields to the class, which will unfortunately change the implicit serialVersionUID (e.g., adding a reference to String.class will generate a static field class$java$lang$String). Also, different source code to bytecode compilers may use different naming conventions for synthetic variables generated for references to class objects or inner classes. To ensure interoperability of Serializable across versions, consider adding an explicit serialVersionUID.解釋:實(shí)現(xiàn)了Serializable接口,卻沒有實(shí)現(xiàn)定義serialVersionUID字段,序列化的時(shí)候,我們的對象都保存為硬盤上的一個(gè)文件,當(dāng)通過網(wǎng)絡(luò)傳輸或者其他類加載方式還原為一個(gè)對象時(shí),serialVersionUID字段會(huì)保證這個(gè)對象的兼容性,考慮兩種情況:1. 新軟件讀取老文件,如果新軟件有新的數(shù)據(jù)定義,那么它們必然會(huì)丟失。2. 老軟件讀取新文件,只要數(shù)據(jù)是向下兼容的,就沒有任何問題。序列化會(huì)把所有與你要序列化對象相關(guān)的引用(包括父類,特別是內(nèi)部類持有對外部類的引用,這里的例子就符合這種情況)都輸出到一個(gè)文件中,這也是為什么能夠使用序列化能進(jìn)行深拷貝。這種序列化算法給我們的忠告是,不要把一些你無法確定其基本數(shù)據(jù)類型的對象引用作為你序列化的字段(比如JFrame),否則序列化后的文件超大,而且會(huì)出現(xiàn)意想不到的異常。解決方法:定義serialVersionUID字段18.SE_COMPARATOR_SHOULD_BE_SERIALIZABLEBug: ToStringComparator implements Comparator but not Serializable Pattern id: SE_COMPARATOR_SHOULD_BE_SERIALIZABLE, type: Se, category: BAD_PRACTICE解釋:ToStringComparator類實(shí)現(xiàn)了Comparator接口卻沒有實(shí)現(xiàn)Serializable接口,因?yàn)橄馮reeMap這種可序列化數(shù)據(jù)結(jié)構(gòu)(它實(shí)現(xiàn)了Serializable接口)只有當(dāng)比較器繼承了Serializable接口時(shí),它才能被序列化。解決方法:實(shí)現(xiàn)Serializable接口并定義serialVersionUID字段19. ES_COMPARING_STRINGS_WITH_EQBug: Comparison of String objects using == or != Pattern id: ES_COMPARING_STRINGS_WITH_EQ, type: ES, category: BAD_PRACTICE解釋:你確定你已經(jīng)了解string的全部了?如果你不了解,請參考FX大神的博文:請別再拿“String s = new String("xyz");創(chuàng)建了多少個(gè)String實(shí)例”來面試了吧那么,接下來我就開始剝皮了: Object和StringBuilder的toString方法都是返回一個(gè)new String(),跟””不相等。如果你之前是這樣的定義的:String name = “”;OK,它們處于同一個(gè)class常量池,跟””相等。如果在這之前,你使用了String. Intern方法,你是高手,跟””相等。如果你沒有意識到這些問題,卻仍然使用==和!=去比較字符串,那么請不要告訴我是你手滑了= =!解決方法:老實(shí)使用equals方法吧,至少為了保持代碼的清晰性。20. ES_COMPARING_STRINGS_WITH_EQBug: Comparison of String parameter using == or != Pattern id: ES_COMPARING_PARAMETER_STRING_WITH_EQ, type: ES, category: BAD_PRACTICE解釋:跟前面的例子差不多,你如果不能確保propertyName來源于常量池,那么用==比較沒有一點(diǎn)意義,難不成你告訴我這能提高性能? 如果有功夫?yàn)檫@點(diǎn)性能擔(dān)驚受怕,還不如花點(diǎn)時(shí)間去找找性能瓶頸。解決方法:使用equals方法21. IM_AVERAGE_COMPUTATION_COULD_OVERFLOWBug: Computation of average could overflow Pattern id: IM_AVERAGE_COMPUTATION_COULD_OVERFLOW, type: IM, category: STYLE解釋:參照了Findbugs的解釋,(low+high)/2當(dāng)平均數(shù)過大的時(shí)候(難道是超過了int最大值?)會(huì)溢出,會(huì)出現(xiàn)一個(gè)負(fù)值,此問題出現(xiàn)在早期實(shí)現(xiàn)的二進(jìn)制搜索和歸并排序,但是已經(jīng)被修復(fù)了。參見Joshua Bloch(google首席java架構(gòu)師)widely publicized the bug pattern(需FQ).解決方法:建議使用無符號右移位運(yùn)算符:use (low+high) >>> 122. SC_START_IN_CTORBug: new AsyncCentral() invokes AsyncCentral$FireThread.start() Pattern id: SC_START_IN_CTOR, type: SC, category: MT_CORRECTNESS解釋:構(gòu)造方法里重啟新的線程,我還是第一次見過這樣寫的。首先說明三點(diǎn):1. 對象的創(chuàng)建一般分兩步走,在堆上new對象操作,執(zhí)行<init>方法(包含構(gòu)造方法),為什么我們開發(fā)人員看見的只有一步,那是因?yàn)镴VM不想讓開發(fā)人員在這個(gè)過程中插上一腳,破壞對象的初始化流程。2. 類的加載和初始化是由虛擬機(jī)保證同步的,但是對象的生成和初始化就沒有任何同步機(jī)制來保證了。3. 構(gòu)造器不能加synchronized,是一項(xiàng)程序語言設(shè)計(jì)上的選擇(見:JLS 8.8.3 Constructor Modifiers),正常情況下,是不需要加上synchronized,但不代表所有的情況都不要加上synchronized,更不能認(rèn)為一個(gè)構(gòu)造器隱含的就是一個(gè)synchronized。那什么時(shí)候構(gòu)造方法需要同步呢?通常來說,<init>方法在生成對象的時(shí)候只被執(zhí)行一次,一般new對象的操作可能因?yàn)镴VM自身的關(guān)系保證原子性操作(自己臆測的,沒有任何根據(jù)),所以我們經(jīng)常不用關(guān)心構(gòu)造方法同步的問題。但是上述情況就不一樣了,在構(gòu)造方法中新啟線程,如果AsyncCentral是一個(gè)狀態(tài)類,F(xiàn)ireThread線程極有可能對AsyncCentral的狀態(tài)進(jìn)行反復(fù)讀取和寫入,更嚴(yán)重的一種情況是,AsyncCentral有父類,極有可能在父類的構(gòu)造方法還沒開始前,F(xiàn)ireThread線程就已經(jīng)開始執(zhí)行并對AsyncCentral的狀態(tài)進(jìn)行“破壞”了。這個(gè)時(shí)候,就有兩個(gè)線程來對AsyncCentral的狀態(tài)進(jìn)行操作了(一個(gè)是執(zhí)行<init>方法的線程,一個(gè)是FireThread線程),自然而然,就會(huì)存在同步的問題了。多數(shù)時(shí)候,我們沒有發(fā)現(xiàn),可能是AsyncCentral類沒有狀態(tài),或者是時(shí)候未到,我想說的是,我們寫的大部分程序都存在同步的問題,本例子就是其中一個(gè),值得我們好好思考。另一種理解(覺得更靠譜,來自于Java.Concurrency.in.Practice)叫做“對象逃逸”,意思就是說在構(gòu)造方法里,this是可以訪問的到的,同一時(shí)間,F(xiàn)ireThread線程而是可以訪問到this對象的,所以這時(shí)候this就從<init>方法線程逃逸到了FireThread線程中,這時(shí)候初始化就會(huì)存在并發(fā)問題。解決方法:不要再構(gòu)造方法中新啟線程,可以提供init方法,其他方法根據(jù)實(shí)際情況而定。23. EQ_SELF_USE_OBJECTBug: ManageItem defines equals(ManageItem) method and uses Object.equals(Object) Pattern id: EQ_SELF_USE_OBJECT, type: Eq, category: CORRECTNESS解釋:這是重載,不是覆蓋,除非你能保證其他人調(diào)用這個(gè)方法傳入的參數(shù)都是ManageItem 的,否則會(huì)調(diào)用Object的boolean equals(Object)方法,這樣的話根本就不會(huì)跑到這個(gè)方法里來?。『芏嗨^的大牛都會(huì)犯這么一個(gè)錯(cuò)誤,我堅(jiān)信這是你手滑了。解決方法:如果你想覆蓋父類的方法,請?jiān)谏厦婕由螥Override注解,它會(huì)防止這種錯(cuò)誤的出現(xiàn)(透露一個(gè)小細(xì)節(jié),JDK1.5覆蓋接口方法時(shí)加上@Override編譯器會(huì)報(bào)錯(cuò),JDK1.6修正,這可能是當(dāng)初實(shí)現(xiàn)者對@Override注解理解的問題)。24.DLS_DEAD_LOCAL_STORE案例二:Bug: Dead store to date Pattern id: DLS_DEAD_LOCAL_STORE, type: DLS, category: STYLE解釋:先看看,我們的程序有多少個(gè)這樣的例子:真是傷不起啊,不知道當(dāng)時(shí)的作者這是神馬意圖?手滑?還是眼花?雖然說這不是神馬問題,也不會(huì)對程序性能造成多大的影響,但是這就像一顆沙子,我們每個(gè)程序員對待程序都應(yīng)該是眼里不能進(jìn)沙子的態(tài)度,當(dāng)然,你非要這么寫,我也沒神馬可說的。By the way:對本地變量定義了之后未使用到,編譯器能夠做優(yōu)化處理,也就是在編譯之后的class文件中刪除這些本地變量。方法是在eclipse的Preferences里將以下的鉤去除:解決方法:大膽的去掉或者注釋掉。誤報(bào)的案例:上述案例二種: IntegralItemDO integralItem = new IntegralItemDO();是一個(gè)局部的變量,不需要定義到外部去,定義在外部,可能會(huì)變成一個(gè)無效的變量。25.FE_TEST_IF_EQUAL_TO_NOT_A_NUMBERBug: Doomed test for equality to NaN Pattern id: FE_TEST_IF_EQUAL_TO_NOT_A_NUMBER, type: FE, category: CORRECTNESS解釋:我也開眼界了,照搬Findbugs的理解:大概意思就是說Nan很特殊(表示未定義和不可表示的值),沒有任何值跟它相等,包括它自身,所以x == Double.NaN永遠(yuǎn)返回false。解決方法:如果要檢查x是特殊的,不是一個(gè)數(shù)值,請用Double.isNaN(x)方法。26. FI_EMPTYBug: FilterIPConfigDialog.finalize() is empty and should be deleted Pattern id: FI_EMPTY, type: FI, category: BAD_PRACTICE解釋:空的finalize方法,有什么用?根據(jù)JDK文檔, finalize() 是一個(gè)用于釋放非 Java 資源的方法。但是, JVM 有很大的可能不調(diào)用對象的finalize() 方法,因此很難證明使用該方法釋放資源是有效的。解決方法:刪除掉finalize方法27.REC_CATCH_EXCEPTIONBug: Exception is caught when Exception is not thrown Pattern id: REC_CATCH_EXCEPTION, type: REC, category: STYLE解釋:我覺得有點(diǎn)迷惑,有些catch (Exception e)并沒有被Findbugs捕捉到,開始以為它的意思是try catch里沒有任何異常的產(chǎn)生,包括RuntimeException,但是后來我寫了例子證明并不是這么回事??傊囊馑紤?yīng)該是說JVM對RuntimeException有統(tǒng)一的捕獲機(jī)制(一般都是打印異常棧信息,然后向外拋,沒有遇到Exception線程就死掉,EDT線程除外),你搞一個(gè)catch (Exception e)這樣也把RuntimeException就捕獲了。但是如果你的處理機(jī)制中沒有針對這些異常,那就可能有問題了。通常來說,很多應(yīng)用程序都把異常記錄在日志之中,但是我覺得也應(yīng)該同時(shí)打印在調(diào)試屏幕中,這樣有利于開發(fā)人員調(diào)試。比如上面的程序,假如發(fā)生了空指針異常,你只有去日志中才能看到,這對我們調(diào)試人員來說很不方便的。解決方法:其實(shí)這樣寫也沒有問題(除非你有意),有時(shí)候我們確實(shí)需要捕獲RuntimeException,比如我們有一個(gè)批處理,這個(gè)任務(wù)很重要,必須保證某個(gè)任務(wù)出了問題不能影響其他的任務(wù),這個(gè)時(shí)候就可以在for循環(huán)內(nèi)捕獲RuntimeException,出現(xiàn)了異常還可以continue。不過上面的例子最好再把異常信息打印到調(diào)試屏幕上。28. DM_GCBug: DBExportTask2.exportDBRecords(DBExportProperty, String) forces garbage collection; extremely dubious except in benchmarking code Pattern id: DM_GC, type: Dm, category: PERFORMANCE解釋:有兩點(diǎn):1. System.gc()只是建議,不是命令,JVM不能保證立刻執(zhí)行垃圾回收。2. System.gc()被顯示調(diào)用時(shí),很大可能會(huì)觸發(fā)Full GC。GC有兩種類型:Scavenge GC和Full GC,Scavenge GC一般是針對年輕代區(qū)(Eden區(qū))進(jìn)行GC,不會(huì)影響老年代和永生代(PerGen),由于大部分對象都是從Eden區(qū)開始的,所以Scavenge GC會(huì)頻繁進(jìn)行,GC算法速度也更快,效率更高。但是Full GC不同,F(xiàn)ull GC是對整個(gè)堆進(jìn)行整理,包括Young、Tenured和Perm,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。解決方法:去掉System.gc()28. DP_DO_INSIDE_DO_PRIVILEGEDBug: com.taobao.sellerservice.core.test.BaseTestJunit.autoSetBean() invokes reflect.Field.setAccessible(boolean), which should be invoked from within a doPrivileged block Pattern id: DP_DO_INSIDE_DO_PRIVILEGED, type: DP, category: BAD_PRACTICEThis code invokes a method that requires a security permission check. If this code will be granted security permissions, but might be invoked by code that does not have security permissions, then the invocation needs to occur inside a doPrivileged block.此代碼調(diào)用一個(gè)方法,需要一個(gè)安全權(quán)限檢查。如果此代碼將被授予安全權(quán)限,但可能是由代碼不具有安全權(quán)限調(diào)用,則需要調(diào)用發(fā)生在一個(gè)doPrivileged的塊。30. MS_SHOULD_BE_FINALBug: IPv4Document.m_strInitString isn't final but should be Pattern id: MS_SHOULD_BE_FINAL, type: MS, category: MALICIOUS_CODE解釋:使用public和protected,別的包可以輕易修改它,如果你不想它被修改,請使用final。封裝很重要,不管是從維護(hù)方面和技術(shù)方面來說,都很重要,我不明白為神馬有那么多的人把變量都寫成public的(就算要給別人共享,也要提供get方法),特別是在并發(fā)環(huán)境中,特別特別注意類變量的共享,而且特別特別特別注意共享的這個(gè)變量是否是線程安全的。解決方法:加上final31. NM_FIELD_NAMING_CONVENTIONBug: The field name TopoControlPaneII.SyncSelection doesn't start with a lower case letter Pattern id: NM_FIELD_NAMING_CONVENTION, type: Nm, category: BAD_PRACTICE解釋:為神馬字段是大寫開頭的?喂神馬?喂神馬???解決方法:建議按照sun規(guī)定的命名方式Bug: Field only ever set to null: RaisecomStatus.infoURL Pattern id: UWF_NULL_FIELD, type: UwF, category: CORRECTNESS解釋:字段infoURL整個(gè)過程中一直為null,但卻被用來作為分支判斷的條件,不知道作者何意?難道真的是傳說中的手滑?解決方法:這個(gè)就要問作者的意圖了,當(dāng)時(shí)你究竟要干神馬來著?32. MS_PKGPROTECTBug: ActionPatternManager.m_This should be package protected Pattern id: MS_PKGPROTECT, type: MS, category: MALICIOUS_CODE解釋:Findbugs說,靜態(tài)字段m_oThis應(yīng)該是包權(quán)限的,如果是protected的話,可以被其他包訪問到,其實(shí)個(gè)人覺得僅僅是封裝范圍的話是一個(gè)“小問題”,畢竟很多人都沒意識到public、protected等關(guān)鍵字的重要性。但是我接著往下看:單例模式??這是神馬單例模式?字段不是private,還是單例模式嗎?我在任何一個(gè)地方繼承UserManager,然后直接m_oThis = new UserManager();這還是一個(gè)單例嗎?在看看Findbugs為我們找出了多少個(gè):另外,我很客觀的說一點(diǎn),我們后怕,因?yàn)橹懒苏嫦?,在想想我們?shí)際情況中遇到很多不能復(fù)現(xiàn)的問題,我們有理由去知道這一切。解決方法:修改protected為private,然后將單例模式實(shí)現(xiàn)方式改為惡漢,或者雙重校驗(yàn)鎖定。33. FI_USELESSPattern: Finalizer does nothing but call superclass finalizer id: FI_USELESS, type: FI, category: BAD_PRACTICE解釋:finalize() 是一個(gè)用于釋放非 Java 資源的方法,這里的finalize直接用Object的finalize方法,無任何意義。解決方法:勇敢去掉finalize()34. NP_NULL_ON_SOME_PATHBug: Possible null pointer dereference of busCatId Pattern id: NP_NULL_ON_SOME_PATH, type: NP, category: CORRECTNESSThere is a branch of statement that, if executed, guarantees that a null value will be dereferenced, which would generate a NullPointerException when the code is executed. Of course, the problem might be that the branch or statement is infeasible and that the null pointer exception can't ever be executed; deciding that is beyond the ability of FindBugs.解釋:方法中存在空指針解決方法:增加字段busCatId為空的判斷35. NP_NULL_ON_SOME_PATHBug:.HierarchicalManagerImpl.isExistByName(String, long) forgets to throw new exception.HierarchicalServiceException(String, Throwable) Pattern id: RV_EXCEPTION_NOT_THROWN, type: RV, category: CORRECTNESSThis code creates an exception (or error) object, but doesn't do anything with it. For example, something likeif (x < 0)new IllegalArgumentException("x must be nonnegative");It was probably the intent of the programmer to throw the created exception:if (x < 0)throw new IllegalArgumentException("x must be nonnegative");解釋:此代碼創(chuàng)建了一個(gè)異常(或錯(cuò)誤)的對象,但并不做任何事情??赡茏髡呤窍肜^續(xù)拋出異常信息吧,可是卻產(chǎn)生了一個(gè)對象,啥也不干。解決方法:拋出這個(gè)錯(cuò)誤36. FI_FINALIZER_NULLS_FIELDSBug: CustomerResTreeDialog.java:[line 67] is set to null inside finalize method Pattern id: FI_FINALIZER_NULLS_FIELDS, type: FI, category: BAD_PRACTICE解釋:關(guān)于finalize方法,前面應(yīng)該已經(jīng)介紹過了,所以m_UniResTree = null,純粹是多此一舉,沒有任何意義。解決方法:勇敢去掉finalize()37. FI_PUBLIC_SHOULD_BE_PROTECTEDBug: FilterIPConfigDialog.finalize() is public; should be protected Pattern id: FI_PUBLIC_SHOULD_BE_PROTECTED, type: FI, category: MALICIOUS_CODE解釋:Finalize方法不是protected的,當(dāng)然你寫成public也沒錯(cuò),依然可以覆蓋父類中的finalize方法。解決方法:勇敢去掉finalize()38. IS2_INCONSISTENT_SYNCBug: Inconsistent synchronization of URLAlarmMonitor.m_Counter; locked 50% of time Pattern id: IS2_INCONSISTENT_SYNC, type: IS, category: MT_CORRECTNESS解釋:m_Counter只鎖住了50%,它還是處于線程不安全的狀態(tài),如果一個(gè)字段只被read,那么它是線程安全的,不需要提供額外的同步開銷,可以定義為final的(參考不可變類的實(shí)現(xiàn)),如果既有read也有write,那么就必須保證每個(gè)get和set方法都同步,而不能像上面一樣,只對set方法進(jìn)行了同步。解決方法:對get和set方法都實(shí)行同步。39. LI_LAZY_INIT_UPDATE_STATICBug: Incorrect lazy initialization and update of static field MonitorRuleManager.m_This Pattern id: LI_LAZY_INIT_UPDATE_STATIC, type: LI, category: MT_CORRECTNESS解釋:此問題的m_This也是protected的,這里就不再追究了。這里的問題是,當(dāng)線程1執(zhí)行到initMonitorRules方法時(shí),線程2執(zhí)行g(shù)etInstance方法,它直接返回m_This,這時(shí)候它可以用m_This做任何事情,但是此時(shí)線程1的初始化動(dòng)作還沒有完成,如果initMonitorRules方法里有對對象狀態(tài)進(jìn)行更新的操作,那么很可能線程2得到的對象的狀態(tài)是還沒有初始化的,這就是一個(gè)多線程的BUG(多線程的問題之所以很嚴(yán)重,是因?yàn)槲覀兒茈y復(fù)現(xiàn)解決它,但它又是的確存在的,它總是在關(guān)鍵時(shí)候爆發(fā),讓你感到很郁悶)!當(dāng)然就算沒有initMonitorRules方法,這個(gè)單例模式也不是線程安全的,下面會(huì)講到這個(gè)問題。解決方法:將initMonitorRules方法放在構(gòu)造方法里,然后將單例改成惡漢模式,或者使用雙重校驗(yàn)鎖。40. LI_LAZY_INIT_STATICBug: Incorrect lazy initialization of static field TopoController.m_This Pattern id: LI_LAZY_INIT_STATIC, type: LI, category: MT_CORRECTNESS解釋:為什么它存在多線程的bug,比如線程1進(jìn)入到if語句內(nèi),被線程2打斷,線程2同樣進(jìn)入了if語句內(nèi)然后生成了一個(gè)對象a,隨即被線程1打斷,線程1又生成了另外一個(gè)對象b,這還是一個(gè)單例么?更詳細(xì)的解釋請看:雙重檢查鎖定以及單例模式另外,關(guān)于單例模式更多的資料,參見單例模式的七種寫法如果你并發(fā)功底相當(dāng)好,請看這篇文章:用happen-before規(guī)則重新審視DCL解決方法:我比較鐘情于惡漢,如果需要傳遞參數(shù),我會(huì)使用雙重校驗(yàn)鎖。41. WMI_WRONG_MAP_ITERATOR案例一:案例二:Bug: Method JTAMainFrame.initView(JFrame) makes inefficient use of keySet iterator instead of entrySet iterator Pattern id: WMI_WRONG_MAP_ITERATOR, type: WMI, category: PERFORMANCEThis method accesses the value of a Map entry, using a key that was retrieved from a keySet iterator. It is more efficient to use an iterator on the entrySet of the map, to avoid the Map.get(key) lookup.解釋:很多人都這樣遍歷Map,沒錯(cuò),但是效率很低,先一個(gè)一個(gè)的把key遍歷,然后在根據(jù)key去查找value,這不是多此一舉么,為什么不遍歷entry(桶)然后直接從entry得到value呢?它們的執(zhí)行效率大概為1.5:1(有人實(shí)際測試過)。我們看看HashMap.get方法的源代碼: 1. public V get(Object key) { 2. if (key == null) 3. return getForNullKey(); 4. int hash = hash(key.hashCode()); 5. for (Entry<K,V> e = table[indexFor(hash, table.length)]; 6. e != null; 7. e = e.next) { 8. Object k; 9. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 10. return e.value; 11. } 12. return null; 13. }從這里可以看出查找value的原理,先計(jì)算出hashcode,然后散列表里取出entry,不管是計(jì)算hashcode,還是執(zhí)行循環(huán)for以及執(zhí)行equals方法,都是CPU密集運(yùn)算,非常耗費(fèi)CPU資源,如果對一個(gè)比較大的map進(jìn)行遍歷,會(huì)出現(xiàn)CPU迅速飚高的現(xiàn)象,直接影響機(jī)器的響應(yīng)速度,在并發(fā)的情況下,簡直就是一場災(zāi)難。解決方法: 1. for (Map.Entry<String, JMenu> entry : menuList.entrySet()) { 2. mb.add(entry.getValue());}for(Map.Entry<String, List<BlackListDO>> tempEntiy: companyBlackItemsMap.entrySet()) {String key = tempEntiy.getKey();List<BlackListDO> eachCompanyBlackItems = tempEntiy.getValue();42. BC_VACUOUS_INSTANCEOFBug: instanceof will always return true, since all TopoTreeNode are instances of TopoTreeNode Pattern id: BC_VACUOUS_INSTANCEOF, type: BC, category: STYLE解釋:因?yàn)間etSelectedTreeNode方法返回類型就是TopoTreeNode,所以if里的instanceof測試永遠(yuǎn)為true,除非它是null,確保你沒有其他的邏輯上的誤解,你這樣寫,會(huì)讓其他人丈二和尚摸不著頭腦。解決方法:去掉instanceof檢測。43. INT_BAD_REM_BY_1Bug: Integer remainder modulo 1 computed Pattern id: INT_BAD_REM_BY_1, type: INT, category: STYLE解釋:I % 1永遠(yuǎn)都為0,I / 1也為i,不知道作者想干嘛。解決方法:恕我愚昧,不明白作者的意圖。Bug: Load of known null value Pattern id: NP_LOAD_OF_KNOWN_NULL_VALUE, type: NP, category: STYLEThe variable referenced at this point is known to be null due to an earlier check against null. Although this is valid, it might be a mistake (perhaps you intended to refer to a different variable, or perhaps the earlier check to see if the variable is null should have been a check to see if it was nonnull).解釋:Node為null,還進(jìn)一步調(diào)用它上面的方法,除非你能保證當(dāng)node為null的時(shí)候isDeleteSingleObject為false,否則很可能發(fā)生空指針異常,我估計(jì)作者是第二個(gè)if是想判斷node != null吧。解決方法:努力找到原作者,當(dāng)面詢問其用意。44. EI_EXPOSE_REP2案例DO類Bug: SingleNePollConfigDialog.collectValues(Hashtable) may expose internal representation by storing an externally mutable object into SingleNePollConfigDialog.values Pattern id: EI_EXPOSE_REP2, type: EI2, category: MALICIOUS_CODE解釋:參數(shù)values保存在當(dāng)前線程的執(zhí)行棧中,而this.values保存在堆上,它們同時(shí)指向同一個(gè)對象,對參數(shù)values的任何操作都會(huì)影響到this.values,如果你知道這一點(diǎn),而且本意就是這樣的,那么你可以忽略上面這些話,但是下面這些話你應(yīng)該好好聽聽。這是一段正確的代碼,但不是一段可維護(hù)性強(qiáng)、可理解性強(qiáng)的代碼,參數(shù)代表操作的條件,它們應(yīng)該是只讀的,我們不應(yīng)該對它直接進(jìn)行操作或者賦值。解決方法:如果把上面對參數(shù)values的操作都改成this.values,我相信你和你的同事都會(huì)覺得這樣的代碼更加清晰。}案例二DO類Bug: SingleNePollConfigDialog.collectValues(Hashtable) may expose internal representation by storing an externally mutable object into SingleNePollConfigDialog.values Pattern id: EI_EXPOSE_REP2, type: EI2, category: MALICIOUS_CODEThis code stores a reference to an externally mutable object into the internal representation of the object. If instances are accessed by untrusted code, and unchecked changes to the mutable object would compromise security or other important properties, you will need to do something different. Storing a copy of the object is better approach in many situations.翻譯愿意:此代碼存儲(chǔ)到一個(gè)到對象的內(nèi)部表示外部可變對象的引用。如果實(shí)例是由不受信任的代碼,并以可變對象會(huì)危及安全或其他重要的屬性選中更改訪問,你需要做不同的東西。存儲(chǔ)一個(gè)對象的副本,在許多情況下是更好的辦法。解釋:DO類實(shí)例產(chǎn)生之后,里面包含的Date不是原始數(shù)據(jù)類型,導(dǎo)致其gmtCrate屬性不光是set方法可以改變其值,外部引用修改之后也可能導(dǎo)致gmtCreate 被改變,會(huì)引起可能的不安全或者錯(cuò)誤。這個(gè)是一個(gè)不好的實(shí)踐,不過我們應(yīng)用里面DO都是比較簡單使用,不太會(huì)出現(xiàn)這種情況。解決方法:修改成:public Date getGmtCreate() { return new Date(this.gmtCreate.getTime()); //正確值}45. EI_EXPOSE_REPBug: temsLoader.getItemsWithPriority() may expose internal representation by returning ItemsLoader.m_htItemsWithPriority Pattern id: EI_EXPOSE_REP, type: EI, category: MALICIOUS_CODE解釋:剛開始一看挺納悶的,這個(gè)方法有什么問題嗎?后來仔細(xì)看一下,發(fā)現(xiàn)返回值都有一個(gè)特點(diǎn),它們都是集合數(shù)組之類的,我想findBugs的本意是,某些數(shù)據(jù)集合不應(yīng)該直接對外提供public返回方法,即使表面上提供了get方法,但實(shí)際上可以任意修改里面的數(shù)據(jù)。解決方法:如果你確定這些數(shù)據(jù)集合不應(yīng)該被外界修改,那么對于基本數(shù)據(jù)類型,你提供get方法即可,對于引用,get方法里的返回值應(yīng)該是數(shù)據(jù)的拷貝。46. NP_NULL_PARAM_DEREFBug: Method call passes null for nonnull parameter of queryScriptData(ObjService) Pattern id: NP_NULL_PARAM_DEREF, type: NP, category: CORRECTNESS解釋:當(dāng)getAllListFiles方法發(fā)生了任何異常(checked和unchecked),allFiles都為null,關(guān)鍵是在queryScriptData方法里,并沒有對參數(shù)是否為null進(jìn)行判斷,它直接調(diào)用了參數(shù)對象上面的方法,這肯定會(huì)發(fā)生空指針異常。一個(gè)優(yōu)秀的程序員,在過馬路時(shí)都要向兩邊看一下,在寫一個(gè)方法時(shí),首先要考慮的就是對方法參數(shù)的有效性判斷。解決方法:在queryScriptData方法里對參數(shù)進(jìn)行有效性判斷。46. SBSC_USE_STRINGBUFFER_CONCATENATIONBug: Method InitDBPoolParaTask.execute() concatenates strings using + in a loop Pattern id: SBSC_USE_STRINGBUFFER_CONCATENATION, type: SBSC, category: PERFORMANCEThe method seems to be building a String using concatenation in a loop. In each iteration, the String is converted to a StringBuffer/StringBuilder, appended to, and converted back to a String. This can lead to a cost quadratic in the number of iterations, as the growing string is recopied in each iteration.Better performance can be obtained by using a StringBuffer (or StringBuilder in Java 1.5) explicitly.解釋:每次循環(huán)里的字符串+連接,都會(huì)新產(chǎn)生一個(gè)string對象,在java中,新建一個(gè)對象的代價(jià)是很昂貴的,特別是在循環(huán)語句中,效率較低。解決方法:利用StringBuffer或者StringBuilder重用對象。47. RV_RETURN_VALUE_IGNORED_BAD_PRACTICEBug: NewScriptAction.actionPerformed(ActionEvent) ignores exceptional return value of java.io.File.delete() Pattern id: RV_RETURN_VALUE_IGNORED_BAD_PRACTICE, type: RV, category: BAD_PRACTICE解釋:關(guān)于一個(gè)方法邏輯執(zhí)行是否成功,有兩種方式,一種是拋出異常,一種是提供boolean類型的返回值。舉一個(gè)例子,用戶登錄,某些人將login方法的返回值定義為int,然后枚舉出各個(gè)值的含義,比如0代表成功,1代表用戶名不存在等等;而有些人,把這些枚舉值看成是use case中的異常流,將它們定義為異常對象,遇到“異?!鼻闆r直接拋出異常從而實(shí)現(xiàn)分支的流程。第一種方式是典型的C語言面向過程風(fēng)格,第二種方式,帶有強(qiáng)烈的面向?qū)ο笪兜?,特別是java提供了checked Exception,貌似偏離主題了。java中很多方法的執(zhí)行成功依賴于異常的分支實(shí)現(xiàn),但也有提供返回值的實(shí)現(xiàn),比如這里的File.delete方法,上面的寫法忽略了返回值(如果調(diào)用某個(gè)方法卻不使用其返回值要特別注意),刪除一個(gè)文件很可能不成功,但是從代碼里并沒有看到這一層面的意思。解決方法:文件刪除不成功該怎么辦?現(xiàn)在能處理就處理,現(xiàn)在不能處理就把父類的方法也改成有返回值的,然后向上傳遞,這跟處理異常的道理是一樣的,當(dāng)然,你也可以把它封裝成一個(gè)異常對象。48. RV_RETURN_VALUE_IGNOREDBug: BackupFileListPanel$PopupListener.maybeShowPopup(MouseEvent) ignores return value of String.trim() Pattern id: RV_RETURN_VALUE_IGNORED, type: RV, category: CORRECTNESS解釋:String是一個(gè)不可變類,調(diào)用String上的任何操作都會(huì)返回新的String對象,雖然String是一個(gè)class,但實(shí)際上對它的任何操作都可以把它看成基本數(shù)據(jù)類型,比如s.trim方法是不會(huì)改變s值的。解決方法:S = s.trim49. DM_BOOLEAN_CTORBug: TopoCardManagerAction.processLocalCard(Hashtable) invokes inefficient Boolean constructor; use Boolean.valueOf(...) instead Pattern id: DM_BOOLEAN_CTOR, type: Dm, category: PERFORMANCE解釋:不必創(chuàng)建一個(gè)新的Boolean對象,使用Boolean.valueOf方法可以重用Boolean.FALSE和Boolean.TRUE對象。我們可以從API中可以看到public Boolean(boolean value)方法的解釋:注:一般情況下都不宜使用該構(gòu)造方法。若不需要新 的實(shí)例,則靜態(tài)工廠 valueOf(boolean) 通常是一個(gè)更好的選擇。這有可能顯著提高空間和時(shí)間性能。解決方法:使用Boolean.valueOf方法或者直接返回Boolean.FALSE和Boolean.TRUE對象。50. RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUEPattern id: RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE,解釋:StringBuffer連接更有効率,因此,建議使用StringBuffer51. DM_NUMBER_CTORnew Integer(int) 和 Integer.valueOf(int)bug描述:[Bx] Method invokes inefficient Number constructor; use static valueOf instead [DM_NUMBER_CTOR]Using new Integer(int) is guaranteed to always result in a new object whereas Integer.valueOf(int) allows caching of values to be done by the compiler, class library, or JVM. Using of cached values avoids object allocation and the code will be faster.說明:[參考]http://www.cnblogs.com/hyddd/articles/1391318.htmlFindBugs推薦使用Integer.ValueOf(int)代替new Integer(int),因?yàn)檫@樣可以提高性能。如果當(dāng)你的int值介于-128~127時(shí),Integer.ValueOf(int)的效率比Integer(int)快大約3.5倍。下面看看JDK的源碼,看看到Integer.ValueOf(int)里面做了什么優(yōu)化:public static Integer valueOf(int i) { final int offset = 128; if (i >= -128 && i <= 127) { // must cache return IntegerCache.cache[i + offset]; } return new Integer(i); } private static class IntegerCache { private IntegerCache(){} static final Integer cache[] = new Integer[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache = new Integer(i - 128); } }從源代碼可以知道,ValueOf對-128~127這256個(gè)值做了緩存(IntegerCache),如果int值的范圍是:-128~127,在ValueOf(int)時(shí),他會(huì)直接返回IntegerCache的緩存給你。所以你會(huì)看到這樣的一個(gè)現(xiàn)象:public static void main(String []args) { Integer a = 100; Integer b = 100; System.out.println(a==b); Integer c = new Integer(100); Integer d = new Integer(100); System.out.println(c==d); }結(jié)果是:true false因?yàn)椋簀ava在編譯的時(shí)候 Integer a = 100; 被翻譯成-> Integer a = Integer.valueOf(100);,所以a和b得到都是一個(gè)Cache對象,并且是同一個(gè)!而c和d是新創(chuàng)建的兩個(gè)不同的對象,所以c自然不等于d。再看看這段代碼:public static void main(String args[]) throws Exception{ Integer a = 100; Integer b = a; a = a + 1; //或者a++; System.out.println(a==b); }結(jié)果是:false因?yàn)樵趯操作時(shí)(a=a+1或者a++),a重新創(chuàng)建了一個(gè)對象,而b對應(yīng)的還是緩存里的100,所以輸出的結(jié)果為false。