您的位置:首页 > 编程语言 > Java开发

Java项目代码规范

2014-12-19 15:26 169 查看
做Java项目很多年了,虽然项目架构设计对以后的性能、扩展、维护很重要,但是最终的产品还是由每个程序员码砖堆出来的,每块砖都有问题,有时也会对整体造成负面影响,以至于维护困难。下面的一些总结是来自以前Java项目的开发规范以及过去看到的一些太随意的代码,还有一些是来自网络资源,希望在项目中对新手做开发前期的普及。Linus曾说“Talk is cheap,show me the code”,代码确实能看出一个人的基本功和编程习惯,让我们在java编程中尽可能做得更好一些。

1.尽量使用framework提供的Utility Class实现

在看项目的代码时,经常发现Integer.parseInt(str),str.substring(start,
end)等没有做检查的语句,这里存在潜在的风险,会经常导致运行时例外。如果开发者没有这个意识,自己在做UT时也会遗漏bug,更危险的是某些问题语句只在特例业务的数据才被发现。修改方式:

Integer.parseInt(str);-->org.apache.commons.lang.math.NumberUtils.toInt(str);

str.substring(start, end);-->org.apache.commons.lang.StringUtils.substring(str, start, end);

原则:优先使用struts和srping框架提供的Utils类:org.apache.commons.collections.CollectionUtils

建议:多看已有Utility Class的API,还有JDK的API,有些方法可能已经提供现有的了,我们直接使用就好了,毕竟被大家广泛使用过,安全性和性能都还行。比如CollectionUtils提供了一些交集、并集等方法。另外与业务相关的功能最好不要提供公开静态方法,容易增加耦合,使用时常会发现超出暴露此方法的初衷。

Util类误用:写好的Util类,全是和业务无关的一些公开静态方法,发现过居然被人new后使用,避免方法:在Util类中覆盖无参构造函数为private,在里面抛一个例外。

2.提高代码可读性

每个程序猿都应该写自己代码的Java doc注释。自己方便,与人方便。

方法名和参数都尽量使用意义明确的英语,另外命名不要太长。

内部结构不要多层嵌套。项目中也见过不少多层嵌套是人为造成的,阅读困难。举个反例(部分代码):

// 只有3个条件分支
if (a) {
// 大量代码
// 没有return
} else if (b) {
// 很少代码
return;
} else if (c) {
// 很少代码
return;
}
// ...后续处理
改为下面可读性更好:

if (b) {
// 很少代码
return;
}
if (c) {
// 很少代码
return;
}
//if (a) {
// 大量代码
// 没有return
//}
// ...后续处理


去掉else if,如果3条件必居其一,还可以去掉a条件的判断。尊重阅读的自然习惯,对于条件分支嵌套,最好把短的处理放在前面,另外理清业务逻辑关系,不写多余的判断分支。

3.避免创建不必要的java对象

尽量避免在经常调用的方法,循环中new对象,由于系统不仅要花费时间来创建对象,而且还要花时间对这些对象进行垃圾回收和处理,在我们可以控制的范围内,最大限度的重用对象。

在循环外new对象A,每次循环,把对象A里面的值用set方法对部分属性重新赋值,new对象A的开销如果比较大、每次循环体里改对象只有少数几个属性不同时这种方法能有所优化。

能用基本数据类型(String,int,long,double),就不用对象(Integer,Long,Double);永远不要用String str = new String("hello");此时程序除创建字符串外,str所引用的String对象底层还包含一个char[]数组,这个char[]数组依次存放了h,e,l,l,o。应改为String
str = "hello";这种方式会创建一个“hello”字符串,而且JVM的字符缓存池还会缓存这个字符串。

对于类中的某个经常调用的方法里,会用到不会被修改的可变对象B,可以把B改为类的private static final修饰的field,初始急切实例化或者第一次用到时延迟实例化来让对象B只生成一个实例。

在循环中避免自动装箱构造多个不必要的实例:

Long sum = 0L;// 应改为long sum = 0L;
for(long i = 0;i < Integer.MAX_VALUE;i++)
{
sum += i;// 会将变量i转换成Long对象实例
}


4.消除过期的对象引用

方法局部引用变量所引用的对象会随着方法结束而变成垃圾,因此,大部分时候程序无需将局部引用变量显式设为null。将对象设为null(Obj==null;)只是告诉jvm这个对象已经成为垃圾,至于什么时候回收是不确定的,还会造成可读性下降。

