Java学習シリーズ(26)Javaコードの最適化解説


転載は出典を明記してください.http://blog.csdn.net/lhy_ycu/articale/detail/45506549
 
始まる前に、まず『Java学習シリーズ』のinstance ofキーワードの使用と落とし穴を補充します.簡単な説明:instance ofは簡単な二元演算子であり、オブジェクトがクラスであるかどうかを判断するための例である.instance ofの左右の操作数が継承または実現される関係がある限り、プログラムはコンパイルされます.簡単な例を通して、instance ofキーワードの使用とその落とし穴を説明します.
 
class A {
	public boolean isDateInstance(T t) {
		return t instanceof Date;
	}
}

public class InstanceofTest {

	public static void main(String[] args) {
		// true。  String   Object  (java Object       )
		System.out.println("zhangsan" instanceof Object);
		// false。Object   ,        String    
		System.out.println(new Object() instanceof String);
		// true。  String   String   
		System.out.println(new String() instanceof String);
		//       。'a'    char  ,     
		System.out.println('a' instanceof Character);
		// false。       null(      ),         false
		System.out.println(null instanceof String);
		// false。   null      null
		System.out.println((String) null instanceof String);
		//       。  Date String          
		System.out.println(new Date() instanceof String);
		// false。        ,T   Object   ,       "lisi"     ,  T   String   。
		System.out.println(new A().isDateInstance("lisi"));
		List list = new ArrayList();
		//       。instanceof         。
		System.out.println(list instanceof List);
	}
}
 
【注意】instance ofは対象の判断にしか使えず、基本タイプの判断には使えない.
 
以下から正式にテーマに入ります.まず自己増加の罠から始めましょう.
1)自己増加の罠
 
int num = 0;
for (int i = 0; i < 100; i++) {
	num = num++;
}
System.out.println("num = " + num);
印刷結果は何ですか?答えは0です.なぜですか?まず実行手順を見てください.プログラムの初回循環時の詳細な手順は以下の通りです.JVMはnum値(0)を臨時変数エリアにコピーし、num値に1を加算します.これはnumの値が1で、次に臨時変数エリアの値を返します.この値は1が修正されていないので、最後にnumに戻ります.この時numの値は0にリセットされます.簡単に言えばint temp=numです.num+=1return temp;この3ステップです.だから印刷結果はまだ0で、numはずっと元の状態を保っています.
 
最適化:num=num+;num++に変更すればいいです.
 
2)定数は変数になりますか?
みなさんは常量が変数になりますか?答えは可能ですが、これは認められません.
 
public static final int RAND_CONST = new Random().nextInt();

public static void main(String[] args) {
	//       ,        ,                           
	System.out.println("     ?" + RAND_CONST);
}
最適化の提案:必ず定数の値は運行期間に不変であるため、RAND_CONSTは定義時に直接値を付けて書き込みます.
 
 
3)「l」はiの大文字、数字1ですか?それともアルファベットの小文字ですか?
 
public static  long l = 11;
最適化:文字拡張子lはできるだけ大文字Lにします.
 
 
4)3つの演算子のタイプが一致しませんか?
 
int i = 70;
System.out.println(i < 100 ? 80 : 90.0);
印刷結果は予想外で、結果は80.0です.なぜですか?i<100は確かにtrueですが、最後の操作数は90.0で浮動小数点です.このときコンパイラは第二の操作数80を80.0浮動小数点に変更します.結果のタイプを統一しますので、印刷結果は80.0です.最適化:90.0を90に変更します.
 
5)変化するパラメータを含む方法を積載しないでください.
 
簡単に説明します.変長パラメータは方法の最後のパラメータでなければなりません.一つの方法は複数の変長パラメータを定義することができません.
 
public class Test01 {
	public static void fruitPrice(int price, int discount) {
		float realPrice = price * discount / 100.0F;
		System.out.println("          :realPrice = " + realPrice);
	}

	public static void fruitPrice(int price, int... discounts) {
		float realPrice = price;
		for (int discount : discounts) {
			realPrice = price * discount / 100.0F;
		}
		System.out.println("         :realPrice = " + realPrice);
	}

