『Effective Java』はこの1編で十分です

9203 ワード

前言
最近《Effective Java》という本を読み終わったので、筆者はここでいくつかの比較的重点の準則についてまとめた.
不要なオブジェクトを作成しない
String s = new String("wugui");

上記の文は、実行するたびに新しいStringインスタンスを作成します.改善されたバージョンは次のとおりです.
String s = "wugui";

改善後は、実行するたびに新しいインスタンスを作成するのではなく、Stringインスタンスが1つしか使用されません.
もう一つ重要な点は、基本タイプではなく基本タイプを優先するには、無意識の自動梱包に注意することです.
上の言葉はどういう意味ですか.次の例を見てみましょう.
public static void main(String[] args){
    Long sum = 0L;
    for(long i = 0;i < Integer.MAX_VALUE; i++){
      sum += i;
    }
    System.out.println(sum);
}

このプログラムで算出された答えは正しいが、実際の状況よりも遅い.文字を間違えたからだ.変数sumは、Longではなくlongとして宣言され、iのサイクル毎に Longタイプにアップグレードされ、プログラムが約2の31乗の余分なLongインスタンスを構築したことを意味する.
期限切れのオブジェクト参照を削除
期限切れの引用とは、永遠に解除されない引用を指す.
たとえば、スタックから飛び出したオブジェクトはゴミ回収として扱われず、スタックを使用するプログラムがこれらのオブジェクトを参照しなくても回収されません.スタック内では、これらのオブジェクトへの期限切れの参照が維持されます.
したがって、オブジェクトリファレンスが期限切れになると、これらのリファレンスを空にするだけで済みます.
element[size]=null;

クラスとメンバーのアクセス性を最小限に抑える
各クラスまたはメンバーが外部からアクセスされないようにするpublic static final変数の特殊な状況を除いて、どのクラスもpublic変数を含むべきではなく、public static final変数が参照するオブジェクトが可変であることを確認する必要があります(例えばString).public static final変数は、基本タイプを指すか、可変オブジェクトを指すかのいずれかです.参照自体は変更できませんが、参照されるオブジェクトは変更できます.
多くのエディタは、プライベート配列ドメインへのアクセス方法を返します.次の方法で解決できます.
private static final Employee[] PRIVATE_VALUES = {......}
public static final List VALUES = Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));