在我们自己设计数组型数据结构时,需要注意及时清除过期的对象引用。可以参照java.util.ArrayList的remove方法(把无用的引用清空):

elementData[--size] = null; // clear to let GC do its work

这里是让List对象里的过期引用尽早被GC回收,否则数组的该引用对象虽然被业务删除,单仍然被数据所引用无法被GC回收。

还有在设置监听器和回调方法调用时,可能造成已经不需要的引用被保持下来,导致GC没法回收。可用第三方工具来检测Java内存泄露问题。

5.永远不要使用finalize方法

实际上,将资源清理放在finalize方法中完成是非常不好的选择,JVM的规范并不保证何时执行该方法甚至不保证会被执行,有可能造成长时间资源得不到释放,可能会导致GC负担更大,程序运行效率更差。

对于GC回收不要在Java代码里干预,可根据实际状况改善JVM参数,比如修改新生代、年轻代、永久代的内存比例,修改GC回收方式及参数

6.声明变量时使用超类型(supper type),定义类方法优先考虑泛型

一个重要的OO原则:program to an interface, not an implementation(针对接口编程,不针对实现编程)。

在声明变量和定义接口参数时,使用超类型、泛型,能减少对实现的依赖,具有更大的灵活性,泛型还能在编译时进行类型的安全检查提前发现问题。举个简单的例子,接口方法参数的类型是java.util.Map,后来把调用此接口的传参从Hashtable实例改为ConcurrentHashMap后,具体实现里面的代码都不需要修改。

7.对不需要序列化的字段使用transient关键字

使用transient关键字修饰后,该变量的值不包括在序列化的表示中,一来可以让网络传输数据减少,还有就是避免不需要的或者敏感的信息泄露。

对于不需要RPC调用传递的、缓存的bean对象不要实现Serializable接口,每次修改bean对象后,如果有该bean有缓存要考虑清空缓存,否则有可能读取缓存时反序列化失败。

8.判断两个实例是否是同一个类

当需要精确判断两个对象是否同一个类型时,不要使用instanceof来判断,因为只要当前实例是目标类的子类时也会返回true;这时正确的判断应该是:

objA.getClass() == objB.getClass()

9.覆盖equals时同时覆盖hashCode方法

覆盖hashCode时要遵守如下规则:如果两个对象根据equals(Object)方法比较是相等的,它们的hashCode也应该是相等的,反之不相等则尽可能返回不相同的hashCode(产生的hashCode越均匀,有利于提高散列表的性能)。在关键字段上生成散列码可参照Long,String,Arrays等对象上的hashCode方法。

10.尽量在finally块中释放资源

程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。 一般是文件的关闭、连接资源占有以及锁的释放。

11.对数值运算注意事项

第一,要注意边界值,有符号整形数值的加减乘法都可能溢出,尤其是在累计求和及乘法中,要避免结果溢出。在做浮点数的比较时最好使用Float.compare方法。对精度要求高的应该使用BigDecimal。

第二,能用基本类型时,避免使用包装对象类型。

第三,优先考虑位运算。计算机的数值运算最终都是加法运算,如果在一些循环的关键计算能用位运算的话,能提高性能。位运算符包括: 与(&)、非(~)、或(|)、异或(^)、左移位(<<)、右移位(>>)、无符号移位(3个小于或者大于号)。不要滥用位运算,要权衡利弊;位运算难于理解,会牺牲可读性,使用时一定要写好注释说明。

遇到的一个应用就是用户类型判断,把各种用户类型设为short类型的1,2,4,8;组合起来一共有16种类型,用户增加或者失去一个用户角色时,直接增减/减去对应的值就好,判断一个用户是否属于某类型、某组合类型的用户也非常方便,做与运算即可,这里直接借鉴了Linux文件权限设置的做法。还有就是物品分类时ID设置,可用几个高位做大类的区分,拿ID直接和目标类做与运算的结果作判断。

12.字符串操作

在循环体里,不要使用+号来连接字符串,使用“+”来连接字符串时会生成很多StringBuilder对象。bean对象的toString方法里也不要使用“+”连接字符串,自己new一个StringBuilder至少能预估一个初始大小,在调用append方法时能减少重新分配内存的开销。另外也不要使用StringBuffer(线程安全但性能低下),因为一般是在线程封闭情况下new一个StringBuilder对象拼接完字符串,退出方法区后就被GC回收了。另外JDK6中String对象的substring()方法存在内存泄漏,请参照http://www.programcreek.com/2013/09/the-substring-method-in-jdk-6-and-jdk-7/;在Jdk7中修复了此问题,字符串本是不可变对象,substring()的初衷是提高性能,返回的子串与原字符串共享一个字符数组,起始下标和长度不一样,结果造成了原字符串不能被GC回收。