	public static void main(String[] args) {
		fruitPrice(48888, 85);
	}
}
印刷結果は何ですか?非変長パラメータから得られた結果:realPrice=41554.8、つまりプログラム実行は最初の方法で、変長パラメータ方法を実行しなかったのはなぜですか?Javaはコンパイルする時、まず実際の参数と種類(ここは2つともintタイプの実参です.int配列に転じることがないように注意してください.)によって処理します.つまり、flutPrice方法を見つけます.また、方法の署名条件を確認します.コンパイラも“なまける”ことが好きなので、プログラムは最初の方法を実行します.もう一つ見てください
 
public class Test02 {
	public void method1(String str, Integer... integers) {
		System.out.println("       Integer      ...");
	}

	public void method1(String str, String... strs) {
		System.out.println("       String      ...");
	}

	public static void main(String[] args) {
		Test02 t = new Test02();
		//      。           ,             ,      。
		t.method1("test02");
		//      。  [   null      ],    。
		t.method1("test02", null);
	}

}
 
t.methodに対して(「test 02」、null);String[]string=nullまたはInteger[]ints=nullを先に宣言したら.つまり、コンパイラにこのnullがStringまたはIntegerタイプであることを知ってもらえば、コンパイルによってできます.
6)静的導入を慎む
この点は分かりやすいです.静的導入の役割は、ある種類のクラスのメンバー(静的変数、静的方法)をこのクラスに導入することです.この場合、ちょうどこのクラスに同名のクラスのメンバーがいると、混淆が生じる可能性があり、後から維持するのも面倒です.
最適化:タイプ.クラスメンバー
7)タイプを黙って変えない
 
public class Test03 {
	//    30    
	public static final int LIGHT_SPEED = 30 * 10000 * 1000;

	public static void main(String[] args) {
		long distance = 8 * 60 * LIGHT_SPEED;
		//     (   ):         :-2028888064
		System.out.println("         :" + distance);
	}
}
どうしてマイナスですか?これはJavaが先に計算してからタイプ変換をするからです.distanceの3つの演算パラメータは、いずれもintタイプで、3つの結果が等しいのもintタイプですが、int値の最大範囲を超えていますので、負の値になります.このようにしてlong型になります.結果は負の値です.解決策:long distance=1 L*8*60*LIGHT_SPEED;1 Lは長い整体型で、右の等式タイプは自動的に進級して、計算した結果も長い整体型です.
 
最適化:基本的なタイプの変換は、アクティブな声明を使用して演算に参加することが望ましいです.
8)包装類性の値はnullですか?
 
 
public static void main(String[] args) {
	List list = new ArrayList();
	//     (          )。       valueOf     。
	list.add(1);
	list.add(2);
	list.add(null);
	//     (          )。             intValue     。
	int count = 0;
	for (int item : list) {
		count += item;
	}
	System.out.println("count = " + count);
}
運転結果報告異常java.lang.Null PointerException.理由は簡単です.箱を開ける過程で、包装対象のintValue方法を標準的に呼び出して実現しました.包装類はnull値ですので、空の針が異常です.ソリューション:
 
 
for (Integer item : list) {
	count += (item == null) ? 0 : item;
}
 
最適化:包装タイプが演算に参加する場合、null検査を行います.
9)ツールクラスを実装できないようにする
ツールクラスの方法と属性は静的で、インスタンスを生成する必要がなくてもアクセスできます.また、そのクラスのメンバーはメモリにコピーが一つしかなく、jdkも良い処理をしました.初期化が望まれないので、その構造関数はプライベートアクセス権限として設定される.
 
public class UtilClass {
	//       
	private UtilClass() {

	}
}
しかし、このような問題があります.道具類には方法が多いかもしれません.なんとなくnewは新しい対象を作ってしまいました.このようにして、実際にインスタンスを生成する必要がない目的に達していません.
 
最適化:ツールクラスを使用する場合、すべてのアクセスはクラス名で行われることを保証します.
public class UtilClass {
	//       
	private UtilClass() {
		throw new Error("please don't instantial this util class...");
	}
}
 
10)循環条件に計算を持たないでください.
サイクル(for、whileなど)条件で計算すると、サイクル毎に計算しなければなりません.
 
