Ad-1:不要在变量和常量中出现容易混淆的数字和字母
1)包名称通常全小写;
2)多个单词构成的类名称,全首字母大写;
3)变量名称和函数名称的首单词首字母小写,后续单词的首字母大写;
4)常量全大写,单词之间用"_"连接;
5)long型整数赋值时,需要加字母"l"或"L",建议用L;
6)字母O与数字0 易混淆,建议常量和变量中不少使用,使用时增加注释;
Ad-2:莫让常量蜕变成变量
1)常量就是常量,是在编译的时候确定了的值,在运行期间不能发生变化;
2)常量定义会变的例子:
public static final int RAND_CONST = new Random().nextInt();
Ad-3:三元操作符的类型必须一致
1)三元操作符的操作数类型不一致的时候,会发生类型转换,可能true/false条件下,预期的结果类型不符合;
2)三元操作符的类型转换规则:
a)如果两个操作数不可转换,则不会转换,返回值为Object类型;
b)如果两个操作数是明确类型的表达式, 则按照正常的二进制数字转换,int类型转long类型,long类型转float类型;
c)如果连个操作数是直接数字量(常数),则返回值类型为范围较大者(注意是范围较大者,而不是值,java的数字类型是有符号的);
d)如果两个操作数,一个是数字,一个是表达式,则返回值类型是这两个类型表示的范围较大者;
总之,如果两个操作的范围之间存在包含关系(可以转换的),则返回范围较大者,否则返回Object类型;
Ad-4:避免带有变长参数的方法重载
1)变长参数介绍:
a)变长参数的使用目的是提高程序的灵活性;在Java5之前,使用容器类型的缺点是需要对空参数进行判断和筛选;
b)变长参数的必须是方法的最后一个参数,当然一个方法不可能有多个变长参数;
2) 变长参数的定义
在参数类型之后,加三个".";
3)变长参数的在方法中使用方式与数组相同;
2)变长参数在重载的时候使用注意编译器对调用方法的选择
例:public float calcPrice(int price, int discount);
public float calcPrice(int price, int... discount);
calcPrice(100, 80); //非变长参数
calcPrice(100, 90, 90); //变长参数
要点:Java编译器在编译的时候,首先根据实参的类型和个数查找最符合条件的,话虽如此,参数80也可以作为{80}一个元素的数组,
其次,Java编译器“偷懒”,当然从最简单的开始匹配,而且int 80 转换为 int[] {80}也是有成本的,因此java编译器聪明的选择了非变长参数的方法;
注意:代码给机器执行的,但是是给人看的,所以一定要人能看得懂,要避免晦涩易误解的代码。
Ad-5:别让null值和空值威胁到变长方法
例子:public void methodA(String str, Integer...is){ System.out.println("Integer");}
public void methodA(String str, String...strIs){ System.out.println("String");}
testFunc()
{
Integer intA = null;
String str = "str";
methodA(str, 0); //Integer
methodA(str, "People"); //String
methodA(str); //编译错误,方法模糊不清,编译器无法确定调用哪个函数
methodA(str, null); //编译错误,方法模糊不清,编译器无法确定调用哪个函数
methodA(str, intA); //Integer
}
Ad-6:覆盖变长方法也要循规蹈矩
1)子类覆盖父类的方法条件
a)重写方法不能缩小访问权限;
b)参数列表必须与被重写的方法相同;
c)返回类型必须与被重写的方法返回类型相同,或者是其子类型;
d)重写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少、范围更小的异常,或者不抛异常;
e)注意变长参数在继承和覆盖的时候,可能出现参数的表现形式不同而导致编译失败;
Ad-7:警惕自增陷阱
int count = 0;
for (int i = 0; i < 10; i++)
{
count = count++;
}
运行结果:count=0
1)注意count++ 是一个表达式,是有返回值的,返回值是增加之前的值;
2)分析一下运行过程,i=0时
a)JVM将count=0拷贝到一个零时变量(零时工)中;
b)count值加1,即count=1;
c)count++表达式返回值时零时变量的值0;
d)将表达式的值0赋值给count,这样count变为0;
3)千万要注意在循环中,如果出现i=i++; i < K; 将出现死循环;
Ad-9:少用静态导入
1)静态导入语法(import static)目的是为了减少字符输入量,提高代码的可读性;
2)静态属性和静态方法缺少类来限定范围,易被无限放大;
3)静态导入的时候,慎用*号,避免把一个类的所有静态成员都导入;
4)静态导入规则:
a)不使用*(星号)通配符,除非导入的类是静态常量类;
b)方法名是具有明确、清晰表象意义的工具类;
Ad-10:不要在本类中覆盖静态导入的变量和方法
1)如果本地方法和变量与静态导入的方法和变量相同,不会导致编译失败;
2)编译器根据“最短路径”原则:如果能在本类中找到变量、常量、方法,就不会从包含的包或者父类、接口中查找,“地方保护主义”;
Ad-11:养成良好习惯,显示声明UID
1)类实现Serializable接口的目的是为了持久化(网络传输或者本地存储),为系统的分布和异构部署提供先决支持条件;
2)通常在网络传输中,发送方进行序列化,接受方进行反序列化;
3)在序列化和反序列化的类不一致的情形下,反序列化会报一个InvalidClassException异常, 原因是序列化和反序列化所对应的类版本发生变化,JVM不能
把数据流转换为实例对象。
4)JVM通过SerialVersionUID(也称:流标示符, Stream Unique Identifier),即类的版本定义,可以显示声明,也可以是隐式的;
5)显示声明SerialVersionUID方法:private static final long serialVersionUID = xxxxxL;
6)隐式则是编译器在编译的时候生成,根据包名、类名、继承关系、非私有方法和属性、以及参数、返回值等通过计算公式所得;
7)JVM在反序列化的时候,会比较数据流中的serialVersionUID和类中的serialVersionUID是否相同,如果不相同,则抛InvalidClassException异常;
否则认为类没有变化,根据数据流初始化实例对象;
8)通过显示声明serialVersionUID可以实现分布式或者升级前后类之间的兼容;
Ad-12:避免用序列化类在构造函数中为不变量赋值
Ad-13:避免位final变量复杂赋值
1)static静态变量不会被序化,final不变量被序列化;
2)final变量初始化方法有两种,一种是在定义的时候赋值,一种是在构造方法中赋值;
3)序列化的基本原则是序列化和反序列化的时候,类的数据保持一致;
4)final变量在反序列化的时候,如果类中的赋值方法是定义时简单表达式(基本类型)赋值,则会重新计算,
如果是复杂对象, 该对象和关联类信息一起保存,并且持续递归下去(复杂对象类也实现了Serializable接口,否则会出现序列化异常),则会重新计算;
如果定义时通过复杂赋值(函数调用返回值),则不会重新计算;
如果是通过构造方法赋值,则不会重新计算;
5)序列化保存的信息
a)类描述信息:包路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值、以及变量的关联类信息,但是不是class文件,不包括方法、构造方法、static变量;
b)非瞬态(transient关键字)和非静态(static关键字)的实例变量;
Ad-14:使用序列化类的私有方法巧妙解决部分属性持久化问题
1)在实现Serializable的类中实现两个私有方法,可以影响和控制序列化,反序列化的过程:
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
out.defaultWriteObject();
//out.writeInt()
}
private void readObejct(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException
{
in.defaultReadObject();
//var=in.readInt();
}