编写java程序151条建议读书笔记(1)

来源:互联网 发布:网络短剧 搞笑 编辑:程序博客网 时间:2024/05/22 05:42

改善Java程序的151个建议

建议1:不要在常量和变量中出现易混淆的字母

包名全小写,类名首字母全大写,常量全部大写并用下划线分隔,变量采用驼峰命名法(Camel Case)命名等,这些都是最基本的Java编码规范,字母“l”(还包括大写字母“O”)尽量不要和数字混用。如果字母和数字必须混合使用,字母“l”务必大写,字母“O”则增加

注释。字母“l”作为长整型标志时务必大写。

建议2:莫让常量蜕变成变量

import java.util.Random;public class test {public static void main(String[] args) {test02();}public static void test02() {System.out.println("常量会变哦:" + Constant.RAND_CONST);}}interface Constant {public static final int RAND_CONST = new Random().nextInt();}
常量在编译期就必须确定其值,不应该在运行期更改,否则程序的可读性会非常差,甭想着使用常量会变的这个功能来实现序列号算法、随机种子生成,除非这真的是项目中的唯一方案,否则就放弃吧,常量还是当常量使用。

注意:务必让常量的值在运行期保持不变。

建议3:三元操作符的类型务必一致 

public static void test03() {        int i = 80;        String str = String.valueOf(i < 100 ? 90 : 100);        String str1 = String.valueOf(i < 100 ? 90 : 100.0);        System.out.println("两者是否相等:" + str.equals(str1));    }
三元运算时if-else的缩写,三元操作符必须要返回一个数据,而且类型要确定。这里就涉及到类型的转化。1. 若两个操作数不可转换,则不作转换,返回值是Object类型;
2. 若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字转换,int转为long,long转为float等;
3. 若两个操作数中有一个是数字S,另外一个是表达式,且其类型标志位T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T的范围,则T转换为S;
4.  若两个操作数都是直接量数字,则返回值类型范围较大者。

保证三元运算符的两个操作数类型一致。

建议4:避免带有变长参数的方法重载

import java.text.NumberFormat;public class test {public static void main(String[] args) {        test client = new test();        // 499元的货物 打75折        client.calPrice(499, 75);    }    // 简单折扣计算    public void calPrice(int price, int discount) {        float knockdownPrice = price * discount / 100.0F;        System.out.println("简单折扣后的价格是:" + formatCurrency(knockdownPrice));    }    // 复杂多折扣计算    public void calPrice(int price, int... discounts) {        float knockdownPrice = price;        for (int discount : discounts) {            knockdownPrice = knockdownPrice * discount / 100;        }        System.out.println("复杂折扣后的价格是:" + formatCurrency(knockdownPrice));    }    public String formatCurrency(float price) {        return NumberFormat.getCurrencyInstance().format(price);    }}
不确定数量的参数在方法中的传递,在JAVA5之前常用的设计技巧就是把形参定义成Collection类型或其子类类型,或者数组类型,这种方法的缺点就是需要对空参数进行判断和筛选,比如实参为null值和长度为0的Collection或数组。而Java5引入了变长参数(varags)就是为了更好地挺好方法的复用性,变长参数也是要遵循一定规则的,比如变长参数必须是方法中的最后一个参数;一个方法不能定义多个变长参数等。本例中是一个商店打折的例子,这里的方法重载calPrice(int price ,int... discounts)的参数范畴覆盖了calPrice(int price,int discount)的参数范畴,对于calPrice(499,75)这样的计算到底走的哪个函数,如果参数多余两个肯定走的不定参数,而此处的两个参数走的是calPrice(499,75),int是一个原生数据类型,而数组本身是一个对象,编译器想要"偷懒",于是它会从最简单的开始"猜想",只要符合编译条件的即可通过,于是就出现了此问题。
注:慎重考虑变长参数的方法重载

建议5:别让null值和空值威胁到变长方法 

public class test {    public void methodA(String str, Integer... is) {    }    public void methodA(String str, String... strs) {    }    public static void main(String[] args) {        test client5 = new test();        client5.methodA("china", 0);        client5.methodA("china", "people");        client5.methodA("china");        client5.methodA("china", null);    }}
两个methodA都进行了重载,现在的问题是:上面的client5.methodA("china");client5.methodA("china", null);编译不通过,提示相同:方法模糊不清,编译器不知道调用哪一个方法,但这两处代码反应是不同的对于methodA("china")方法,根据实参"china"(String类型),两个方法都符合形参格式,编译器不知道调用那个方法,符合规则却不能调用违反了KISS原则(Keep it Smile,Stupid,即懒人原则),对于Client5.methodA("China",null),null是没有类型的,调用者隐藏了自己的实参类型导致符合规则却不知调用哪个。(这里需要修改为明确null为string类型)。

建议6:覆写变长方法也循规蹈矩 
子类覆写父类的中的方法既可以修正bug,也可以提供扩展的业务功能支持,同时还符合开闭原则(Open-Closed Principle)。符合开闭原则(Open-Closed Principle)的主要特征:
1)对于扩展是开放的(Open for extension)。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。也就是说,我们可以改变模块的功能。
2)对于修改是关闭的(Closed for modification)。对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者.EXE文件,都无需改动。
覆写必须满足的条件:
1)覆写方法不能缩小访问权限;
2)参数列表必须与被覆写方法相同;
3)返回类型必须与被重写方法的相同;
4)重写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少,更有限的异常,或者不抛出异常。

