不要在常量和变量中出现易混淆的字母
包名全小写,类名首字母全大写,常量全部大写并用下划线分割,变量采用驼峰(Camel Case)命名。在变量的声明中不要引入容易混淆的字母:
long i=1l;
System.out.println("i的两倍是:"+(i+i));
如果字母和数字混合使用,字母“l”务必大写,字母“O”则增加注释。
注意,字母“l”作为长整型标志时务必大写。
莫让常量蜕变成变量
加了final和static的常量可能会变吗?
interface ConstDemo {
public static final int RAND_CONST = new Random().nextInt();
}
System.out.println("常量会变哦:" + ConstDemo.RAND_CONST);
常量就是常量,在编译器就必须确定其值,不应该在运行期更改,负责程序的可读性会非常差,甚至连作者自己都不能确定在运行 期发生了何种神奇的事情。
三元操作符的类型务必一致
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));
三元操作符类型的转换规则:
若两个操作数不可转换,则不做钻换,返回值为Object类型
若干两个操作数是明确类型额表达式(比如变量),则按正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
若两个操作数中有一个是数字S,另外一个是表达式,且其类型标识为T,那么,若数字S在T的范围内,则转换为T类型;若S超出T类型的范围,则T转换为S类型
若两个操作数都是直接量数字,则返回值类型为范围较大者。
注意保证三元操作符中两个操作数类型一致,即可减少错误的发生。
避免带有变长参数的方法重载
public void calPrice(int price, int discount) {
float knockdownPrice = price * discount / 100.0F;
System.out.println("简单折扣后的价格是:" + formateCurrency(knockdownPrice));
}
public void calPrice(int price, int... discounts) {
float knockdownPrice = price;
for (int discount : discounts) {
knockdownPrice = knockdownPrice * discount / 100;
}
System.out.println("复杂折扣后的价格是:" + formateCurrency(knockdownPrice));
}
private String formateCurrency(float price) {
return NumberFormat.getCurrencyInstance().format(price / 100);
}
Java在编译时,首先会根据实参的数量和类型来进行处理,也就是查到calPrice(int price,int discount)方法,而且确认它是否符合方法签名条件。
int是一个原生数据类型,而数组本身是一个对象,编译器想要“偷懒”,所以会这样选择。
慎用变长参数的方法重载,让人伤脑经不说,苏红补丁哪天就陷入这类小陷阱里了。
别让null值和空值威胁到变长方法
public class Client {
public void methodA(String str, Integer... is) {
}
public void methodA(String str, String... strs) {
}
public static void main(String[] args) {
Client client = new Client();
client.methodA("China", 0);
client.methodA("China", "People");
client.methodA("China", null);
}
}
public static void main(String[] args){
Client client = new Client();
String[] strs = null;
client.methodA("China",strs);
}
让编译器知道这个null值是String类型的,编译即可顺利通过,也就减少了错误的发生。
覆写变长方法也循规蹈矩
在Java中,子类覆写符类的方法很常见,这样做既可以修正Bug也可以提供扩展的业务功能支持,同时还符合开闭原则(Open-Closed Principle)。覆写必须满足的条件:
重写方法不能缩小访问权限。
参数列表必须与被重写方法相同。
返回类型必须与重写方法的相同或者是其子类。
重写方法不能抛出新的异常,或者超出符类范围的异常,但是可以抛出更少,更有限的异常,或者不抛出异常。
public class Client {
public static void main(String[] args) {
Base base = new Sub();
base.fun(100, 50);
Sub sub = new Sub();
sub.fun(100, 50);
}
}
class Base {
void fun(int price, int... discounts) {
System.out.println("Base.....fun");
}
}
class Sub extends Base {
@Override
void fun(int price, int[] discounts) {
System.out.println("Sub.....fun");
}
}
警惕自增的陷阱
int count = 0;
for (int i = 0; i < 10; i++) {
count = count++;
}
System.out.println("count=" + count);
结果是0
count++是一个表达式,是由返回值的,它的返回值就是count自加前的值,Java对自加是这样处理的:首先把count的值(注意是值,不是引用)拷贝到一个临时变量区,然后对count变量加1,最后返回临时变量区的值。程序第一次玄幻时的详细处理步骤如下:
1.JVM把count的值(其值是0)拷贝到临时变量区
2.count值加1,这时候count的值是1
3.返回临时变量区的值,注意这个值是0,没修改过。
4.返回值赋值给count,此时count值被重置成0.
不要让旧语法困扰你
Java中没有了goto关键字,但是扩展了break和continue关键字。
少用静态导入
静态导入语法(import static)其目的是为了减少字符串输入量,提高代码的可阅读性,以便更好的理解程序。但是,滥用静态导入回是程序更难于都,更难维护。会让阅读者很难弄清除其属性或方法代表何意,甚至是哪一个类的属性(方法)都要思考一番。(IDE友好提示是另说)
对于静态导入,一定要遵循两个原则:
1.不使用*(星号)通配符,除非是导入静态常量类(只包含常量的类或接口)。
2.方法名是具有明确,清晰表象意义的工具类
不要在本类中覆盖静态导入的变量和方法
编译器有一个“最短路径”原则:如果能在奔雷中找到的变量,常量,方法,就不会到其他包或者符类,接口中查找,以确保奔雷中的属性,方法优先。
如果要变更一个被导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。
养成良好习惯,显式声明UID
类实现Serializable接口目的是为了可持久化,比如网络传输或本地存储,为系统的分布和异构部署提供先决支持条件。若没有序列化,现在我们熟悉的远程调用,对象数据库都不可能存在。
通过SerialVersionUID,也叫做流标识符,即类的版本定义的,它可以显式声明也可以隐式声明。显式声明格式如下:
private static final long serialVersionUID = XXXXXL;
而隐式声明是我不声明,你编译器在编译的时候帮我生成。
JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有发生改变;如果不相同,JVM不干了,抛个异常InvalidClassException。
显式声明serialVersionUID可以提高代码的健壮性。
~~## 避免用序列化类在构造函数中为不变量赋值
避免为final变量复杂赋值
使用序列化类的私有方法巧妙解决部分属性持久化问题~~
break万万不可忘
case后缺少break往往会称为bug所在。
可以再IDE中做相应的设置,将switch语句中,每个case不以break结尾的情况设置为编译错误。
易变业务使用脚本语言编写
例如我们项目中的公式。
慎用动态编译
动态编译一致是java的梦想,从Java6版本它开始支持动态编译了,可以在运行期直接编译.java文件,执行.class,并且能够获得相关的输入输出,甚至还能监听相关的事件。
使用动态编译时需要注意以下几点:
1.在框架中谨慎使用
2.不要再要求高性能的项目使用
3.动态编译要考虑安全性问题
4.记录动态编译过程。(空中编译和运行是很让人不放心的,留下些一句可以更好地优化程序)
避免instanceof非预期结果
public class Client {
public static void main(String[] args) {
//String对象是否是Object的实例
boolean b1 = "Sting" instanceof Object;
//String对象是否是String的实例
boolean b2 = new String() instanceof String;
//Object对象是否是String的实例
boolean b3 = new Object() instanceof String;
//拆箱类型是否是装箱类型的实例
boolean b4 = 'A' instanceof Character;
//空对象是否是String的实例
boolean b5 = null instanceof String;
// 类型转换后的空对象是否是String的实例
boolean b6 = (String) null instanceof String;
// Date对象是否是String的实例
boolean b7 = new Date() instanceof String;
// 在泛型类中判断String对象是否是Date的实例
boolean b8 = new GenericClass<String>().isDateInstance("");
}
}
class GenericClass<T> {
//判断是否是Date类型
public boolean isDateInstance(T t) {
return t instanceof Date;
}
}
'A' instanceof String无法编译通过,因为'A'是一个char类型,也就是一个基本类型,不是一个对象,instanceof只能用于对象的判断,不能用于基本类型的判断。
不要只替换一个类
对于final修饰的基本类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值变异到字节码中了,编码了在运行期引用,以提高代码的执行效率。
对于final修饰的类(即非基本类型,编译器认为它是不稳定态(Mutable Status)),在编译时简历的则是引用关系,如果Client类引入的常量是一个类或实例,即使不重新编译也会输出最新值。
注意:发布应用系统时禁止使用类文件替换方式,整个WAR包或者JAR包发布才是万全之策。