zoukankan      html  css  js  c++  java
  • 编写高质量代码:改善Java程序的151个建议 --[0~25]

    警惕自增的陷阱
    public class Client7 {
        public static void main(String[] args) {
            int count=0;
            for(int i=0; i<10;i++){
                count=count++;
            }
            System.out.println("count = "+count);
        }
    }
    

    count++是一个表达式,是由返回值的,它的返回值就是count自加前的值,Java对自加是这样处理的:首先把count的值(注意是值,不是引用)拷贝到一个临时变量区,然后对count变量+1,最后返回临时变量区的值。程序第一次循环处理步骤如下:

    JVM把count的值(其值是0)拷贝到临时变量区;
    count的值+1,这时候count的值是1;
    返回临时变量区的值,注意这个值是0,没修改过;
    返回值赋给count,此时count的值被重置为0.

    如下理解:

    public static int mockAdd(int count) {
            // 先保存初始值
            int temp = count;
            // 做自增操作
            count = count + 1;
            // 返回原始值
            return temp;
        }
    
    不要在本类中覆盖静态导入的变量和方法

    编译器有一个"最短路径"原则:如果能够在本类中查找到相关的变量、常量、方法、就不会去其它包或父类、接口中查找,以确保本类中的属性、方法优先。
    因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖.

    显示声明serialVersionUID可以避免对象的不一致,但尽量不要以这种方式向JVM撒谎。
    避免用序列化类在构造函数中为不变量赋值

    反序列化时构造函数不会执行

    反序列化时final变量在以下情况下不会被重新赋值:

    1. 通过构造函数为final变量赋值
    2. 通过方法返回值为final变量赋值
    3. final修饰的属性不是基本类型
    使用序列化类的私有方法巧妙解决部分属性持久化问题
    public class Person implements Serializable {
    
        private static final long serialVersionUID = 9146176880143026279L;
    
        private String name;
    
        private transient Salary salary;
    
        public Person(String _name, Salary _salary) {
            this.name = _name;
            this.salary = _salary;
        }
        //序列化委托方法
        private void writeObject(ObjectOutputStream oos) throws IOException {
            oos.defaultWriteObject();
            oos.writeInt(salary.getBasePay());
        }
        //反序列化委托方法
        private void readObject(ObjectInputStream input)throws ClassNotFoundException, IOException {
            input.defaultReadObject();
            salary = new Salary(input.readInt(), 0);
        }
    }
    
    switch 语句里面break不可少
    慎用动态编译
    1. 在框架中谨慎使用:比如要在struts中使用动态编译,动态实现一个类,它若继承自ActionSupport就希望它成为一个Action。能做到,但是debug很困难;再比如在Spring中,写一个动态类,要让它注入到Spring容器中,这是需要花费老大功夫的。
    2. 不要在要求性能高的项目中使用:如果你在web界面上提供了一个功能,允许上传一个java文件然后运行,那就等于说:"我的机器没有密码,大家都可以看看",这是非常典型的注入漏洞,只要上传一个恶意Java程序就可以让你所有的安全工作毁于一旦。
    3. 记录动态编译过程:建议记录源文件,目标文件,编译过程,执行过程等日志,不仅仅是为了诊断,还是为了安全和审计,对Java项目来说,空中编译和运行时很不让人放心的,留下这些依据可以很好地优化程序。

    断言绝对不是鸡肋

    什么情况下能够使用assert呢?一句话:按照正常的执行逻辑不可能到达的代码区域可以防止assert。具体分为三种情况:

    在私有方法中放置assert作为输入参数的校验:在私有方法中可以放置assert校验输入参数,因为私有方法的使用者是作者自己,私有的方法的调用者和被调用者是一种契约关系,或者说没有契约关系,期间的约束是靠作者自己控制的,因此加上assert可以更好地预防自己犯错,或者无意的程序犯错。
    流程控制中不可能到达的区域:这类似于Junit的fail方法,其标志性的意义就是,程序执行到这里就是错误的

    public void doSomething() {
            int i = 7;
            while (i > 7) {
                /* 业务处理 */
            }
            assert false : "到达这里就表示错误";
        }
    

    不要只替换一个类

    对于final修饰的基本类型和String类型,编译器会认为它是稳定态的(Immutable Status)所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Reference),以提高代码的执行效率。对于我们的例子来说,Client类在编译时字节码中就写上了"150",这个常量,而不是一个地址引用,因此无论你后续怎么修改常量类,只要不重新编译Client类,输出还是照旧。

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

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

    用偶判断,不用奇判断

     i % 2 == 0 ? "偶数" : "奇数";
    

    用整数类型处理货币

    (1)、使用BigDecimal

        BigDecimal是专门为弥补浮点数无法精确计算的缺憾而设计的类,并且它本身也提供了加减乘除的常用数学算法。特别是与数据库Decimal类型的字段映射时,BigDecimal是最优的解决方案。

    (2)、使用整型

        把参与运算的值扩大100倍,并转为整型,然后在展现时再缩小100倍,这样处理的好处是计算简单,准确,一般在非金融行业(如零售行业)应用较多。此方法还会用于某些零售POS机,他们输入和输出的全部是整数,那运算就更简单了.

    基本类型转换时,使用主动声明方式减少不必要的Bug.

    long dis2 = LIGHT_SPEED * 60L * 8;
    

      60L是一个长整型,乘出来的结果也是一个长整型的(此乃Java的基本转换规则,向数据范围大的方向转换,也就是加宽类型),在还没有超过int类型的范围时就已经转换为long型了,彻底解决了越界问题。在实际开发中,更通用的做法是主动声明类型转化(注意,不是强制类型转换)代码如下:

    long dis2 = 1L * LIGHT_SPEED * 60L * 8
    

      既然期望的结果是long型,那就让第一个参与的参数也是Long(1L)吧,也就说明"嗨"我已经是长整型了,你们都跟着我一块转为长整型吧。

    边界还是边界

    int long 这些类型的最大值,最小值的边界处理

    银行家舍入(Banker's Round)的近似算法,其规则如下:

    舍去位的数值小于5时,直接舍去;
    舍去位的数值大于等于6时,进位后舍去;
    当舍去位的数值等于5时,分两种情况:5后面还有其它数字(非0),则进位后舍去;若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。

    import java.math.BigDecimal;
    import java.math.RoundingMode;
    
    public class Client25 {
        public static void main(String[] args) {
            // 存款
            BigDecimal d = new BigDecimal(888888);
            // 月利率,乘3计算季利率
            BigDecimal r = new BigDecimal(0.001875*3);
            //计算利息
            BigDecimal i =d.multiply(r).setScale(2,RoundingMode.HALF_EVEN);
            System.out.println("季利息是:"+i);
            
        }
    }
    

    ROUND_UP:原理零方向舍入。向远离0的方向舍入,也就是说,向绝对值最大的方向舍入,只要舍弃位非0即进位。
    ROUND_DOWN:趋向0方向舍入。向0方向靠拢,也就是说,向绝对值最小的方向输入,注意:所有的位都舍弃,不存在进位情况。
    ROUND_CEILING:向正无穷方向舍入。向正最大方向靠拢,如果是正数,舍入行为类似于ROUND_UP;如果为负数,则舍入行为类似于ROUND_DOWN.注意:Math.round方法使用的即为此模式。
    ROUND_FLOOR:向负无穷方向舍入。向负无穷方向靠拢,如果是正数,则舍入行为类似ROUND_DOWN,如果是负数,舍入行为类似以ROUND_UP。
    HALF_UP:最近数字舍入(5舍),这就是我们经典的四舍五入。
    HALF_DOWN:最近数字舍入(5舍)。在四舍五入中,5是进位的,在HALF_DOWN中却是舍弃不进位。
    HALF_EVEN:银行家算法

  • 相关阅读:
    字符串中去除连续相同的字符保留一个
    截取字符串中最后一个中文词语(MS SQL)
    WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for 'jquery'. Please add a ScriptResourceMapping named jquery(case-sensitive).
    Unexpected error from external database driver (1)
    RDLC报表显示存储于数据库的图片
    从angularjs传递参数至Web API
    angularjs呼叫Web API
    创建Web API并使用
    打印网页中某一段内容
    在angularjs实现一个时钟
  • 原文地址:https://www.cnblogs.com/androidsuperman/p/9354912.html
Copyright © 2011-2022 走看看