13.for-each循环

对于jdk1.5以后的版本,比起传统的while循环和带索引的for循环,要优先使用for-each循环,for-each循环更简洁。for-each循环依赖于该集合是否实现Iterable接口,Iterable的接口方法没有真正实现时(比如remove),在循环中删除集合中的元素或者有并发修改时,可能出现ConcurrentModificationException。

在使用带索引的for循环时,如果边界是不变的,要避免多次计算。如

for(int i=0;i<list.size();i++)

应该改为

for(int i=0,len=list.size();i<len;i++)

另外在循环的边界条件判断中应该避免使用复杂的表达式,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。 

14.尽量在声明时确定StringBuilder和集合对象的初始容量

StringBuilder 的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建 StringBuilder的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。 另外在new其它一些集合对象(HashMap、ArrayList)时,尽可能指定初始容量和加载因子,减少对存储空间的申请次数。

如:StringBuilder buffer = new StringBuilder(100);  

15.尽量用循环代替递归调用

递归调用自身能让代码简洁,但是递归调用的层次很深,会占用很多的栈空间时,最好能考虑使用循环代替。

16.尽可能让类对外暴露最少的接口

高内聚低耦合对于单个类来说就是使类和成员的可访问性最小,隐藏内部的数据及算法结构,这样最安全。

第一,尽量使用final修饰符,当整个类都是final时,只需要单实例就行,而且还是线程安全的;

第二,严禁提供public的类属性,应该提供相应的get方法;

第三,当类属性是通过其他属性计算得出时,该属性就不应该提供对应的set方法,在相应的get方法里可以使用缓存来避免每次都计算;

第四,该类是单实例时,不要有可变的类属性,否则会有线程安全问题,类的状态不可控;

第五,不要让类的静态属性为对象实例,除非该对象实例是不可变的也需要加上final修饰符;

第六,尽量使用局部变量,而不是增加类属性,局部变量还应该尽可能缩小作用范围,让GC尽快回收。

17.尽量使用System.arraycopy ()代替通过来循环复制数组

System.arraycopy() 要比通过循环来复制数组快的多 

18.尽量避免使用二维数组

二维数据占用的内存空间比一维数组多得多,大概10倍以上。

19.尽量避免使用String对象的split方法

除非是必须的,否则应该避免使用split,split由于支持正则表达式,所以效率比较低,如果是频繁的几十,几百万的调用将会耗费大量资源,如果确实需要频繁的调用split,可以考虑使用apache的StringUtils.split(string,char),频繁split的可以缓存结果。另外如果大量用正则表达式替换的话,String提供的replaceAll等方法也应该避免使用,可以去看replaceAll方法的源码,自己写个方法Pattern对象和Matcher对象都是可以重用的,replaceAll里都是每次调用都new了一遍

20.确定的常量集优先考虑枚举

对确定的常量集要优先考虑使用枚举类型,而不是在常量类里声明public static final类型的常量。在使用枚举时要避免使用ordinal序数,最好使用实际含义的私有值域与value对应,在构造方法里设置;因为ordinal序数是从0开始连续的,将来需要删除中间一个值时,会很不方便修改。

21.方法参数列表不要超过5个

如果一个方法的参数的列表超过5个,尤其还有相同类型的参数,很容易在调用时把参数传错,编译不会出错,运行时会出错,最坏的情况是运行时不出例外而是返回一个错误的结果。对于构造函数要求可变的参数列表时,可以考虑用Builder Pattern。

22.返回集合对象的方法,不要返回null

这是一个通用的约定,如果你提供一个返回null的方法会让使用者困扰,因为大家都习惯不会返回null,直接用size或者isEmpty方法判断结果集是否为空时就会出空指针。建议返回java.util.Collections的EMPTY_SET、EMPTY_LIST、EMPTY_MAP。

23.Bean对象要覆盖toString方法

方便出log时,打印出类似【类名@xxxx】的无用信息。

24.慎用sun.misc.Unsafe和reflect

一般情况下,不要用Unsafe和反射机制去生成操作对象实例。会带来可读性不好,性能损失,可能的安全问题。

