Javaの罠

4558 ワード

Javaでオブジェクトを作成する一般的な方法は、次の5つです.
 
        1.new呼び出しコンストラクタによってJavaオブジェクトを作成する.
        2.ClassオブジェクトのnewInstance()メソッドでコンストラクタを呼び出してオブジェクトを作成します.
        3.Javaの逆シーケンス化メカニズムによってIOストリームからオブジェクトを復元する.
        4.Javaオブジェクトが提供するcloneメソッドによってオブジェクトをコピーする.
        5.基本タイプおよびStringタイプは、直接量を与えることができる.
 
        Javaの文字列直接量の場合、JVMは文字列プールを使用して保存します.最初に直接量を使用すると、JVMは文字列プールにキャッシュされます.一般的に、文字列プールの文字列はJava回収器で回収されません.プログラムが直接量を再使用する場合、新しい文字列を再作成する必要はありません.参照変数を文字列プールにすでに存在する文字列に直接指定します.
        文字列プールの文字列は回収されません.これはJavaメモリの漏洩の原因の一つです.
        プログラムに文字のシーケンスが変化する文字列が必要な場合は、StringBuilderまたはStringBufferを使用することを考慮する必要があります.もちろん、StringBuilderを使用することが望ましいです.スレッドが安全なStringBufferでは、StringBuilderはスレッドが安全ではありません.つまり、StringBufferのほとんどのコードにsynchronized修飾子が追加されているからです.
        コードが存在するプログラムまたはプロセスが複数のスレッドで同時に実行され、これらのスレッドがこのコードを同時に実行する場合、実行結果が単一のスレッドの実行結果と同じであり、他の変数の値も予想通りであれば、スレッドが安全であるか、または複数のスレッドの切り替えがインタフェースの実行結果に二義性をもたらすことはありません.インタフェースはスレッドが安全だと言います.
 
        Javaは強いタイプの言語で、いわゆる強いタイプの言語で、通常2つの基本的な特徴を持っています:
        1.すべての変更は先に宣言してから使用しなければならない.変数を宣言するときは、その変数のデータ型を指定しなければならない.
        2.ある変数の量のデータ型が決定されると、その変数は永遠にそのタイプのデータしか受け入れられず、他のタイプのデータは受け入れられない.
 
        1つの算術式に複数の基本タイプが含まれている場合、算術式全体のデータ型が自動的に向上します.これはすでに知られている規定です.その他に特例があります.次のコードを参照してください.
short sv = 5;
sv = sv - 2;

        私たちが一般的に理解できない問題は、このコードが間違っていることです.しかし、データ型の自動アップグレードと組み合わせて、sv−2、2はint型であるため、sv−2の結果はint型であるため、svに値を割り当てることはできないと理解できる.
        次のコードを参照してください.
short sv = 5;
sv -= 2;

        自動タイプアップで理解すると、このコードの結果も間違っていると解釈されますが、残念ながらJavaには、複合付与演算子には暗黙的なタイプ変換が含まれており、sv-=2はsv=(short)(sv-2)に等価です.
        巨大なintをshortに変換すると何か問題が発生するのは明らかですか?次のコードを参照してください.
short sv = 5;
sv += 9000;

        shortタイプの数値範囲は−32768~32767の間であることが知られているので,9005をsvに付与すると高位遮断が出現し,svの最終結果は24471であった.
        このように、複合賦値演算子は簡単で便利であり、性能上の優位性があるが、複合賦値演算子には、潜在的な暗黙的なタイプ変換が、知らず知らずのうちに計算結果の上位遮断を招く可能性があるという危険がある.
       
        次のコードを参照してください.
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
List intList = list;
for (int i = 0; i < list.size(); i++) {
    System.out.println(intList.get(i));
}

        私がこのコードを見たとき、私は当然このコードが間違っていると思っていました.リストにはStringが含まれているので、リストに値を付けることはできません.Eclipseでこのコードを実行すると、このコードには間違いがなく、コンパイルしたり、実行したりすることができます.これが汎用的な罠です.汎用型を使用する場合は、次の点に注意してください.
        1.プログラムが元のタイプの変数を汎型情報を持つ変数に付与する場合、常にコンパイルすることができ、上述のコードのように、List intList=listはエラーを報告しないという警告を出すだけである.
        2.プログラムが汎用宣言付きの集合の集合要素にアクセスしようとすると、コンパイラは常に集合要素を汎用タイプとして処理する.それは集合内の集合要素の実際のタイプに関心を持たない.例えば、上述のコードではintList.get(i)の結果はIntegerタイプである.
        3.プログラムが汎用宣言付き集合の集合要素にアクセスしようとすると、JVMは各集合要素を巡回して強制型変換を自動的に実行し、集合要素の実際のタイプが集合が持つ汎用情報と一致しない場合、実行時にClassCastException異常を引き起こし、上記コードのforサイクルにInteger in=intList.get(i)を付け加えると、この異常を報告する.
        元のタイプを汎用タイプに割り当てるということですが、逆にすれば?次のコードを参照してください.
List list = new ArrayList();
list.add("a");
list.add("b");
list.add("c");
List li = list;
for (int i = 0; i < list.size(); i++) {
    System.out.println(li.get(i));
    System.out.println(li.get(i).length());   // 1)
}

        1)でコードコンパイルエラーが発生します.これは、汎用タイプのJava変数を汎用タイプの変数を持たない変数に割り当てると、Javaプログラムが消去されます.この消去は実際のタイプの実パラメータを消去するだけでなく、上記のコードのように、li.get(i)は最終的にObjectオブジェクトとして使用されます.
 
        Java汎用設計の原則:コードの一部がコンパイル時にシステムに「[unchecked]チェックされていない変換」警告が発生していない場合、プログラムは実行時にClassCastException異常を引き起こすことはありません.
 
        JDK 1.5から、Javaは3つの方法でマルチスレッドを作成、起動します.
        1.Threadクラスを継承してスレッドクラスを作成し、run()メソッドをスレッド実行体として書き換える.
        2.Runnableインタフェースを実装してスレッドクラスを作成し、run()メソッドをスレッド実行体として書き換える.
        3.Callableインタフェースを実現してスレッドクラスを作成し、run()メソッドをスレッド実行体として書き換える.
        このうち、1つ目の方法は最も効果が悪く、2つのデメリットがあります.
        1.スレッドクラスはThreadクラスを継承し、他の親クラスを継承できない.
        2.各スレッドはThreadサブクラスのインスタンスであるため、複数のスレッド間でデータを共有するのは面倒である.
        2つ目と3つ目については、本質は同じですが、Callableインタフェースに含まれるcall()メソッドは、放出異常を宣言したり、戻り値を有したりすることができます.
        いずれの方法を使用してもrun()メソッドを呼び出してスレッドを起動しないでください.起動スレッドはstart()メソッドを使用する必要があります.プログラムがスレッドオブジェクトのstart()メソッドを呼び出して起動していない場合、このスレッドオブジェクトは常に新しい状態になります.