public class test {    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 extends Base {    @Override    void fun(int price, int[] discounts) {        System.out.println("Sub......fun");    }}
该程序中sub.fun(100, 50)报错,提示找不到fun(int,int)方法,表面上看符合了继承规则,但是base对象是把子类对象做了向上转型,形参列表由父类决定,由于是变长参数,在编译时,base.fun(100, 50);中的50这个实参会被编译器"猜测"而编译成"{50}"数组,再由子类Sub执行。直接调用子类编译器并不会把"50"实参类型转换因为数组本身也是一个对象,编译器还没有聪明到要在两个没有继承关系的类之间转换,要知道JAVA是要求严格的类型匹配的,类型不匹配编译器自然就会拒绝执行,并给予错误提示。这里覆写的方法参数列表竟然与父类不相同,这违背了覆写的定义。
注:覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式。

建议7:警惕自增的陷阱

public class test {    public static void main(String[] args) {        int count=0;        for(int i=0; i<10;i++){            count=count++;        }        System.out.println("count = "+count);    }}
结果为0,count++是一个表达式,返回count自加前的值,java中的自加把count的值拷贝至一个临时变量区,然后自加1,最后返回临时变量区的值,此程序处理过程为1)JVM把count的值(其值是0)拷贝到临时变量区;2)count的值+1,这时候count的值是1;3)返回临时变量区的值,注意这个值是0,没修改过;4)返回值赋给count,此时count的值被重置为0。count=count++可理解为用以中间变量保存初始值,自增操作,返回初始值。所以此处结果为0,在不同的语言环境中有着不同的实现,C++中"count=count++"与"count++"是等效的,而在PHP中保持着与JAVA相同的处理方式。每种语言对自增的实现方式各不相同。

建议8:不要让旧语法困扰你 

public class test {public static void main(String[] args) {// 数据定义初始化int fee = 200;// 其它业务处理saveDefault: save(fee);}static void saveDefault() {System.out.println("saveDefault....");}static void save(int fee) {System.out.println("save....");}}
这里的奇怪之处在于出现了标号,java中保留关键字go to但抛弃了其语法,类似的还有const关键字,如果是纯java开发者看到这会容易看到,学过C++之类的可能不会感到奇怪。java扩展了break和continue关键字但是仍要尽量少用,要跟随java更新的脚步,抛弃旧的语法。

建议9:少用静态导入

Java5开始引入了静态导入语法(import static),其目的是为了减少字符的输入量,提高代码的可阅读性。但是滥用静态导入会使程序更难阅读更难维护,静态导入后,代码中就不需要再写类名了,类是"一类事物的描述",缺少了类名的修饰,静态属性和静态方法的表象意义可以被无限放大,很难弄清楚其属性或者方法代表何意,甚至哪一类的属性(方法)都要思考一番。

import static java.lang.Math.*;import static java.lang.Double.*;import static java.lang.Integer.*;import static java.text.NumberFormat.*;import java.text.NumberFormat;public class Client {    public static void formatMessage(String s) {        System.out.println("圆面积是: " + s);    }    public static void main(String[] args) {        double s = PI * parseDouble(args[0]);        NumberFormat nf = getInstance();        nf.setMaximumFractionDigits(parseInt(args[1]));        formatMessage(nf.format(s));    }}
常量PI,这知道是圆周率,parseDouble方法可能是Double类的一个转换方法,那getInstance()方法是哪个类的?是Client本地类?本地没有这个方法,原来是NumberFormat类的方法,这个和formatMessage本地方法没有任何区别了---这代码太难阅读了,所以,对于静态导入,一定要追寻两个原则:
1)不使用*(星号)通配符,除非是导入静态常量类(只包含常量的类或接口)。
2)方法名是具有明确、清晰表象意义的工具类。
import static org.junit.Assert.*;class DaoTest{    @Test    public void testInsert(){        //断言        assertEquals("foo","foo");        assertFalse(Boolean.FALSE);    }}
从程序中很容易判断出assertEquals方法是用来断言两个值是否相等的,assertFalse方法则是断言表达式为假,这是静态导入用到正确的地方带来的好处。减少代码量且可读性较高。

建议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 abs) {return 0;}public static void main(String[] args) {System.out.println("PI = "+PI);System.out.println("abs(-100) = "+abs(-100));}}
程序的输出结果为PI  = "祖冲之",abs(-100) = 0,而不是所期望的正确值,那是因为定义的方法与静态导入相同,很明显是本地的方法被调用了,为何不调用Math类中的那是因为编译器有一个"最短路径"原则:如果能够在本类中查找到相关的变量、常量、方法、就不会去其它包或父类、接口中查找,以确保本类中的属性、方法优先。因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。

 


0 0
原创粉丝点击