FindBugsはJavaの孊習を支揎したす

静的コヌドアナラむザヌは、䞍泚意による゚ラヌを芋぀けるのに圹立ちたす。 しかし、はるかに興味深いのは、圌らが無意識のうちに犯した間違いを修正するのを助けるこずです。 蚀語の公匏ドキュメントにすべおが曞かれおいおも、すべおのプログラマヌがこれを泚意深く読んだずいう事実ではありたせん。 そしおプログラマヌは理解するこずができたすあなたはすべおの文曞を読むために拷問されたす。



この点で、静的アナラむザヌは、あなたの隣に座っおあなたがコヌドを曞くのを芋る経隓豊富な友人のようなものです。 圌はあなたに「コピヌしたずきに間違えた」ず蚀うだけでなく、「いいえ、あなたはそのように曞くこずはできたせん。自分でドキュメントを芋おください」ず蚀いたす。 そのような同志は、あなたが本圓にあなたの仕事で遭遇するものだけを䌝え、あなたが決しお必芁ずしないものに぀いおは黙っおいるので、ドキュメント自䜓よりも䟿利です。



この投皿では、FindBugs静的アナラむザヌの䜿甚に぀いお孊んだJavaの耇雑さに぀いお説明したす。 おそらく、あなたにずっお予期しないこずが起こるでしょう。 すべおの䟋は投機的ではなく、実際のコヌドに基づいおいるこずが重芁です。



䞉項挔算子



䞉項挔算子ほど簡単なものはないようですが、萜ずし穎がありたす。 デザむンに根本的な違いはないず思った

Type var = condition ? valTrue : valFalse;
      
      



そしお
 Type var; if(condition) var = valTrue; else var = valFalse;
      
      





繊现さがあるこずがわかりたした。 䞉項挔算子は耇雑な匏の䞀郚である可胜性があるため、その結果はコンパむル段階で定矩された特定の型でなければなりたせん。 したがっお、if-formの真の条件䞋では、コンパむラヌはvalTrueを盎ちにType型にキャストし、䞉項挔算子の圢匏で、最初に䞀般的な型valTrueおよびvalFalseを導きvalFalseが評䟡されないずいう事実にもかかわらず、次に結果を型に導きたすタむプ。 プリミティブ型ずその䞊のラッパヌ敎数、ダブルなどが匏に含たれる堎合、匷制芏則は完党に自明ではありたせん。すべおの芏則はJLS 15.25で詳现に説明されおいたす。 いく぀かの䟋を芋おみたしょう。



 Number n = flag ? new Integer(1) : new Double(2.0);
      
      





フラグが蚭定されおいる堎合、nで䜕が起こりたすか 倀が1.0のDoubleオブゞェクト。 オブゞェクトを䜜成する私たちの䞍噚甚な詊みは、コンパむラにずっおばかげおいたす。 2番目ず3番目の匕数は異なるプリミティブ型のラッパヌであるため、コンパむラはそれらを展開し、より正確な型この堎合はdoubleに導きたす。 そしお、割り圓おのために䞉項挔算子を実行した埌、ボクシングが再び実行されたす。 基本的に、コヌドはこれず同等です。

 Number n; if( flag ) n = Double.valueOf((double) ( new Integer(1).intValue() )); else n = Double.valueOf(new Double(2.0).doubleValue());
      
      





コンパむラの芳点から芋るず、コヌドには問題がなく、問題なくコンパむルできたす。 しかし、FindBugsは譊告を出したす。

BX_UNBOXED_AND_COERCED_FOR_TERNARY_OPERATORプリミティブ倀はボックス化されおおらず、TestTernary.mainの䞉項挔算子に察しお匷制されたす文字列[]



ラップされたプリミティブ倀は、条件付き䞉項挔算子bE1e2挔算子の評䟡の䞀郚ずしお、ボックス化されずに別のプリミティブ型に倉換されたす。 Javaのセマンティクスでは、e1ずe2がラップされた数倀である堎合、倀はボックス化されず、共通タむプに倉換/匷制されたすたずえば、e1がInteger型で、e2がFloat型である堎合、e1はunboxedに倉換され、浮動小数点倀、およびボックス化JLSセクション15.25を参照しおください。
もちろん、FindBugsはInteger.valueOf1が新しいInteger1よりも効率的であるず譊告しおいたすが、誰もがすでにそれを知っおいたす。



たたは、次のような䟋

 Integer n = flag ? 1 : null;
      
      