equalsを上書きする場合は共通の約束を守ってください
1.equals法は等価関係を実現した:(1)任意のnullの参照値xに対して自己反転性、x.equals(x)true(2)任意のnullの参照値xに対して対称性を返さなければならず、x.equals(y)trueを返すと、y.equals(x)true(3)任意のnullの参照値xに対して伝達性を返さなければならず、x.equals(y)trueを返すと、y.equals(z)trueを返し、x.equals(z)true(4)の整合性がnull以外の任意の参照値xに対して返されるべきであり、オブジェクトが変更されていない限り、x.equals(y)true(5)の非空性がnull以外の任意の参照値xに対して返され、x.equals(null)falseに戻さなければならない.
  • 高品質equalsメソッドを実現するコツ:
  • (1)==オペレータを使用して、「パラメータがこのオブジェクトの参照であるかどうか」をチェックします.
    もしそうであれば、trueを返します.これはパフォーマンスの最適化にすぎません.比較操作が高価になる可能性がある場合は、このようにする価値があります.
    (2)instanceofオペレータを使用して「パラメータが正しいタイプかどうか」をチェックする
    そうでない場合は、falseを返します.正しいタイプとはequalsメソッドが存在するクラスを指す.場合によっては、クラスが実装されたクラスの比較を可能にするequalsメソッドが改良されたインタフェースを指す.
    (3)パラメータを正しいタイプに変換する
    変換前にinstanceofのテストを行ったので、成功することを確認します.
    (4)パラメータ内のドメインがそのオブジェクト内の対応するドメインと一致するかどうかをチェックする
    これらのテストがすべて成功した場合、trueを返し、そうでなければfalseを返します.floatおよびdoubleタイプでない基本タイプドメインについては、==オペレータを使用して比較することができる.オブジェクト参照ドメインの場合、euqalsメソッドを再帰的に呼び出すことができる.floatおよびdoubleドメインでは、Float.compareおよびDouble.compareメソッドが使用されるべきである.Float.NaN、-0.0f等の定数が配列に対して存在するため、Arrays.equalsの方法を用いることができる.一部のオブジェクトリファレンスドメインにnullが含まれているのは合法です.空のポインタの異常を回避するには、(field==null?o.field==null:field.equals(o.field))
    (5)equalsメソッドを書き終わったら,それらが対称的,伝達的,一致しているかどうかをテストすべきである.
    例は次のとおりです.
    public boolean equals(Object o){
        if(o == this) 
            return true;
        if(!(o instanceof MyClass)) 
           return false;
        Myclass mc=(MyClass)o;
        return mc.x == x && mc.y == y;
    }

    3.注意:(1)equalsを上書きするときはhashCode(2)equalsメソッドをスマートにしすぎないように(3)equals宣言のObjectオブジェクトを他のタイプに置き換えない
     public boolean equals(MyClass o){
              ..........
         }

    問題は、この方法がObjectをカバーしていないことです.パラメータタイプがObjectであるべきであるためequals.逆にObjectを再ロードしましたequals.
    equalsを上書きする場合はhashCodeを上書きします
    equalsメソッドを上書きしたクラスごとにhashCodeメソッドも上書きする必要があります.
  • x.euqals(y)がtrueを返す場合、xとyのハッシュ値は必ず等しい.
  • x.equals(y)がfalseを返す場合、xとyのハッシュ値も等しい可能性がある.
  • xとyのハッシュ値が等しくない場合、x.equals(y)は必ずfalseを返す.

  • 例は次のとおりです.
    public int hashCode(){
       int result = 17;
       result = 31 * result + x;
       result = 31 * result + y;
       result = 31 * result + z;
       return result;
    }

    クラスが可変であり、ハッシュ値を計算するオーバーヘッドが大きい場合は、要求されるたびにハッシュ値を再計算するのではなく、ハッシュ値をオブジェクト内に保存することを考慮する必要があります(たとえば、Stringクラス内にintタイプのhash変数がハッシュ値を保存する).
    Override注記の使用を継続
    まず、次の反例を見てみましょう.
    public class Bigram {
    
        private final char first;
        private final char second;
    
        public Bigram(char first, char second) {
            this.first = first;
            this.second = second;
        }
    
        public boolean equals(Bigram b) {
            return b.first == first && b.second == second;
        }
    
        public int hashcode() {
            return 31 * first + second;
        }
    
        public static void main(String[] args) {
            Set s = new HashSet<>();
            for (int i = 0; i < 10; i++) {
                for (char ch = 'a'; ch <= 'z'; ch++) {
                    s.add(new Bigram(ch, ch));
                }
            }
            System.out.println(s.size());
        }
    }
    

    上記の例では、セットに重複オブジェクトが含まれていないため、プログラムが印刷したサイズは26だと思っていますが、実行後、26ではなく260で印刷されていることに気づきます.いったいどこが間違っているのでしょうか.
    Bigramクラスの作成者がequalsメソッドを上書きしようとしたことは明らかであり、hashcodeメソッドを上書きしたことも覚えているが、残念ながらこのプログラムはequalsメソッドに上書きできず、Objectクラスのequalsメソッドを再ロードした.Objectクラスのequalsメソッドを上書きするには、Objectタイプのequalsメソッドを定義する必要がありますが、上記の例ではリロード操作を行っただけです.
    @Overrideを使用してBigramクラスをマークしている場合にのみ、コンパイラはこのエラーを発見することができます.このコメントを加えてプログラムを実行してみると、コンパイラは次のようなエラーメッセージを生成します.method does not override or implement a method from a supertypeです.そうすると、すぐに自分がどこが間違っているのかを意識します.エラーの代わりに正しい方法を使用します.次のようにします.
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Bigram)) {
                return false;
            }
            Bigram b = (Bigram) o;
            return b.first == first && b.second == second;
        }

    したがって、親を上書きしたいメソッドごとに@Override注記を付ける必要があります.そうすると、コンパイラは大量のエラーを防ぐことができます.
    for-eachサイクルは従来のforサイクルより優先される
    Java1.5リリースで導入されたfor-eachループは、反復器またはインデックス変数を完全に隠すことで、混乱やエラーの可能性を回避します.
    for(Element e : elements){
         doSomething(e)
    }

    注意:for-eachループを使用してもパフォーマンスの損失はありません.実際には、配列インデックスの境界値を1回しか計算しないため、通常のforループよりもパフォーマンスの利点があります.
    要するに、for-eachサイクルは簡潔性とBUG予防の面で従来のforサイクルとは比べものにならない優位性があり、性能損失はない.for-eachサイクルは可能な限り使用すべきである.残念なことに、for-eachサイクルを使用できないことは3つあります.
  • フィルタリング:コレクションを巡回し、指定した要素を削除する必要がある場合は、表示された反復器を使用してremoveメソッド
  • を呼び出す必要があります.
  • 変換-リストまたは配列を巡回し、その要素値の一部または全部を置換する必要がある場合は、要素の値
  • を設定するために、リスト反復器またはインデックス配列が必要です.
  • 平行反復-複数のセットを並列に巡回する必要がある場合は、制御反復器またはインデックス変数を表示する必要があります.すべての反復器またはインデックス変数が同期移動
  • を得ることができるようにする.
    正確な答えが必要な場合はfloatとdoubleは避けてくださいfloatおよびdoubleのタイプは主に科学計算およびエンジニアリング計算のために設計されているが、タワーゲートは完全に正確な結果を提供していないので、正確な結果を必要とする場合に使用されるべきではない.floatおよびdoubleタイプは、floatおよびdoubleを0.1で正確に表すことは不可能であるため、特に通貨計算に適していない.
    例えば次の例です
    public class Test {
        public static void main(String[] args) {
            System.out.println(1.0 - 0.9);
        }
    }

    出力結果:0.0999999999999999998
    この問題を解決する正しい方法は、BigDecimalintまたはlongを使用して通貨計算を行う.
    基本タイプは梱包基本タイプより優先されます
    Javaの変数は主に2つの部分から構成され、1つはint、double、booleanなどの基本タイプであり、もう1つはStringやListなどの参照タイプである.各基本タイプには、梱包基本タイプと呼ばれる対応する参照タイプがあります.例えばintはInteger,booleanはBooleanなどに対応する.
    Java1.5バージョンでは自動梱包と自動解体が追加されましたが、この2つのタイプには違いがあります.
    次のコンパレータを見てください
    Comparator comparator = new Comparator(){
       public int compare(Integer first,Integer second){
           return first < second ? -1:(first == second ? 0:1);
       }
    }

    この比較器は表面的によく見え、多くのテストに合格することができます.でも印刷するとcompare(new Integer(42)、new Integer(42))では、0を印刷するはずだったが、最後の結果は1で、1番目のInteger値が2番目より大きいことを示している.
    問題はどこですか.メソッドの最初のテストはよくできています.first正しいプログラムは次の通りです.
    Comparator comparator = new Comparator(){
       public int compare(Integer first,Integer second){
           int f = first;
           int s = second;
           return f < s ? -1:(f == s ? 0:1);
       }
    }

    では、いつ箱詰めの基本タイプを使うのでしょうか.それらにはいくつかの合理的な用途があります.1つ目は集合の要素として、ListではなくListのような基本的なタイプを集合に置くことはできません.2つ目は、汎用型では、ThreadLocalのような梱包基本タイプをタイプパラメータとして使用する必要があります.
    要するに、選択可能な場合、基本タイプは梱包基本タイプより優先される.基本的なタイプはもっと簡単で、もっと速いです.
    まとめ
    『Effective Java』についての知識点をここまで紹介しますが、最近は『コード整頓の道』を見ていて、あとは単独でまとめることもあるかもしれませんので、間違ったところがあればよろしくお願いします.