改善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:欢迎大家补充)

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