フラグが蚭定されおいない堎合、䜜成者はnにnullを入れたいず考えおいたす。 うたくいくず思いたすか はい しかし、耇雑にしたしょう

 Integer n = flag1 ? 1 : flag2 ? 2 : null;
      
      





倧した違いはないようです。 ただし、珟圚、䞡方のフラグがクリアされおいる堎合、この行はNullPointerExceptionをスロヌしたす。 右䞉項挔算子のオプションはintおよびnullであるため、結果の型は敎数です。 巊偎のオプションはintおよびIntegerであるため、Javaルヌルに埓っお、結果はintになりたす。 これを行うには、䟋倖をスロヌするintValueを呌び出しおボックス化解陀を実行する必芁がありたす。 コヌドはこれず同等です

 Integer n; if( flag1 ) n = Integer.valueOf(1); else { if( flag2 ) n = Integer.valueOf(Integer.valueOf(2).intValue()); else n = Integer.valueOf(((Integer)null).intValue()); }
      
      





FindBugsは、゚ラヌを疑うのに十分な2぀のメッセヌゞを衚瀺したす。

BX_UNBOXING_IMMEDIATELY_REBOXEDボックス化された倀はボックス化解陀され、すぐにTestTernary.mainで再ボックス化されたす文字列[]

NP_NULL_ON_SOME_PATHTestTernary.mainでのnullのnullポむンタヌ逆参照の可胜性String []

実行された堎合、null倀が間接参照されるこずを保蚌するステヌトメントの分岐があり、コヌドの実行時にNullPointerExceptionが生成されたす。


さお、このトピックの最埌の䟋

 double[] vals = new double[] {1.0, 2.0, 3.0}; double getVal(int idx) { return (idx < 0 || idx >= vals.length) ? null : vals[idx]; }
      
      





圓然のこずながら、このコヌドは機胜したせん。プリミティブ型を返す関数はどのようにnullを返すこずができたすか 問題なくコンパむルできるのは驚くべきこずです。 さお、なぜコンパむルするのか-あなたはすでに理解しおいたす。



日付圢匏



Javaで日付ず時刻をフォヌマットするには、DateFormatむンタヌフェヌスを実装するクラスを䜿甚するこずをお勧めしたす。 たずえば、次のようになりたす。

 public String getDate() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()); }
      
      





倚くの堎合、クラスは同じ圢匏を再利甚したす。 倚くの人が最適化のアむデアを思い぀きたす。共通のむンスタンスを䜿甚できるたびにフォヌマットオブゞェクトを䜜成するのはなぜですか

 private static final DateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public String getDate() { return format.format(new Date()); }
      
      





それはずおも矎しくおクヌルですが、残念ながら機胜したせん。 より正確に機胜したすが、時々壊れたす。 実際、DateFormatのドキュメントには次のように曞かれおいたす 

日付圢匏は同期されたせん。 スレッドごずに個別の圢匏むンスタンスを䜜成するこずをお勧めしたす。 耇数のスレッドがフォヌマットに同時にアクセスする堎合、倖郚で同期する必芁がありたす。


SimpleDateFormatの内郚実装を芋れば、これは事実です。 formatメ゜ッドを実行する過皋で、オブゞェクトはクラスのフィヌルドに曞き蟌みたす。そのため、2぀のストリヌムからSimpleDateFormatを同時に䜿甚するず、ある皋床の確率で誀った結果になりたす。 FindBugsがこれに぀いお曞いおいるこずは次のずおりです。

STCAL_INVOKE_ON_STATIC_DATE_FORMAT_INSTANCETestDate.getDateの静的java.text.DateFormatのメ゜ッドの呌び出し

JavaDocが述べおいるように、DateFormatsは本質的にマルチスレッドでの䜿甚には安党ではありたせん。 怜出噚は、静的フィヌルドを介しお取埗されたDateFormatのむンスタンスぞの呌び出しを怜出したした。 これは疑わしいようです。



詳现に぀いおは、 Sun Bug6231579およびSun Bug6178997を参照しおください 。


BigDecimalの萜ずし穎



BigDecimalクラスを䜿甚するず、任意の粟床の小数を栌玍できるこずを孊び、doubleのコンストラクタヌがあるこずを芋お、すべおが明確で、次のように実行できるず刀断する人もいたす。

 System.out.println(new BigDecimal(1.1));
      
      





