改善Java程序的151個建議

建議1:不要在常量和變量中出現易混淆的字母

1、包名全小寫,類名首字母大寫,常量全部大寫並用下劃線分隔,變量纔有駝峯命名法

2、舉例(long類型數值後面小寫l 和大寫L的影響)

public class TestDemo {
    public static void main(String[] args) {
        long i = 1l;
        // long i = 1L;
        System.out.print("i * 2 = " + (i + i))
    }
}

此處,容易得出結果是22,真正結果是 2。

比如字母“o” 和 數字 0。

建議2:莫讓常量蛻變成 變量

常量一般加了 final 和 static 修飾符,基本不可能二次賦值。但下面有個特殊例子:

public class TestDemo {
    public static void main(String[] args) {
        System.out.print("變化的常量: " + Const.RAND_CONST)
    }
}

// 接口常量
interface Const {
    // 變化的常量
    public final static int RAND_CONST = new Random().nextInt();
}

注意:務必讓常量的值在運行期保持不變。

建議3:三元操作符的類型務必一致

舉例:

public class TestDemo {
    public static void main(String[] args) {
    	int i = 80;
    	String s1 = String.valueOf(i < 100 ? 90 : 100);		// s1:90
    	String s2 = String.valueOf(i < 100 ? 90 : 100.0);	// s2:90.0
        System.out.print("s1 等於 s2: " + s1.equals(s2));  // false
    }
}

此處包含了 隱式類型轉換(int -> float),所以二者 不相等。

建議4:避免帶有變長參數的方法重載

方法重載也叫(overload):就是在同一個類中,方法的名字相同,參數列表不同(順序不同、個數不同、類型不同),實現相似的功能,與修飾符、返回值類型無關。我們會覺得方法調用的時候就像調用一個方法一樣。

方法重寫也叫方法覆蓋(override):首先存在繼承的關係中,子類繼承父類並重寫父類的屬性、方法。方法名字相同,參數列表一致、返回值類型一致或父類返回類型的子類類型、修飾符不能縮小範圍。子類不能重寫構造方法、屬性、靜態方法、私有方法。

舉例:

public class TestDemo {
    // 簡單折扣
    public void calPrice(int price, int discount) {
        float money = price * discount / 100.0F;
        System.out.println("簡單折扣後價格:" + formatCurrency(money));
    }
    // 複雜折扣
    public void calPrice(int price, int... discount) {
        float money = price;
        for(int count : discount) {
            money = money * count / 100;
        }
        System.out.println("複雜折扣後價格:" + formatCurrency(money));
    }
    private String formatCurrency(float price) {
        return NumberFormat.getCurrencyInstance().format(price / 100);
    }
    
    public static void main(String[] args) {
    	TestDemo test = new TestDemo();
    	// 75 折
    	test.calPrice(49900, 75);
    }
}

此處,重載方法也是可以運行的。實際調用的方法是第一個。

建議5:別讓 null 值 和 空值威脅到 變長方法

舉例:

public class TestDemo {
    public void methodA(String str, Integer... value){}

    public void methodA(String str, String... value){}

    public static void main(String[] args) {
        TestDemo test = new TestDemo();
        test.methodA("test", 0);
        test.methodA("test", "haha");
        test.methodA("test");			// 編譯報錯,因爲2個方法均滿足,編譯器無法決定使用哪一個
        test.methodA("test", null);		// 編譯報錯,因爲2個方法均滿足,編譯器無法決定使用哪一個
    }
}

可以修改爲這樣調用:

String[] arr = new String[1];
arr[0] = null;
test.methodA("test", arr);

建議6:覆寫變長方法也循規蹈矩

public class TestDemo {
    public static void main(String[] args) {
        // 向上轉型
        Base base = new Sub();
        base.fun(100, 50);
        // 不轉型
        Sub sub = new Sub();
        sub.fun(100, 50);  // 編譯報錯
    }
}
// 基類
class Base {
    void fun(int price, int... discounts){
        System.out.println("Base fun");
    }
}
// 子類
class Sub extend Base {
	@Override
    void fun(int price, int[] discounts){
        System.out.println("Sub fun");
    }
}

注意:方法重寫(覆寫)的方法參數需與父類相同,不僅僅是類型、數量、順序,還包括顯示形式。

建議7:警惕自增的陷阱

public class TestDemo {
    public static void main(String[] args) {
        int count = 0;
        for(int i = 0; i< 10; i++) {
            count = count++;
        }
        System.out.println("count= " + count); 
    }
}

此處輸出count=0。

首先將count的值拷貝到臨時變量區,然後對count加1,最後返回臨時變量區的值,將count重置爲0。

建議9:少用靜態導入

import static java.lang.Double.*;
import static java.lang.Math.*;
import static java.lang.Integer.*;
import static java.text.NumberFormat.*;

public class Test {
    public static void main(String[] args) {
        double s = PI * parseDouble(1);
        NumberFormat nf = getInstance();//此處可閱讀性差,應改爲 NumberFormat.getInstance()
    }
}

注意:

  • 不建議使用 *(星號)來導入靜態元素;
  • 方法名是具有明確、清晰表象意義的工具類。

建議10:不要在本類中覆蓋靜態導入的變量和方法

import static java.lang.Math.PI;
import static java.lang.Math.abs;

public class Test {
	// 常量名與靜態導入的 PI 相同
	public final static String PI = "祖沖之";
	
	// 方法名 與 靜態導入的方法相同
	public static int abs(int value) {
        return 0;
	}
	
    public static void main(String[] args) {
        System.out.println("PI=" + PI);				// 輸出“祖沖之”
        System.out.println("abs()=" + abs(-100));	// 輸出 0
    }
}

此處並沒有調用 Math的方法,而是調用 本類(Test)的常量和方法。

因爲編譯器有一個“最短路徑”原則:如果能夠在本類中找到變量、常量和方法,就不會到其他包或父類、接口中查找,以確保本類的屬性、方法優先。

建議11:養成良好習慣,顯示聲明 UID

我們將一個類 實現 Serializable 接口時,常常編譯器會提醒我們加上 serialVersionUID。如果我們不指定,則會在編譯時自動生成。此後,我們進行反序列化時,程序會比較 serialVersionUID的值。相同則可以反序列化,不同則拋出異常(InvaildClassException)。

所以,請顯示聲明 serialVersionUID。

建議12:避免用序列化類在構造函數中爲不變量賦值

不知道怎麼說,那就記住它吧。

注意:

  • 反序列化時構造函數不會被執行。
  • 在序列化類中,不要使用構造函數爲 final 變量賦值。

建議13:避免爲 final 變量複雜賦值

注意:此建議可以結合建議12來看,是其拓展。總結如下:

  • 在序列化類中,不要使用構造函數爲 final 變量賦值。
  • 在序列化類中,不要使用方法返回值爲 final 變量賦值。

建議14:使用序列化類的私有方法巧妙解決部分屬性持久化問題

部分屬性持久化問題,常見的解決方法是在不需要持久化的的字段前加 transient 關鍵字即可。

建議15:break萬不可忘

未完,待續。。。(PS:歡迎大家補充)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章