命名规范
1.类名(含接口名)是一个名词,采用大小写混合的方式,每个单词的首字母大写,尽量使类名简洁而富于描述。类名使用完整单词,避免缩写,除非该缩写词广泛使用,如URL、DAO、VO等
2.类名中建议体现类的类型或功能分类,建议接口命名使用I开头或者Interface结尾,抽象类命名使用Abstract或者Base开头,异常类命名使用Exception结尾,测试类以Test结尾,如果使用到了设计模式建议在类名中体现
3.方法名(除构造函数外)采用动词,大小写混合,第一个单词首字母小写,其后单词首字母大写
格式规范
1.关键字出现顺序:public,protected,private,abstract,final,transient,volatile,synchronized,native,strctfp
2.缩进采用四个空格,不使用tab字符
3.使用空行将逻辑相关的代码分隔开
下列情况应该总是使用两个空行:
(1)一个源文件的两个片段之间;
(2)类声明和接口声明之间;
下列情况应该总是使用一个空行:
(1)两个方法之间;
(2)方法内的局部变量和方法的第一条与句之间;
(3)块注释或单行注释之前;
(4)一个方法内的两个逻辑段之间;
注释规范
1.类、接口、方法、属性声明之前增加文档注释 (/* * …* /) ,其中类注释主要描述该类的主要功能和用途,方法注释主要描述该方法做了什么工作而不是怎么实现的
2.在注释中使用TODO来表示某些待实现的内容,使用XXX来标识某些待商榷的但可以工作的内容,用FIXME来表示某些错误、不能正常工作的但无法立即修复的内容。上述特殊注释标记应该注明标记人以及标记时间、处理时间
常量规范
1.禁止在长整型常量后出现小写的l,应该使用大写的L代替
2.如果可以使用Enum,就代替掉String常量以及int常量,因为这些常量通常不具备类型安全性,而且这些常量是编译时常量,如果该常量值发生变化,程序依旧能够继续运行,不过其行为可能已经不准确
正例:
public enum Apple{
FUJI,PIPPIN,GRANNY_SMITH
}
反例:
public staitc final int APPLE_FUJI = 0;
public staitc final int APPLE_PIPPIN= 1;
public staitc final int APPLE_GRANNY_SMITH= 2;
控制语句
1.每个switch语句都应该包含default分支且应该最后出现,每个case要么通过break或return等来终止,要么通过注释说明程序将继续执行到哪一个case为止
2.避免在循环中重复执行无需重复执行的相同内容,避免不必要的开销
正例:
void method(Vector vector){
int size = vector.size();
String pattern = getPattern();
for(int i = 0; i < size; i++){
// do
}
}
反例:
void method(Vector vector){
for(int i = 0; i < vector.size(); i++){
String pattern = getPattern();
}
}
3.慎用非短路逻辑运算符,因为它可能会造成空指针等异常情况。如下列情况,非短路逻辑运算符 & 左右两侧的表达式都会被计算,当字符串为null时右侧表达式将会出现空指针异常,但使用短路运算符 && 则不会出现如上情况。
正例:
if((s != null) && s.equals(...))
反例:
if((s != null) & s.equals(...))
面向对象
1.覆盖了equals方法,请务必覆盖hashCode方法。在Object类中定义了上述两个方法,equals方法按照内存地址比较对象是否相等,因此如果object1.equals(object2)为true,表明object1和object2实际上引用同一个对象,那么object1和object2的哈希码肯定也相同
2.避免使用过时的类或方法,如:**Thread.resume(),Threadd.stop()**等
3.Object的equals方法同意抛空指针异常,应使用常量或确定有值的对象来调用equals,都不确定有值时,应先进行空值判断
4.使用 == 或 != 与 null 比较,而不是equals
正例:
String nullStr = null;
if(nullStr == null){
// do
}
反例:
String nullStr = null;
if(nullStr.equals(null)){
// do
}
5.序列化类维护属性时,如果是兼容升级(如新增属性),不要修订serialVersionUID字段值,避免反序列失败;如果是不兼容升级(如修改属性名),务必修改serialVersionUID字段值,避免反序列化混乱
6.方法应该按功能,而非作用域或访问权限分组。当一个类有多个构造器,或者多个同名方法,这些方法应该按照顺序放置在一起,便于阅读
7.慎用Object的clone方法,默认的clone是浅拷贝,若想实现深拷贝需要重写clone方法
8.类变量访问控制应遵循如下原则:
(1)实例变量(非静态成员变量)与子类共享,必须是**protected**;
(2)实例变量(非静态成员变量)仅在本类使用,必须是**private**;
(3)类变量(静态成员变量)仅在本类使用,必须是**private**;
(4)类变量(静态成员变量)必须考虑是否声明为**final**
9.类方法访问控制应遵循如下原则:
(1)如果不允许外部直接通过**new**来创建对象,那么构造方法必须是**private**;
(2)工具类不允许有**public**或**default**构造方法;
(3)类成员方法只供类内部调用,必须是**private**;
(4)类成员方法只对继承类公开,限制为**protected**
数值计算
1.避免自动类型转换导致计算结果已出或精度丢失,如果需要精确地答案,避免使用float和double,可以使用BigDecimal、int、long进行货币等计算
正例:
// 将第一个运算数声明为long型,避免自动识别为int类型导致计算溢出
public static final long LARGE_NUMBER = 30L * 60 * 60 * 1000 * 1000;
int x = 4;
int y = 5;
// 计算结果为0.8
double z = ((double)x)/y
反例:
// 第一个运算数未声明为long型,int最大职位625817600,而计算值为108000000000,计算溢出
public static final long LARGE_NUMBER = 30 * 60 * 60 * 1000 * 1000;
int x = 4;
int y = 5;
// 计算结果为0,精度丢失
double z = x/y
2.避免高精度数据类型强转为低精度数据类型溢出
正例:
public static int double2Int(double d){
if(d >= Integer.MIN_VALUE && d <= Integer.MAX_VALUE){
return (int)d;
}else{
logger.info("{} out of integer bounds", d);
return 0;
}
反例:
public static int double2Int(double d){
return (int)d;
}
3.不要使用 BigDecimal(double) 构造器,应使用基于整数或String的构造器
正例:
BigDecimal d1 = new BigDecimal("0.1").add(new BigDecimal("0.1");
BigDecimal d2 = new BigDecimal("0.2");
if(d1.compareTo(d2) == 0){
// 可达
}
// 使用setScale指定小数位的精度以及舍入方式
BigDecimal d3 = new BigDecimal("3.14159").setScale(4, BigDecimal.ROUND_HALF_UP);
反例:
double d1 = 0.1 + 0.1;
double d2 = 0.2;
if(d1 == d2){
// 不可达
}
// 浮点型的.1的值等于".10000000000000000555111...."
BigDecimal d3 = new BigDecimal(.1);
集合规范
1.慎用ArrayList.subList方法,subList产生子集时,返回对象时ArrayList的内部类SubList,SubList是ArrayList的一个视图,对SubList子列表的操作也会反映到原列表上,原列表元素的个数的修改会导致SubList子列表在遍历、增加、删除时发生ConcurrentModificationException异常
2.慎用Arrays.asList方法,道理同上
3.不要在foreach循环内进行元素的remove或add操作,使用Iterator方式remove元素,如果并发操作,需要对Iterator对象加锁
正例:
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String item = iterator.next();
if(condition){
iterator.remove();
}
}
反例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
list.add("3");
for(Stirng item : list){
if("1".equals(item)){
list.remove(item);
}
}
4.使用entrySet遍历Map集合KV,而不是keySet,因为keySet需要遍历两次集合,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value,而entrySet之遍历一次,效率更高
5.集合类型可按以下规则选用:有序有重复对象用List,无序无重复对象用HashSet,有序无重复对象用TreeSet
并发处理
1.对于存在并发访问的单例对象(如Spring管理的Controller、DAO等),禁止使用有状态的成员变量,避免多线程并发写覆盖等问题
2.高并发时,同步调用应考量锁的性能损耗,优先使用无锁数据结构,必须使用锁时,加锁的代码块尽可能小,能锁区块就不要锁整个方法,能用对象所就不要用类锁
3.使用countDownLatch进行异步转同步操作,每个线程推出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行到await方法,直到超时才返回结果
4.在占用锁时,用wait替换sleep,避免造成性能不良和死锁
5.应尽量避免在synchronize方法中调用synchronize方法,容易造成死锁
异常处理
1.try-catch时,try块要尽可能的小,避免对不会出错的稳定代码进行try-catch,同时catch块不要直接捕获Exception或其他顶层异常,也不要捕获不会发生的异常
2.对于实现了AutoCloseable接口的类,如InputStream等类,建议使用try-with-resources代替try-catch-finally,可以在很大程度上简化代码,减低因为忘记显示关闭资源带来的隐患
正例:
try(InputStream in = new FileInputStream(src);OutputStream out = new FileOutputStream(dst)){
byte[] buf = new byte[BUFFER_SIZE];
int n;
while((n=in.read(buf)) > 0){
out.write(buf, 0, n);
}
}
反例:
InputStream in = null;
try{
in = new FileInputStream(src);
}catch(IOException e){
logger.err(e.getMessage());
}
try{
OutputStream out = new FileOutputStream(dst);
try{
byte[] buf = = new byte[BUFFER_SIZE];
int n;
while((n=in.read(buf)) > 0){
out.write(buf, 0, n);
}
}catch(IOException e){
logger.err(e.getMessage());
}finally{
try{
out.close();
}catch(IOException e){
logger.err(e.getMessage());
}
}
}catch(IOException e){
logger.err(e.getMessage());
}finally{
try{
in.close();
}catch(IOException e){
logger.err(e.getMessage());
}
}
3.在处理跨模块边界的异常时,不应将异常堆栈信息等技术细节直接抛出并展示,建议对异常提示信息做处理和转换,提供准确和友好的提示信息,避免不必要的信息暴露及异常信息过长
日志规范
1.不使用 System.out… 来打印日志,而使用通用的日志框架,如log4j
正例:
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
public class LogTest{
private static Logger logger = (Logger)LoggerFactory.getLogger(LogTest.class);
}
反例:
System.out.print("");
System.err.println("");
2.输出的日志信息,不应该包含敏感信息:用户信息、密码等,也应该减少不含业务信息的输出,有利于提升系统性能,便于快速定位错误
其他
1.方法入参的校验
下列情况应该进行参数校验:
(1)调用频次低的方法;
(2)批量调用入口方法;
(3)执行时间开销很大的方法;
(4)需要极高稳定性和可用性的方法;
(5)对外提供的开放接口;
(6)敏感权限入口;
下列情况不建议做参数校验:
(1)被循环调用的方法;
(2)底层调用频度比较高的方法;
(3)声明为**private**的,只会被本类调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题的,可以不校验
2.使用readLine方法时建议增加非空判断,避免空指针问题发生
3.当对A对象已经明确不再使用,但对象A又被另一个对象B所引用且对象B短期还不能释放时,可以将对象A的引用指向null,有助于对象A引用的其他对象被及时释放,避免出现内存溢出