The reasonable man adapts himself to the world; the unreasonable one persists in trying to adapt the world to himself.
明白事理的人使自己适应世界;不明事理的人想让世界适应自己。
——萧伯纳
字母“l”作为长整型标志时务必大写,字母“O”则增加注释。
务必让常量的值在运行期保持不变。
三元操作符类型的转换规则:
1、若两个操作数不可转换,则不做转换,返回值为Object类型。
2、若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换,int类型转换为long类型,long类型转换为float类型等。
3、若两个操作数中有一个是数字S,另外一个是表达式,且其类型标示为T,那么,若数字S在T的范围内,则转换为T类型;若S超出了T类型的范围,则T转换为S类型。
4、若两个操作数都是直接量数字,则返回值类型为范围较大者。
编译器想要“偷懒”,它会从最简单的开始“猜想”,只要符合编译条件的即可通过。
覆写(重写)必须满足的条件,子类重写父类的方法:
1、重写方法不能缩小访问权限。
2、参数列表必须与被重写方法相同(参数数量相同、类型相同、顺序相同)。
3、返回类型必须与被重写方法的相同或是其子类。
4、重写方法不能抛出新的异常,或者超出父类范围的异常,但是可以抛出更少、更有限的异常,或者不抛出异常。
覆写的方法参数与父类相同,不仅仅是类型、数量,还包括显示形式。
count=count++中count++是一个表达式,是有返回值的,其处理步骤是:
步骤1 JVM把count初始值拷贝到临时变量区。
步骤2 count值加1,这时候count的值是加1之后的值。
步骤3返回临时变量区的值,注意这个只是初始值,没有修改过。
步骤4返回值赋值给count,此时count值被重置成0。
静态导入要遵循的两个原则:
1、不使用*(星号)通配符,除非是导入静态常量类(只包含常量的类或接口)。
2、方法名是具有明确、清晰表象意义的工具类。
编译器“最短原则”:如果能够在本类中查找到的变量、常量、方法,就不会到其他包或父类、接口中查找,以确保本类中的属性、方法优先。因此,如果要变更一个被静态导入的方法,最好的办法是在原始类中重构,而不是在本类中覆盖。
JVM在反序列化时,会比较数据流中的serialVersionUID与类的serialVersionUID是否相同,如果相同,则认为类没有发生变化,可以把数据流load为实例对象;如果不相同,抛出异常InvalidClassException。
显示声明serialVersionUID可以避免对象不一致,但尽量不要以这种方式向JVM“撒谎”。
反序列化的执行过程:JVM从数据流中获取一个Object对象,然后根据数据流中的类文件描述信息(在序列化时,保存到磁盘的对象文件中包含了类描述信息,注意是类描述信息,不是类)查看,发现是final变量,需要重新计算,于是引用Person类中的name值,而此时JVM又发现name竟然没有赋值,不能引用,于是它很“聪明”地不再初始化,保持原值状态,所以结果就是“混世魔王”了。
在序列化类中,不使用构造函数为final变量赋值。
序列化时,保存到磁盘上(或网络传输)的对象文件包括两部分:
1、类描述信息
包括包路径、继承关系、访问权限、变量描述、变量访问权限、方法签名、返回值,以及变量的关联类信息。不记录方法、构造函数、static变量等的具体实现。
2、非瞬态(transient关键字)和非静态(static关键字)的实例变量值
变量值如果是一个基本类型,就是一个简单值保存下来;如果是复杂对象,连该对象和关联类信息一起保存,并且持续递归下去(关联类也必须事先Serializable接口,否则会出现序列化异常),递归到最后,还是基础数据类型的保存。
反序列化时final变量在以下情况下不会被重新赋值:
通过构造函数为final变量赋值。
通过方法返回值为final变量赋值。
final修饰的属性不是基本类型。
在序列化某个类后,想要反序列化这个类时,但是该类中的某些成员变量不可以在反序列化后显示,实现有五个方案:
1、在不需要现实的成员变量前面加上transient关键字
加上transient关键字的类成员变量会失去分布式部署的功能。分布式部署不可能。
2、新增业务对象
重新设计一个类。增加工作量,不是最优方法。
3、请求端过滤
请求端系统获取类对象后,过滤掉不要的成员变量。设计严重失职。
4、变更传输契约
改用XML传输,或者重建一个Web Service服务。成本高。
5、序列化的类中增加私有方法
在序列化的类中增加writeObject和readObject两个方法,并且访问权限都是私有级别。
序列化独有的机制:序列化回调。Java调用ObjectOutputStream类把一个对象转换成流数据时,会通过反射(Reflection)检查被序列化的类是否有writeObject方法,并且检查其是否符合私有、无返回值的特性,若有,则会委托该方法进行对象序列化,若没有,则有ObjectOutputStream按照默认规则继续序列化。同样,在从流数据恢复成实例对象时,也会检查是否有一个私有的readObject方法,如果有,则会通过该方法读取属性值。此处有几个关键点要说明:
1、out.defaultWriteObject()
告知JVM按照默认的规则写入对象,惯例的写法是写在第一句话里。
2、in.defaultReadObject()
告知JVM按照默认的规则读入对象,惯例的写法也是写在第一句话里。
3、out.writeXX和in.readXX
分别是写入和读出相应的值,类似一个队列,先进先出,如果此处又复杂的数据逻辑,建议按封装Collection对象处理。
按照正常执行逻辑不可能到达的代码区域可以放置assert。具体分三种情况:
1、在私有方法中防止assert作为输入参数的校验
2、流程控制中不可能到达的区域
3、建立程序探针
对于final修饰的基本类型和String类型,编译器会认为它是稳定态(Immutable Status),所以在编译时就直接把值编译到字节码中了,避免了在运行期引用(Run-time Reference),以提高代码的执行效率。
当我们引用一个常量类里面的基本类型或String类型的数据时候,第一次引用之后,修改常量类中的该值重新编译该常量类,而不重新编译引用类,此时该类中引用的数据还是修改之前的数据。
对于final修饰的类(即非基本类型),编译器认为它是不稳定态(Mutable Status),在编译时建立的则是引用关系(该类型也叫做Soft Final),如果Client类引入的常量是一个类或实例,即使不重新编译也会输出最新值。
发布应用系统时禁止使用类文件替换方式,整体WAR包发布再是万全之策。