25.慎用synchronized,尽量减小锁的粒度

同步能保证一致性和互斥性,需要较大的系统开销,不好的实现甚至会造成死锁。

同步的粒度应尽可能小,同步的代码块也应尽可能的短小和简单。最好能用无锁的数据结构代替。

另外volatile单独使用只能保证可见性,不能保证互斥修改。

26.单线程应尽量使用HashMap, ArrayList

ConrurrentHashMap和CopyOnWriteArrayList使用了同步机制,损耗了性能。CopyOnWriteArrayList的实现是在每次修改时,都做一份数组的拷贝副本,在副本上修改,所以在多线程下如果频繁的修改该类是不适当的,少量修改频繁遍历则是合适的。另外jkd1.5以后的代码不要再使用HashTable,Vector了。

27.ArrayList & LinkedList

一 个是数组,一个是链表,随机查询应尽量使用ArrayList,ArrayList优于LinkedList,LinkedList需要遍历指针找下一个;有频繁随机添加删除操作时LinkedList优于ArrayList,ArrayList还要移动数组大部分数据,对于只在数组末尾添加的话ArrayList会更快。

28.尽量缓存经常使用的对象

尽可能将经常使用的对象进行缓存,可以使用数组,或HashMap的容器来进行缓存,但这种方式可能导致系统占用过多的缓存,性能下降;推荐可以使用一些第三方的开源工具,如memcached,redis等进行缓存,避免频繁的数据库访问。

29.尽量避免非常大的内存分配

有时候问题不是由当时的堆状态造成的,而是因为分配失败造成的。分配的内存块都必须是连续的,而随着堆越来越满,找到较大的连续块越来越困难。

30.异常

当创建一个异常时,需要收集一个栈跟踪(stack track),这个栈跟踪用于描述异常是在何处创建的。构建这些栈跟踪时需要为运行时栈做一份快照,正是这一部分开销很大。当需要创建一个Exception 时,JVM 不得不说:先别动,我想就您现在的样子存一份快照,所以暂时停止入栈和出栈操作。栈跟踪不只包含运行时栈中的一两个元素,而是包含这个栈中的每一个元素。

如果您创建一个Exception,就得付出代价。好在捕获异常开销不大,因此可以使用 try-catch 将核心内容包起来。从技术上讲,您甚至可以随意地抛出异常,而不用花费很大的代价。招致性能损失的并不是throw 操作——尽管在没有预先创建异常的情况下就抛出异常是有点不寻常。真正要花代价的是创建异常。幸运的是,好的编程习惯已教会我们,不应该不管三七二十一就 抛出异常。异常是为异常的情况而设计的,使用时也应该牢记这一原则。

杜绝空的catch块,如果忽略该异常,至少应加上注释写明忽视的理由,最好还能打印一条log便于以后分析。在catch块里再创建一个Exception往上抛出时,不要使用默认的构造函数,应把当前异常的cause作为参数初始化,把出错源的堆栈信息传递到最上层,否则在解析时会很困难。

31.尽量在合适的场合使用单例

使用单例可以减轻加载的负担,缩短加载的时间,提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面

第一,控制资源的使用,通过线程同步来控制资源的并发访问

第二,控制实例的产生,以达到节约资源的目的

第三,控制数据共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信

32.JSP文件保持整洁

第一,jsp文件中不要出现<% %>的java代码,多用标签。

第二,尽量避免使用内联样式、文件样式,最好使用CSS外部样式,利于重用。

第三,JS代码最好写到单独的js脚本文件里,利用重用。 

第四,JSP里的注释,推荐用<%-- XXXX --%>,不用<!-- XXXX -->,后一种方式在浏览器终端通过查看源码能看到。

第五,行末不要有多余的空格,保持源文件和目标html文件干净,便于修改及提交前比较时美观。遇到过js一行末尾几千个空格,以为后面全是空格,但删除后又出错,比较旧版本发现行末居然有代码,只能猜测开发者碰到空格键无意输入了超多空格,避免方法:提交前扫一眼修改的地方,编辑器里是否出现很长的水平滚动条

33.局部代码保持一个好的结构

局部代码保持一个好的结构,比追求性能更重要。现代java虚拟机的运行速度已经有了很大改善,最大的瓶颈可能就在GC这了。好的结构便于以后扩展,维护,也更容易阅读,不易出错。推荐一本Java版的设计模式书《Head First 设计模式》。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java 开发规范