151个建议读书笔记
普通 |
1、 当使用long 类型的数据时,要将值后的l 写为L ,小写的l 容易与数字1 混淆 例子: long num = 3l; long num = 3 L ; |
2、 务必让常量的值在运行时保持不变: 例子: public static final int CANNOT_CHANGE = new Random.nextInt(); 此时的常量在运行时会变,不能这样写 建议: 常量保存到常量类中 |
3、 三目运算的后面两个值的类型一定要相同: 例子: int num = a<100? 90 : 100 Int num = a< 100? 90 : 100.0 两个操作数的类型不同 当两个操作数不同时的转换规则(只做了解): |
4、 方法重写: 了解 |
5、 自增的错误 例子: Int num = 0 ; for(int a = 0 ;a<10;a++){ num=num++; } 错在 num = num++ 改为 num++ 就可以了 对java 中 num= num++ 的理解: 1、 int temp = num 2、 num =num+1 3、 return temp Num = num++ 这句话在c++ y语言中是正确的,在java和php中是错误的 扩展:复习 num++ ++num num-- --num num ++ : int num = 0 ; int sum = num++; 当运行int sum = i++; 这句话时 ,先将num此时的值即0复制到临时区, 然后将num的值0进行加1 ,此时num=1然后再返回 临时区中的0 给sum Int sum = (num++)+(num++); 在没有int sum = num++;这句代码时sum 值为1 Int sum = (num++)+(num++)+(num++);在没有上面两句代码时 sum值为 3 int num = 0 ; Int sum = num++; sum = (num++)+(num++); sum = (num++)+(num++)+(num++); 此时 sum 的值为 12;num值为6 int num = 10; int sum = num--; 返回10,num为9 sum = (num--)-(num--); 返回9 num为8 – 返回8 num 为7 sum = (num--)-(num--)-(num--); 返回7 num为6 7-6-5 此时sum 的值为-4,num值为5 |
6、 静态引入: 一段代码中不能有多个静态引入,否则代码难以阅读;本类中的属性和方法不能覆盖静态引入的类的属性和方法,否则会触发编译器的最短路径原则(即当发生覆盖时会使用本类中的属性和方法而不会使用静态引入的属性和方法。 个人建议: 除断言外尽量少的使用静态引入 |
7、 显示声明UID: 在编写一个实现了Serializable接口的类,需要增加一个Serial Version ID (流标识符),即类的版本定义,可以显式声明和隐式声明, 显式声明: Private static final long serialVersionUID = xxxxxxL; Jvm 在进行反序列化时,会比较数据流中的serialVersionUID 与类中的serialVersionUID 是否相同,如果相同,则认为类没有发生改变,可以把数据流load为实例对象;如果不同jvm会抛出InvalidClassException. 这是一个非常好的校验机制,可以保证类在网络和磁盘中“滚过”一次,仍能做到“出于泥而不染”,完美地实现类的一致性 使用场景: 当类的改动不大时,jvm是否可以把以前的对象反序列化过来,就依靠显式声明serialVersionUID ,向虚拟机撒谎“类的版本没有改变”,这样,我们编写的类就实现了向上兼容。 序列化: 实现序列化的目的是为了持久化,实现了Serializable 接口,可以在网上传输也可以本地存储,通过对象序列化过程,把一个对象从内存块转化为可传输的数据流,然后通过网络发送给消费者那里,并进行反序列化,生成实例对象。 可以自己写一个SerializationUtils 工具类进行序列化和反序列化 serialVersionUID 的使用例子: 8、 使用switch 的时候记住使用break 防止穿透, default 也要使用 break |
9、 避免instanceof 非预期结果 |
10、 发布应用系统时禁止使用类文件替换的方式,整体war包发布才是完全之策 |
11、 用偶判断,不用奇判断 num%2==0?“偶数”:“奇数”; 在被除数小于除数的情况下 商0余数为被除数 num = -1 ; -1%2 余数为-1 如果 是 num%2 == 1 ?“奇数”:“偶数”,-1!=1 所以 num= -1 为 偶数; 用偶判断 num% 2 == 0 ? “偶数”:”奇数”, -1!= 0 ,所以 num=-1 为奇数 java中取余数(%)的算法: |
12、 注意数据类型 Java是先运算然后再进行类型转换的,当num1*num2*num3 的运算结果超过了int 类型的最大值 ,所以运算结果是负值,再转为long型也是负值了 long mul ,int num1 ,int num2 ,int num3 num 错误: long mul = num1*num2*num3 正确: long mul = num1 * num2L *num3 L 类型千万不能放在最后面,如下: 不建议: long mul = num1 * num2*num3L 如果num1*num2的值就已经超过int的范围再用num3L 也无济于事 |
13数据边界 当用户输入的order值为2147483647(int能表示的最大值)时,代码中的order+cur 会超过int能表示的最大范围,数据运算要做边界测试 14、提防包装类的null值。 例如: Integer的拆箱过程是调用包装对象的intValue方法,如果Integer类型的变量值为null则会报空指针异常。包装类型参与运算时,要做null值校验 15、尽量不要用包装类进行大小比较,如果非要比较要使用包装类的comparator,而不能直接使用>,<,==l来比较包装类,类是有地址的 16、优先使用整型池 Integer 的整型池的范围为-128到127,当数值在整型池范围内切需要用Integer时,使用Integer.valueOf( ); 生成包装类实例 17、优先选择基本类型: int 可以加宽转变成long,然后再转变成Long对象,但是不能直接转变成Long的对象。 即: 基本类型可以先加宽,再转变成宽类型的包装类型,但不能直接转变成宽类型的包装类型。 下面代码的编译和运行,会一直输出基本类型的方法被调用 18、 不能重写静态方法,但是可以隐藏静态方法; 注意:通过实例对象访问静态方法或者静态属性是不好的习惯 注意: 通过对象调用静态方法,jvm则会通过对象的表面类型查找到静态方法的入口 19、 构造函数要尽量简化 20、 尽量不要在构造函数中初始化其他类 如果几个类的构造函数中都在初始化另一个类,并且形成了死循环,那样会造成stackOverflowError 栈溢出 21、构造代码块: 构造代码块不会插入到使用了this关键字的构造函数中;super的在super后插入 上面的代码等价于下面的代码: |
22、使用静态内部类提高封装性 只有在是静态内部类的时候才能把static 放在类 class 的前面,其他任何时候static 都不能修饰class。 在下面的例子中仍然可以在外部使用 new Home 来构造对象,只是要引入Person.Home 例: public class Person { /** 姓名*/ Private String name; /**家庭*/ Private Home home ; //静态内部类 Public static class Home{ /** 地址*/ Private String address; /**电话*/ Private String phoneNum; } } 23、 使用匿名类的构造函数 (39 ) 匿名类虽然没有名字,但是可以有构造函数的,它用初始化块或叫构造函数块来代替 理解三句代码: List one = new ArrayList () ; List two = new ArrayList () {}; List three = new ArrayList () { { } }; 一个类中的构造函数块可以是多个,也就是说可以出现如下代码: List three = new ArrayList () { {} {} {} {} }; 24 、保证工具类的不可实例化; 将构造函数设为私有的,并且在构造函数中抛出异常。工具类不可实例化后,注意不要被继承了,并且子类还能实例化, 子类实例化会使父类初始化,而这里的工具类的构造器时private 的 ,不能被子类访问 Private utilClass(){ throw new error (“这是工具类,请不要实例化!”); } |
25、避免对象的浅拷贝 可以理解浅拷贝和深拷贝 浅拷贝: 一个类Student 实现了 cloneable 接口,并重写了clone 方法,就可以进行拷贝 浅拷贝是 一个对象B 拷贝自 对象A时,A的属性值修改时,B的属性值也会自动更改。说明浅拷贝拷贝的是 对象的一个引用地址,所以被拷贝对象改变,拷贝出的对象也会改变。 深拷贝: 将拷贝得到的对象 自行设置属性值,这样拷贝出来的对象自成一体,不会受“母体”影响, 和new 生成的对象没有区别 |
26、 类最好重写 toString () 方法。 Println 和print 的实现机制 ,如果参数是基本数据类型和String就直接打印,如果参数是自定义类型则调用类的toString () 方法 |
27、 使用equals 容易犯的错误: “ A ”.equals(“A” ) ; 是会返回false的, 因为前面的A的有空格,后面的A没有 。 equals 方法的自反性原则 mylist.contains(“a”); lIst 检查是否包含元素时,是通过调用对象的eqauls()方法进行逐一判断,有一个相等就返回 true 使用eqauls 的时候要注意判 null Equals 的自反性 和对称性 |
28、 重写equals 方法的时候必须重写hashcode 方法 |
29、直接使用string 字符串常量池 b1、b2、b3的结果分别为true 、false、true 字符串常量池的原理: New 一个String 对象时,是不会去检查字符串常量池中是否已经有该字符串字面量的String 的,所以b2的结果为false 30、单元测试: 正常测试、边界测试、异常测试 31、string的 replaceall 的第一参数是正则表达式 32、 1+2+“注意区别” 和 “注意区别”+1+2 的 区别 33、正则表达式对象和匹配器 34、 常用中文汉字按拼音排序使用java 的collator ,如果有晦涩的汉字则需要pinyin4j等开源jar 将汉字转换为拼音了。 35、集合的拷贝也是浅拷贝 36、arrayList 的默认长度是10,当元素数量达到长度临界点时,会扩容1.5倍。 当长度为10的arrayList 添加第11个元素时,arrayList 会自动扩容, 新的长度为 10* 1.5 +1 = 16 37、 这段代码输出结果为一的原因: 基本数据类型不能作为asList() 方法的参数,所以编译器会将整个int 数组作为一个参数,将int 改为Integer 则输出的结果为5 38、 asList 方法产生的List 对象不可更改,该方法产生的对象不具有 add和remove 方法,只有get、set、size、 Contains 、和 toArray 方法。除非能非常确定该集合不会改变集合长度,否则此方法慎用 39、arrayList 采用下标遍历效率高于foreach 遍历; LinkedList 采用foreach 遍历效率高于下标遍历,LinkList 是双向链表 40、频繁的插入和删除元素时使用LinkList 效率更高,在处理大批量的添加和删除时LinkList的效率是ArrayList的40倍,但是修改元素ArrayList的效率更高。 而两者的add 添加效率差别不是很大 41、是有版本区别的 |
重要 |
14、使用序列化类的私有方法巧妙解决部分属性持久化问题 |
16、易变业务使用脚本语言编写
|
17、慎用动态编译
|
22、用整数类型处理货币 |
23、不要让四舍五入亏了一方: 要根据不同的场景,慎重选择不同的舍入模式,以提高项目的精准度,减少算法损失 |
30、不要随便设置随机种子 |
40、匿名类的构造函数很特殊 |
41、让多重继承成为现实(利用内部类) |
48、在equals中使用getCLass() 进行类型判断 |
53、注意方法中传递的参数要求 |
69、列表相等只关心数据元素 |