- 不要在常量和变量中出现易容易混淆的字母
注意:字母“l”作为长整形标志时务必大写。
- 莫让常量蜕变成变量
注意:务必让常量的值在运行器保持不变。
- 三元操作符的类型务必一致。
三元操作符类型的转换规则:
1、若两个操作数不可转换,则不做转换,返回值为Object类型。
2、若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
3、若两个操作数中有一个是数字S,另外一个是表达式,且其类型标示为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S类型。
4、若两个操作数都是直接量数字(Literal),则返回值类型为范围较大者。
- 避免带有变长参数的方法重载
int是一个原生数据类型,而数组本身是一个对象,编译器想要“偷懒”,于是他会从最简单的开始“猜想”,只要符合编译条件的即可通过。
- 别让null值和控制威胁到变长方法
- 覆写变长方法也循规蹈矩
覆写必须满足的条件:
1、重写方法不能缩小访问权限。
2、参数列表必须与被重写方法相同。
3、返回类型必须与被重写方法的相同或是其子类。
4、重写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少、更有限的异常。或者不抛出异常。
注意:覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式。
- 警惕自增的陷阱
count++是一个表达式,是有返回值的,他的返回值就是count自加前的值,Java对自加是这样处理的:首先把count的值(注意是值,不是引用)拷贝到一个临时变量区,然后对count变量加1,最后返回临时变量区的值。
- 不要让旧语法困扰你
- 少用静态导入
对于静态导入,一定要遵循两个规则:
1、不使用*(星号)通配符,除非是导入静态常量类(只包含常量的类或接口)。
2、方法名是具有明确、清晰表象意义的工具类。
- 不要在本类中覆盖静态导入的变量和方法
编译器有一个“最短路径”原则:如果能够在本类中查找到的变量、常量、方法,就不会到其他包或父类、接口中查找,以确保本类中的属性、方法优先。
- 养成良好习惯,显示声明UID
注意:显示声明serialVersionUID可以避免对象不一致,但尽量不要以这种方式向JVM“撒谎”。
- 避免用序列化类在构造函数中为不变量赋值
final属性是一个直接量,在反序列化时就会重新计算。
反序列化的基本规则:
1、保持新旧对象的final变量相同,有利于代码业务逻辑统一。
2、反序列化时构造函数不会执行。
反序列化的执行过程是这样的:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息(在序列化时,保存到磁盘的对象文件中包含了类描述信息,注意,是类描述信息,不是类)查看,发现是final变量,需要重新计算。
注意:在序列化类中,不使用构造函数为final变量赋值。
- 避免为final变量复杂赋值
保存到磁盘上(或网络传输)的对象文件包括两部分:
1、类描述信息
包括包路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值,以及变量的关联类信息。要注意的一点是,他并不是class文件的翻版,他不记录方法、构造函数、static变量等的具体体现。之所以类描述会被保存、很简单,是因为能去也能回,这保证反序列化的健壮运行。
2、非瞬态(transient关键字)和非静态(static关键字)的示例变量值
注意,这里的值如果是一个基本类型,好说,就是一个简单值保存下来:如果是复杂对象,也简单,连该对象和关联类信息一起保存,并且持续递归下去(关联类也必须实现Serializable接口,否则会出现序列化异常),也就是说递归到最后,其实还是基本数据类型的保存。
反序列化时final变量在以下情况下不会重新赋值:
1、通过构造函数为final变量赋值。
2、通过方法返回值为final变量赋值。
3、final修饰的属性不是基本类型。
- 使用序列化类的私有方法巧妙解决部分属性持久化问题
序列化独有的机制:序列化回调。Java调用ObjectOutputStream类把一个对象转换成流数据时,会通过反射(Reflection)检查被序列化的类是否有writeObject方法,并且检查其是否符合私有、无返回值的特性。若有,则会委托该方法进行对象序列化,若没有,则由ObjectOutputStream按照默认规则继续序列化。同样,在从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,如果有,则会通过该方法读取属性值。此处有几个关键点要说明:
1、out.defaultWriteObject()
告知JVM按照默认规则写入对象,惯例的写法是写在第一句话里。
2、in.defaultReadObject()
告知JVM按照默认规则读入对象,惯例的写法也是写在第一句话里。
3、out.writeXX和in.readXX
分别是写入和读出相应的值,类似一个队列,先进先出,如果此处有复杂的数据逻辑,建议按封装Collection对象处理。
- break万万不可忘
最简单的解决办法:修改IDE的警告级别,例如在Eclipse中,可以依次点击PerFormaces→Java→Compiler→Errors/Warnings→Potential Programming problems,然后修改'switch' case fall-through为Errors级别,如果你胆敢不在case语句中加入break,那Eclipse直接报个红叉给你看,这样就可以完全避免该问题的发生了。
- 易变业务使用脚本语言编写
- 慎用动态编译
在使用动态编译时,需要注意以下几点:
1、在框架中谨慎使用
2、不要在要求高性能的项目使用
3、动态编译要考虑安全问题
4、记录动态编译过程
- 避免instanceof非预期结果
- 断言绝不是鸡肋
assert有以下两个特性:
1、默认是不启动的
2、抛出的异常AssertionError是继承自Error的
以下两种情况不可使用:
1、在对外公开的方法中
2、在执行逻辑代码的情况下
按照正常执行逻辑不可能到达的代码区域可以放置assert。具体分为三种情况:
1、在私有方法中放置assert作为输入参数的校验
2、流程控制中不可能达到的区域
3、建立程序探针
- 不要只替换一个类
对于final修饰的基本类型和String类型,编译器会认为他是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行器引用(Run-time Reference),以提高代码的效率。
对于final修饰的类(即非基本类型),编译器认为他是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final),如果CLient类引入的常量是一个类或实例,即使不重新编译也会输出最新值。
注意:发布应用系统时禁止使用类文件替换方式,整体WAR包发布才是万全之策。