如何让你的代码更优雅?
俗话说:不积跬步无以至千里,不积小流无以成江河。如果说考虑的工期等因素,代码能按期正常无 bug 运行上线就算项目完成,如果想让项目运行的更流程,用户体验更好,必要的代码细节还是需要仔细考虑的,更优雅的代码不但可以让你的系统运行更稳定流畅,同时也更方便后续之人的维护,一举多得,那么如何让你的代码更优雅呢?
如何让你的代码更优雅
尽量重用对象
特别是 String 对象的使用,出现字符串连接时应该使用 StringBuilder/StringBuffer 代替。由于 Java 虚拟机不仅要花时间生成对象,以后可能还需要花时间对这些对象进行垃圾回收和处理,因此,生成过多的对象将会给程序的性能带来很大的影响。
尽可能使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快,其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,栈中创建的变量,随着方法的运行结束,这些内容就没了,不需要额外的垃圾回收。
及时关闭流
Java 编程过程中,进行数据库连接、I/O 流操作时务必小心,在使用完毕后,及时关闭以释放资源。因为对这些大对象的操作会造成系统大的开销,稍有不慎,将会导致严重的后果。
尽量减少对变量的重复计算
明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。
这样的话每一次遍历都需要进行一次 dataList.size()操作,可以更换为:
尽量采用懒加载的策略,即在需要的时候才创建
可以替换成:
这样操作的话就是等真的需要用到这个参数了再加载获取,如果用不到的话就获取,其实是没有意义的。
慎用异常
异常对性能不利。抛出异常首先要创建一个新的对象,
Throwable 接口的构造函数调用名为 fillInStackTrace() 的本地同步方法,
fillInStackTrace() 方法检查堆栈,收集调用跟踪信息。
只要有异常被抛出,Java 虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。
异常只能用于错误处理,不应该用来控制程序流程。
不要在循环中使用 try…catch…,应该把其放在最外层
如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度
比如 ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet 等等,以 StringBuilder 为例:
StringBuilder() // 默认分配 16 个字符的空间,(父类 AbstractStringBuilder)
StringBuilder(int size) // 默认分配 size 个字符的空间
StringBuilder(String str) // 默认分配 16 个字符+str.length()个字符空间
可以通过类(这里指的不仅仅是上面的 StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如 StringBuilder 吧,length 表示当前的 StringBuilder 能保持的字符数量。因为当 StringBuilder 达到最大容量的时候,它会将自身容量增加到当前的 2 倍再加 2,
无论何时只要 StringBuilder 达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放 5000 个字符而不指定长度,最接近 5000 的 2 次幂是 4096,每次扩容加的 2 不管,那么:
在 4096 的基础上,再申请 8194 个大小的字符数组,加起来相当于一次申请了 12290 个大小的字符数组,如果一开始能指定 5000 个大小的字符数组,就节省了一倍以上的空间;把原来的 4096 个字符拷贝到新的的字符数组中去。
这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像 HashMap 这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个 table 上只连接一个对象的可能性几乎为 0。初始大小建议设置为 2 的 N 次幂,如果能估计到有 2000 个元素,设置成 new HashMap(128)、new HashMap(256) 都可以。
当复制大量数据时
使用 System.arraycopy()命令(目前遇到的业务上未用到大量复制数据操作)
乘法和除法使用移位操作
执行结果:
循环内不要不断创建对象引用
这样做的话会导致内存中有 size 份对象的引用,当 size 很大的时候,就比较耗费内存。
可以替换为:
这样改之后的话内存中只有一份对象引用,每次 new 的时候,只是对象引用指向不同的对象,但是内存中只有一份,这样也就节省了内存空间。
尽量使用
尽量使用 HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用 Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销。
尽量避免随意使用静态变量
要知道,当某个对象被定义为 static 的变量所引用,那么 GC 通常是不会回收这个对象所占有的堆内存的。
对资源的 close()建议分开操作
可以修改为:
这样做的益处是如果 in.close();执行时发生异常那么也不影响 out.close();的执行,第一种的话如果 in.close 发生异常,则 out.close 将永远不会执行,就会一直占用资源。
使用最有效率的方式去遍历 Map
遍历 Map 的方式很多,通常场景下我们需要获取 map 的 key 和 value 值,推荐的方式如下:
如果只是想遍历一下 map 的 key 值,那么可以用 map.keySet();获取 key 值集合后遍历取值。
基本数据类型转为字符串
obj.toString()是最快的方式、String.valueOf(obj)次之、obj+“”最慢,下面我们来看一下为什么这么说,首先看 String.valueOf(obj):
可以看到 String.valueOf(obj)内部还是 obj.toString(),同时增加了 null 判断;
obj+“”,底层使用了 StringBuilder 实现,先用 append() 方法拼接,再用 toString() 方法获取字符串。
公用的集合类中不使用的数据一定要及时 remove 掉
如果一个集合类是公用的(也就是说不是方法里面的属性),那么这个集合里面的元素是不会自动释放的,因为始终有引用指向它们。所以,如果公用集合里面的某些数据不使用而不去 remove 掉它们,那么将会造成这个公用集合不断增大,使得系统有内存泄露的隐患。
不要对超出范围的基本数据类型做向下强制转型
代码如下
执行结果
强转之后得到的结果并不是我们想要的结果,这是因为 a 值已经超出 int 的范围。
不要对数组使用 toString()方法
对数组用 toString 方法没有意义
想要获取具体某个值时可以通过 System.out.println(a[0]);角标获取。
字符串变量和字符串常量 equals 的时候将字符串常量写在前面
以上就是本次整理的让你的代码变得更优雅的开发时需要注意的地方,希望对大家有帮助哈
版权声明: 本文为 InfoQ 作者【六月的雨在infoQ】的原创文章。
原文链接:【http://xie.infoq.cn/article/7df1a2ac08922d5c4739e7427】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论