while (n < count * 2) {
	//...
}
最適化:whileの中の演算を抽出すればいいです.
int total = count * 2;
while (n < total) {
	//...
}
11)自主的にゴミの回収を行わないでください.
 
できるだけSystem.gc()を使わないでください.自発的にごみを回収しにきます.System.gcはすべての応答を停止しますので、メモリに回収可能なオブジェクトがあるかどうかを確認できます.すべての対象を一度チェックして、そのごみの対象を処分します.これはアプリケーションシステムにとって大きなリスクであり、ウェブプロジェクトであれば、System.gcを呼び出してすべての要求を一時停止させ、ゴミ回収器の実行が完了するのを待つ(正常業務の運行に深刻な影響を与える可能性がある).ウェブプロジェクトの中に対象が多い場合、System.gc実行時間は非常にかかりますので、自発的にゴミ回収を行わない方がいいです.
 
12)静的変数は必ず先に宣言した後に値を割り当てます(または使用します).
 
 
public class Test01 {
	static {
		num = 20;
	}
	public static int num = 2;

	public static void main(String[] args) {
		System.out.println(num);
	}
}
みんなは考えてみて、結果はいくらですか?印刷結果は:2です.なぜですか?これは静的変数(クラス変数)がクラスローディングの時にデータエリアに割り当てられ、メモリにコピーが一つしかないからです.詳細には、静的変数はクラス初期化の時に最初にロードされます.JVMはクラス内のすべての静的声明を検索してアドレス空間を割り当てます.その後、JVMはクラスによって静的に割り当てられます.(静的なクラスの割り当てと静的なコードブロックの割り当てを含む)の順序で実行されます.
 
最適化:静的変数を先に宣言してから使用します.
 
補足1——文字列定数池
 
Javaのオブジェクトはヒープメモリに保存されていることはよく知られていますが、文字列(定数)池は非常に特殊で、コンパイル期間にJVMの常量池の存在が決定されました.ゴミ回収器はそれを回収しません.文字列を作成する時、まず池の中に文字列が等しい文字列があるかどうかを確認します.もしあれば作成しなくて、直接池の中のオブジェクトの参照に戻ります.ない場合は作成してから放します.池に入り、作成対象の参照を返します.以下の例を参照してください.
 
public class Test {
	public static void main(String[] args) {
		String str1 = "java    ";
		String str2 = "java    ";
		String str3 = new String("java    ");
		String str4 = str3.intern();

		System.out.println(str1 == str2);
		System.out.println(str1 == str3);
		System.out.println(str1 == str4);
	}
}
結果は何ですか?答えはtrue、false、trueです.解析:最初の文字列「javaコード最適化」を作成する時、まず文字列の池にそのオブジェクトがあるかどうかを確認します.ないことを発見しました.そこで最初の「javaコード最適化」を作成します.この文字列をプールに入れて、もうこれ以上str 2文字列を作成すると、その文字列が池にあるので、直接にその対象の引用に戻ります.このとき、str 1とstr 2は同じアドレスを指しています.すべてのstr 1==str 2はtrueに戻ります.new String(「javaコード最適化」)声明の対象はStringで、文字列池を確認しないし、対象をプールに入れないので、当然falseに戻ります.intern方法を使ってなぜtrueに戻りますか?internは現在のオブジェクトが池の中に文字列が等しい参照対象があるかどうかをチェックします.もしあればtrueに戻ります.なかったらfalseに戻ります.
 
最適化提案:特殊な要求がないなら、Steringを使って直接量を賦課することをオススメします.
 
補足2:String、StrigBurer、StrigBuiderの使用シーン
 
①Stringの使用シーン:文字列があまり変わらないシーンでは、例えば、定数の声明、少量の変数演算など、String類を使用します.
②StringBufferの使用シーン:頻繁に文字列演算(例えば、文字列スティッチング、置換、削除など)を行い、マルチスレッド環境で実行すると、SteringBufferを使用することが考えられます.例えば、XML解析、HTTPパラメータ解析、パッケージなど.
③StringBuiderの使用シーン:文字列演算(例えば、文字列スティッチング、置換、削除など)が頻繁に行われ、シングルスレッド環境で実行される場合、SQL文のパッケージ、JSONパッケージなど、StringBuiderを使用することが考えられます.
 
参考文献:『高品質コードの作成』