读改善java程序的151个建议(1)

来源:互联网 发布:街篮手游官网巨人网络 编辑:程序博客网 时间:2024/05/21 20:28
1.不要在常量和变量中出现容易混淆的字母
   例如:L 的小写l  所以字母l作为长整型时务必大写

2.务必要让常量的值在运行期间保持不变
     interface Const{
     //这还是常量吗?
     public static final int RAND_CONST=new Random().nextInt();
     }

3.三元操作符的类型务必保持一致
public class Client{
public static void main(Stirng[] args){
     int i=80;
     String s=String.valueOf(i<100?90:100);
     String s1=String.valueOf(i<100?90:100.0);
     System.out.println("两者是否相等:"+s.equals(s1));
}
}
结果是false
这里涉及到三元操作符类型的转换规则:
若两个操作数不可转换,则不做转换,返回值为Object类型。若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。若两个操作数中有一个是数字S,另外一个是表达式,且类型标识为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S型;若两个操作数都是直接量数字,则返回值类型为范围较大者。
保证三元操作符中的两个操作数数类型一致,即可减少可能错误的发生。

4.避免带有变长参数的方法重载
java5及之后引入变长参数(vargs),变长参数也要遵循一定规则,比如,变长参数必须是方法中的最后一个参数;一个方法不能定义多个变长参数等
例如:
public void calPrice(int price,int discount){.....}
public void calPrice(int price,int...discounts){.....}
重载,并且第二个方法的参数范畴覆盖了第一个方法的参数范畴,那问题就来了,对于calPrice(49900,75)这样的计算到底是调用哪一个方法来处理呢? 这个75即可以编译成int类型的75,也可以编译成int数组{75},即只包含一个元素的数组。那到底该调用哪一个方法呢?结果是调用第一个方法,为什么?因为java在编译时,首先会根据参数 的数量和类型(这里是2个实参,都为int类型,注意没有转成int数组)来进行处理,也就是查找到第一个方法,而且确认它是否符合方法签名条件。那为什么会先根据2个int类型的实参而不是1个int类型、1个int数组类型的实参来查找方法呢?因为int是一个原生数据类型,而数组本身是一个对象,编译器想要“偷赖”,于是它会从最简单的开始“猜想”,只要符合编译条件即可通过。
对于calPrice(49900,75,79)这种,因为只有第二个方法符合方法签名,很显然就是调用第二个了

5.别让null或空值威胁到变长方法
有时候需要指定null的具体类型

6.覆写变长方法也循规蹈矩
覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式

7.警惕自增的陷阱
int count=0;
for(;;){
cout=count++
}
count永远是0

8.不要让旧语法困扰你
goto 让它随风而去吧

9.少用静态导入
对于静态导入,一定要遵行2个规则:不使用*(星号)通配符,除非是导入静态的常量类(只包含常量的类或接口);方法名是具有明确、清晰表象意义的工具类

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

11.养成良好习惯,显示声明UID
我们编写一个实现Serializable接口(序列化标志接口)的类,Eclipse马上就会给一个黄色警告:需要增加一个Serial Version ID。为什么要增加?
主要是为了避免序例化与反序例化的类不一致的情表,例如,如果原来的类增加了一些属性,序例化经过网络转输到消费者好边,再返序例化时,会报一个InvalidClassException异常,原因是序例化和反序例会所对应的类版本发生了变化,jvm不能所数据流转换为实例对象。那jvm是根据什么来判断一个类版本呢?就是通过Serial VersionUID,也叫做流标识符(Stream Unique Identifier),即类的版本定义,它可以显示声明也可以隐式声明。显示声明格式如下:
private static final long serialVersionUID=XXXXXL;
隐式声明则由编译器生成(过程复杂,基本上算出来的这个值是唯一的)
JVM在反序列化的时候,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果则认为类没有发生改变,可以把数据流load为实例对象,反之则不可以。抛个InvalidClassException.
手工加上serialVersionUID有一个好处,在添加了新属性后,由于我们没有改变serialVersionUID的值,仍可以兼容旧的版本。

12.避免用序列化类在构造函数中为不变量(final)赋值
反序例化的另一个规则:反序例化时构造函数不会被执行。
例:
public class Person implements Serializable{
private static final serialVersionUID=3452345234L;
//不变量初始不赋值
public final String name;
//构造函数为不变量赋值
public Person(){
name="德天使"
}
}
反序例化的执行过程是这样的:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息(在序例化时,保存到磁盘的对象文件中包含了类描述信息)查看,发生是final变量,需要重新计算,于是引用Person类中的name值,而此时JVM又发现name竟然没有赋值,不能引用,于是它很“聪明”地不再初始化,保持原始值。

13.避免为final变量复杂赋值
总结起来,反序例化时,final变量在以下情况下,不会被重新赋值:
通过构造函数为final变量赋值;通过方法返回值为final变量赋值;final修饰的属性不是基本类型

14.使用序例化类的私有方法巧妙解决部份属性持久化问题
实现了Serializable接口的类可以实现两个私有方法:writeObject和readObject,以影响和控制反序例化的过程。
序例化独有的机制:序例化回计。Java通过ObjcetOutputStream类把一个对象转换成流数据时,会通过反射检查被序例化的类是否有writeObject方法,并且检查其是否符合私有、无返回值的特性,若有,则会委托该方法进行对象序例化,若没有,则由ObjectOutputStream按照默认规则继续序例化。同样,在从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,如果有,则会通过该方法读取属性值。此处有几个关键点要说明:
(1)out.defalutWriteObject();告知jvm按照默认的规则写入对象,惯例的写法是写在第一句话里。
(2)in.defaultReadObject(); 告知jvm按照默认的规则读入对象,惯例的写法也是写在第一句话里。
(3)out.writeXXX和in.readXXX ;分别是写入和读出相应的值。











0 0
原创粉丝点击