读书笔记--编写高质量代码 改善java程序的151个建议(一)基础
不要再常量和变量中出现易混淆的字母
例如在定义long i = 1l时 末尾的l应该使用大写L防止混淆
三元操作符的类型务必一致
例如在i<100?90:100.0时 返回值时float类型 ,三元操作符会类型统一化计算
覆写变长方法也循规蹈矩
例如父类有public void method(int t,int... ints){}方法,子类覆写父类方法:public void method(int t,int[] ints){} 这样子是编译不通过的,int...变长参数在编译后的本质就是int[] 所以覆写失败
警惕自增陷阱
例如一个循环里有i=i++;这样的话 无论循环多少次,i都是初始值,因为i被赋的值是i在自增前的值,自增的是右边i的一个临时变量。
养成良好习惯,先是声明UID
UID是在序列化的时候使用的,是在序列化过程中类的版本号,类在反序列化的时候会把磁盘中的序列化数据中保存的UID和要反序列化的java类中的UID做对比 如果不一致则说明类的版本不一致,也就是类发生过修改。如果默认不声明UID,java会自动添加一个,规则是类名,字段名,方法名的一个复杂计算,总之只要你改过类,UID肯定会发生改变。
break万万不可忘
这个好像是废话,但是还是在提一嘴吧。
避免Instanceof非预期结果
instanceof只能比较对象,不能比较基本类型。
用偶判断不用奇判断
意思是在判断奇数偶数的时候,应该判断不是偶数的为奇数,而不要用不是奇数的是偶数判断。 例如i%2==1?"奇数”:“偶数”,这里如果i是负数,是不正确的。
用整数型类处理货币
当项目中处理货币问题时,应该把货币扩大100直接用整数处理,因为浮点数有不确定性。
不要让类型默默转换
例如用光速计算太阳到地球的距离时,long i = 30100001000608;乍看没问题,可是i值确实负数,应为表达式右边在计算的时候已经超出了整型的范围。应该这样:long i = 30L100001000608
边界问题
和上一个类似,有一个判断:i+600<2000;本意是i的值不能大于1400,可是如果我们让i+600的值刚好超出int整型范围的话,i+600就是一个负数,i就是一个接近整型范围的数,如果是银行系统的话这个bug就会让银行倾家荡产
不要让四舍五入亏了一方
四舍五入默认情况下,如果平均计算的话入的一方会比舍掉的一方多一些。货币方面一定要注意。RoundingMode提供了一些四舍五入的规则可以选择。
谨防包装类型的比较
基本类型在比较时可以直接用==比较,对象比较时需要用equals比较,但是很多时候系统中定义的是Integer,Long等一些包装类型时会忽略,直接用==比较造成bug.大于小于的比较也是一样。
优先使用整型池
虚拟机在启动时会自动初始化-128~127的整型变量,char类型和在此范围内的整型或者Integer对象都是同一个。
优先选择基本类型
例如public void f(long l){} 和public void f(Long l),虚拟机在选择时会优选选择基本类型的方法。如果f(123),会调用f(long l)方法,而不是f(Long l)方法
不要随便设置随即种子
需要random类设置的随机种子相同,那么生成的随机数序列就相同,不论是不是同一对象。
静态变量要先声明,后赋值。
例如,类中的静态变量会先分配内存空间,然后会根据类中的静态代码块和静态变量的先后循序依次执行。
不要重写父类中的静态方法。
如果子类中声明了和父类相同的静态方法,那么在调用时虚拟机会根据实际new出来的对象来调用静态方法,就算new出的对象是赋值给父类也是一样。
构造函数尽量简化
例如代码:
public class Client {
public static void main(String[] args) {
Server s = new SimpleServer(1000);
}
}
//定义一个服务
abstract class Server{
public final static int DEFAULT_PORT = 40000;
public Server(){
//获得子类提供的端口号
int port = getPort();
System.out.println("端口号:" + port);
/*进行监听动作*/
}
//由子类提供端口号,并做可用性检查
protected abstract int getPort();
}
class SimpleServer extends Server{
private int port=100;
//初始化传递一个端口号
public SimpleServer(int _port){
port = _port;
}
//检查端口号是否有效,无效则使用默认端口,这里使用随机数模拟
@Override
protected int getPort() {
return Math.random() > 0.5?port:DEFAULT_PORT;
}
}
输出结果不是0就是4000就是不是100。问题在于,子类初始化时会先初始化父类,这时候子类中的port还没有赋值还是0,然而父类的静态变量已经初始化完成(静态变量的初始化在构造方法之前)
不要在构造函数中初始化其他类
public class Client {
public static void main(String[] args) {
Son s = new Son();
s.doSomething();
}
}
//父类
class Father{
Father(){
new Other();
}
}
//子类
class Son extends Father{
public void doSomething(){
System.out.println("Hi,show me something");
}
}
//相关类
class Other{
public Other(){
new Son();
}
}
这里只是一个简单的例子,真正大型的项目中,问题不是那么容易发现的。为了给自己以后少找麻烦还是尽量避免的好。