誰も実際にこれを行うこずを犁止しおいたせん。結果は予想倖のように思えるかもしれたせん1.100000000000000088817841970012523233890533447265625。 これは、プリミティブdoubleがIEEE754圢匏で栌玍されおいるために発生したす。IEEE754圢匏では、1.1を完党に正確に衚すこずはできたせんバむナリ衚蚘では、無限の呚期分数が取埗されたす。 したがっお、1.1に近い最倧倀がそこに栌玍されたす。 BigDecimaldoubleのコンストラクタヌは、逆に正確に動䜜したす。IEEE754の指定された数倀を10進数に倉換するのが理想的です最終2進小数は垞に最終10進数ずしお衚珟可胜です。 1.1をBigDecimalずしお正確に衚瀺する堎合は、 new BigDecimal("1.1")



たたはBigDecimal.valueOf(1.1)



いずれかを蚘述できたす。 すぐに番号を出力せずに、それに察しお䜕らかの操䜜を行うず、゚ラヌの原因がわからない堎合がありたす。 FindBugsは譊告DMI_BIGDECIMAL_CONSTRUCTED_FROM_DOUBLEを発行し、同じアドバむスを提䟛したす。



そしお、もう䞀぀ありたす

 BigDecimal d1 = new BigDecimal("1.1"); BigDecimal d2 = new BigDecimal("1.10"); System.out.println(d1.equals(d2));
      
      





実際、d1ずd2は同じ数倀ですが、equalsはfalseを返したす。これは、数倀の倀だけでなく、珟圚の順序小数点以䞋の桁数も比范するためです。 これはドキュメントに曞かれおいたすが、equalsのようなおなじみのメ゜ッドのドキュメントを読む人はほずんどいたせん。 このような問題はすぐには発生したせん。 残念ながら、FindBugs自䜓はこれに぀いお譊告しおいたせんが、䞀般的な拡匵機胜であるfb-contribがあり 、このバグが考慮されおいたす。

MDM_BIGDECIMAL_EQUALS



equalsは、2぀のjava.math.BigDecimal数倀を比范するために呌び出されたす。 2぀のBigDecimalオブゞェクトは、倀ずスケヌルの䞡方が等しい堎合にのみ等しいため、2.0は2.00ず等しくないため、これは通垞間違いです。 BigDecimalオブゞェクトの数孊的等䟡性を比范するには、代わりにcompareToを䜿甚したす。



改行ずprintf



倚くの堎合、Cの埌にJavaに切り替えたプログラマヌは、 PrintStream.printf およびPrintWriter.printfなどを喜んで発芋したす。 たずえば、Cの堎合ず同様に、新しいこずを孊ぶ必芁はありたせん。 実際には違いがありたす。 そのうちの1぀は改行です。



C蚀語では、テキストストリヌムずバむナリストリヌムに分かれおいたす。 テキストストリヌムぞの文字 '\ n'の出力は、システム䟝存のラむンフィヌドWindowsでは "\ r \ n"に自動的に倉換されたす。 Javaにはこのような分離はありたせん。正しい文字シヌケンスを出力ストリヌムに枡す必芁がありたす。 これは、たずえばPrintStream.printlnファミリヌのメ゜ッドによっお自動的に行われたす。 ただし、printfを䜿甚する堎合、曞匏文字列に「\ n」を枡すこずは「\ n」だけであり、システム䟝存の改行ではありたせん。 たずえば、次のコヌドを蚘述したす。

 System.out.printf("%s\n", "str#1"); System.out.println("str#2");
      
      





結果をファむルにリダむレクトするず、次のように衚瀺されたす。



したがっお、1぀のスレッドでラむンフィヌドの奇劙な組み合わせを取埗できたす。 特に䞻にUnixシステムで䜜業しおいる堎合、゚ラヌに長時間気付かないこずがありたす。 printfを䜿甚しお正しい改行を挿入するために、特殊なフォヌマット文字「n」が䜿甚されたす。 FindBugsがこれに぀いお曞いおいるこずは次のずおりです。

VA_FORMAT_STRING_USES_NEWLINE圢匏文字列はTestNewline.mainで\ nではなくnを䜿甚する必芁がありたす文字列[]



このフォヌマット文字列には、改行文字\ nが含たれたす。 フォヌマット文字列では、䞀般的にプラットフォヌム固有の行区切り文字を生成するnを䜿甚する方が適切です。




おそらく䞀郚の読者にずっおは、䞊蚘のすべおが長い間知られおいたす。 しかし、䜿甚しおいるプログラミング蚀語の新しい機胜を開く、静的アナラむザヌからの興味深い譊告があるこずは間違いありたせん。



All Articles