第一章 Java开发中通用的方法和准则
建议1:不要在常量和变量中出现易混淆的字母
字母“l”务必大写,字母“o”增加注释
建议2:莫让常量蜕变成变量
常量就是常量,在编译期必须确定其值,不应该在运行期更改,否则程序在运行期发生各种事情。
如:public static final int RAND_CONST = new Random().nextInt();
建议3:三元操作符的类型务必一致
类型不一致如:i < 100 : 90: 100.0(float)
建议4:避免带有变长参数的方法重载
变长参数,必须是方法的最后一个参数,一个方法不能定义多个变长参数。
避免变长参数重载,如下:
public void calPrice(int price, int discount)
public void calPrice(int price, int... discount)
建议5: 别让null值和空值威胁到变长方法
如:
public void method(String str, Integer... is)
public void method(String str, String... is)
client.method("China");
client.method("China", null);
正确做法:
String strs = null;
client.method("China", strs);
建议6:覆写变长方法也要遵循规矩
@Override必须满足如下条件:
从写方法不能缩小访问权限;
参数列表必须与被重写方法相同;(参数数量相同,类型相同,顺序相同)
返回类型必须与被重写的方法的相同或者是其子类;
重新方法不能抛新的异常,或者超出父类范围的异常,但是可以抛出更少,更有限的异常,或者不抛异常。
建议7:警惕自增的陷阱
错误语句,如:
int count = 0;
count= count++; //结果是count的初始值0
建议8:不要让旧语法困扰你
goto语句
break,continue后面可以加上冒号做跳转,实现goto功能。
建议9:少用静态导入
如: import static java.lang.Math.PI
建议10:不要在本类中覆盖静态导入的变量和方法
//常量名誉静态导入的PI相同
public final static String PI="祖冲之";
建议11:养成良好习惯,显式声明UID
序列化标志接口
Serial Version ID流标识符(类的版本定义),如果没有的话,序列化和反序列化所对应的类版本发生了变化,JVM不能把数据流转换为实例对象。
private static final long serialVersionUID = *****L;
隐式声明会根据类的各种属性,方法,关系,类名,包名,参数,返回值等计算得出。
建议12:避免用序列化类在构造函数中卫不变量赋值(final)
static序列化时候根本没有保存到数据流中
下面错误,如:
public class Person implements Serializable{
public final String name;
Person(){
name = "aa";
}
}
建议13:避免为final变量复杂赋值
final变量赋值还有一种方式,通过方法赋值。
public final String name = initName();
final会被重新赋值,是指简单对象,8个基本类型,数组,字符串(不通过new生成的String),但是不能方法赋值。
保存到磁盘(网络传输)上的对象文件包括2个部分:
类描述信息:包路径,继承关系,访问权限,变量描述,变量访问权限,方法签名,返回值,变量关联类信息。不记录方法,构造函数,static等。
transient,static的实例变量值
关联类也必须序列化
总结,反序列化时候final变量在以下情况下不会被重新赋值。
通过构造函数为final变量赋值
通过方法返回值为final变量赋值
final修饰的属性不是基本类型
建议14:使用序列化类的私有方法巧妙解决部分属性持久化问题
部分属性持久化,把不需要持久化的属性前面加上transient
姓名,基本工资可以序列化
绩效工资保密,不能序列化
实现了Serializable接口的类可以实现两个私有方法:writeObject和readObject。用来影响和控制序列化和反序列化的过程。
序列化的独有机制:序列化回调。Java调用ObjectOutputStream类把一个对象转换成流数据的时候,会通过反射检查被序列化的类是否有writeObject方法,并且检查其是否符合私有、无返回值的特征。如果有,则会委托该方法进行对象序列化。如没有,则由ObjectOutputStream按照默认规则继续序列化。readObject也一样。
import java.io.Serializable;
public class Salary implements Serializable {
private static final long serialVersionUID = -5402065593284392992L;
//基本工资
private int basePay ;
//绩效工资
private int bonus ;
Salary( int basePay , int bonus){
this.basePay = basePay ;
this.bonus = bonus ;
}
public int getBasePay() {
return basePay ;
}
public void setBasePay(int basePay) {
this.basePay = basePay ;
}
public int getBonus() {
return bonus ;
}
public void setBonus(int bonus) {
this.bonus = bonus ;
}
}
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Person implements Serializable{
/**
* Person serialVersionUID
*/
private static final long serialVersionUID = 7592930394427200495L;
//姓名
private String name ;
//薪水
private transient Salary salary ;
Person(String name, Salary salary){
this.name = name ;
this.salary = salary ;
}
public String getName() {
return name ;
}
public void setName(String name) {
this.name = name ;
}
public Salary getSalary() {
return salary ;
}
public void setSalary(Salary salary) {
this.salary = salary ;
}
//序列化委托方法
private void writeObject(ObjectOutputStream out) throws IOException{
out.defaultWriteObject();
out.writeInt( this.salary .getBasePay());
}
//反序列化委托方法
private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException{
in.defaultReadObject();
this.salary = new Salary(in.readInt(), 0);
}
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class SerializationUtils {
private static String FILE_NAME = "d:/a.bin";
//序列化
public static void writeObject(Serializable s) {
ObjectOutputStream oos;
try {
oos = new ObjectOutputStream(new FileOutputStream(FILE_NAME ));
oos.writeObject( s);
oos.close();
} catch (FileNotFoundException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static Object readObject(){
Object obj = null;
//反序列化
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(FILE_NAME ));
obj = input.readObject();
input.close();
} catch (IOException | ClassNotFoundException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return obj ;
}
}
//序列化Client
public class SerializeClient {
public static void main(String[] args) {
//基本工资1000,绩效工资25000
Salary salary = new Salary(1000, 25000);
//记录人员信息
Person person = new Person("张三" , salary );
SerializationUtils. writeObject(person);
}
}
//反序列化
public class DeSerializationClient {
public static void main(String[] args) {
//反序列化并打印信息
Person p = (Person)SerializationUtils. readObject();
StringBuffer sb = new StringBuffer();
sb.append( "姓名 : " + p .getName());
sb.append( "\t基本工资: " + p .getSalary().getBasePay());
sb.append( "\t绩效工资: " + p .getSalary().getBonus());
System. out.println(sb );
}
}
建议15:break万万不可忘
switch... case...
Performaces->Java->Compiler->Errors/Warnings->Potential Programming problems->'switch' case fall-through->Errors
建议16:易变业务使用脚本语言编写
脚步语言:灵活,便捷,简单。如Groovy
建议17:慎用动态编译
建议18:避免instanceof非预期结果
判断一个对象是否是一个类的实例
建议19:断言不是鸡肋
防御式编程中经常用断言Assertion对参数和环境做出判断,避免程序因不当的输入或错误的环境产生逻辑异常。断言是为调试程序而用。
assert默认是不启用的;assert抛出的异常AssertionErro是继承自Error。
防御式编程特点,所有外部因素都是邪恶的,存在企图摧毁程序的罪恶本源。为了抵制,要在程序中处处检验,设卡,不满足条件就不再执行后面的程序。保护主程序的正确性。
不能用断言在公开方法中做输入校验。
不能用断言在布尔表达式中执行逻辑代码
正确使用断言:按照正常执行逻辑不可能达到的代码区域放置断言
(1)私有方法中放置断言,作为输入参数的校验
(2)流程控制中不可能达到的区域,意义:程序执行到这里就是错误的。
(3)建立程序探针,断言变量之间的关系
建议20:不要只替换一个类
一个常量接口(类),编译器认为它是稳定态,所以编译时候直接把值编译到字节码中。以后无论怎么修改这个常量接口(类)里面的值,只要不重新编译,输出的还是原来的值。
发布应用系统时候,禁止使用类文件替换方式,整体WAR包发布是完全之策。
public class Constant{
public final static int MAX_AGE = 100;
}