给程序员的几点编程经验----《编写高质量代码》

一.在非idea编译的情况下,不要只替换一个类

我们经常在系统中定义一个常量接口(或常量类),以囊括系统中涉及的常量,从而简化代码,方便开发,在很多的开源项目中采用了类似的方法,比如在Struts2中,org.apache.struts2.StrutsConstants就是一个常量类,它定义了一个Status框架中配置的有关的常量,而org.apache.status2.StatusStatics则是一个常量接口,其中定义了一个OGNL访问的关键字。

    关于常量接口(类)我们来看一个例子,首先定义一个常量类:

public class Constant{
    //定义人类寿命的极限
    public final static int MAX_AGE = 150;
}

这是一个非常简单的常量类,定义了一个人类的最大年龄,我们引用这个常量,代码如下:

public class Client {
    public static void main(String[] args){
        System.out.println("人类的极限寿命:"+Constant.MAX_AGE);
    }    
}

运行的结果非常简单。目前的代码编写都是在“智能型”IDE工具中完成的,下面我们暂时回溯到原始时代,也就是回归到记事本编写代码的时代,然后看看会发生什么奇妙的事情把。

修改常量类Constant类,人类的寿命增加了,最大能活到180岁,代码如下:

public class Constant {
    //定义人类的极限寿命
    public final static int MAX_AGE = 180;
}

然后重新编译:javac Constant, 编写完成后执行:java Client,大家想看看输出的极限年龄是多少码?

输出的结果是:“人类寿命极限是:150”,竟然没有改变为180,太奇怪了,这是为何?原因是:对于final修饰的基本数据类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Referece),以提高代码的执行效率。针对我们的雷子来说,Client类在编译时,字节码中就写不上“150”这个常量,而不是一个地址的引用,因此无论你后续怎么修改常量类,只要不重新编译Client类,输出还是照旧。

   而对于final修饰的类(即非基本类型),编译器认为它是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final),如果Client类引入的常量是一个类或者实例,即使不重新编译也会输出最新值。

呃,我们例子中为什么不在IDE工具中运行呢?那是因为在IDE中不能重现该问题,若修改了Constant类,IDE工具会自动编译多有的引用类,“智能”化屏蔽了该问题,但潜在的风险其实仍然存在。

注意: 发布应用系统时禁止使用类文件替换的方式,整体war包发布才是万全之策。

二.用偶判断,不用奇判断

  判断一个数是奇数还是小学里学的基本知识,能够被2整除的整数是偶数,不能被2整除的是奇数,这规则简单又明了,还有什么好考虑?好,我们来看一个例子,代码如下:

public class Client {
    public static void main(String[] args){
        //接受键盘输入参数
        Scanner input = new Scanner(System.in);
        System.out.println("请输入多个数字判断奇偶:");
        Where(input.hasNextInt()){
            int i = input.nextInt();
            String str = i+ "->" + (i%2 == 1?"奇数":"偶数");
            Syetem.out.println(str);
        }
    }
}

输入多个数字,然后判断每个数字的奇偶性,不能被2整除就是奇数,其他的都是偶数,完全是根据奇偶数的定义编写的程序,我们看看打印的结果:

     前三个还是很靠谱,第四个参数-1怎么可能会是偶数呢,这Java也太差劲了,如此简单的计算也会出错!我们先了解一下Java中的取余(%标示符)算法,模拟代码如下:

//模拟取余计算,dividend被除数,divisor除数
public static int remainder(int dividend,int divisor){
    return dividend - dividend / divsor * divsor;
}

看到这段代码,相信大家都会心地笑了,原来Java是这么处理取余计算的呀。根据上面的模拟取余可知,当输入-1的时候,运算结果是-1,当然不等于1了,所以它就是被判断为偶数了,也就是说我们的判断失误了。问题明白了,修正也是很简单,改为判断是否是偶数即可,代码如下:

注意: 对于基础知识,我们应该“知其然,并知其所以然”。

三.边界,边界,还是边界

某商家生产的电子产品非常畅销,需要提前30天预定才能抢到手,同时它还规定了一个会员可拥有的最多产品数量,目的是防止囤积压货肆意加价。会员的预定过程是这样的:先登录官方网站,选择产品型号,然后设置需要预定的数量,提交,符合规则即下单成功,不符合规则提示下单失败。后台的处理逻辑模拟如下:

public class Client {
    //一个会员拥有产品的最多数量
    public final static int LIMIT = 2000;
    public static void main(String[] args){
        //会员当前拥有的产品数量
        int cur =  1000;
        Scanner input = new Scanner(System.in);
        System.out.println("请输入需要预定的数量:");
        Where(input.hasNext()){
            int order = input.nextInt();
            //当前拥有的与准备预定的产品数量之和
            if(order>0 && order+cur<=LIMIT){
                System.out.println("你已经成功预定的"+order+"个产品!");
            }else{
                System.out.println("超过限额,预定失败!");    
            }
        }
    } 
}

       这是一个简易的订单处理程序,其中cur代表的是会员已经拥有的产品数量,LIMIT是一个会员最多拥有的产品数量,如果当前预定数量与拥有数量之和超过了最大数量,则预定失败,否则下单成功。业务逻辑很简单,同时在web界面上对订单数量做了严格的校验,比如不能是负值、不能超过最大数量等,但是人算不如天算,运行不大俩小时数据库中就出现异常:某会员拥有的产品数量与预定数量之和远远大于限额。我们模拟一下:

看到没,这个数字远远超过2000的限额,但是竟然预定成功了,真实神奇!

看着2147483647这个数字很眼熟?那就对了,它是int类型的最大值,没错,有人输入一个最大值,是校验条件失效了,why?我们来看程序,order的值是2147483647,那在加上1000就超出int的范围了,其结果是-2147482649,那当然是小于正数2000了!一句话可归原因:数字超界使校验条件失效。

     在单元测试中,有一项测试叫做边界测试(也有交临界值测试),如果一个方法接受的int类型的参数,那么以下三个值是必测的:0、最大值、最小值,其中正最大和负最小是边界值,如果这三个值都没有问题,方法才是比较安全可靠的。我们的例子就是因为缺少边界测试,导致生产系统产生了严重的偏差。

四.优先使用整形池

看代码我们解决问题,

public static void main(String[] args){
    Scanner in =  new Scanner(System.in);
    while(in.hasNextInt()){
    
    int input = in.nextInt();
    System.out.println("\n-----"+ii+"的相等判断-----");
    //俩个通过new 产生的对象
    Integer i = new Integer(input);
    Integer j = new Integer(input);
    
    System.out.println("new产生的对象:"+(i==j));

    //基本类型转换成包装类型后比较
    i=input;
    j=input;
    
    System.out.println("基本类型转换的对象:"+(i==j));
    //通过静态方法生成一个实例
    i=Integer.valueOf(input);
    j=Integer.valueOf(input);

    System.out.println("valueOf产生的对象:"+(i==j));
    }
}

 

分别输入三个值,127,128,555产生的结果图:

很不可思议,对吧。那这是为什么呢?

(1)new产生的Integer对象

new声明就是要生成一个新的对象,没二话,这是俩个对象,地址肯定不相等。false

(2)装箱生成的对象

对于这一点,首先要说明的是装箱动作是通过valueOf方法实现的,也就是说后俩个算法是相同的,那结果肯定也是一样。那现在的问题是:valueOf是如何生成对象的呢?我们阅读一下Integer.valueOf的实现代码。

public static Integer valueOf(int i){
    final int offset = 128;
    if(i>=-128 && i<=127){
       return IntegerCache.cache(i);
     }
    return new Integer(i);
}

 

显而易见,如果是-128到127之前的int类型转换成Integer对象,则直接从cache数组中取,那这个cache数据是作甚么?

static final Integer cache[] = new Integer[-(-128)+127+1];

static{
    for(int i=0; i<cache.length; i++){
        cache[i] = new Integer(i-128);
    }
}

cache是IntegerCache内部类的一个静态数组,容纳的是-127到128之间的Integer对象。通过valueOf产生包装对象时,如果int参数在-128到127之间,则直接在从整型池中获取对象,不再该范围内的int 类型则通过new 生成包装对象。

总结:通过包装类的valueOf生成包装实例可以显著提高空间和时间性能。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值