JAVA 四十条代码优化建议

张开发
2026/4/19 0:02:13 15 分钟阅读
JAVA 四十条代码优化建议
前言代码优化最重要的作用应该是避免未知的错误因此在写代码的时候从源头开始注意各种细节权衡并使用最优的选择将会很大程度上避免出现未知的错误从长远看也极大的降低了工作量。所以说代码优化的目标是减小代码体积、提高代码运行效率。优化是无止境的本文也只是给出了一些常见的优化建议。1. 尽量指定类、方法的final修饰符带有final修饰符的类是不可派生的。在Java核心API中有许多应用final的例子例如java.lang.String整个类都是final的。为类指定final修饰符可以让类不可以被继承为方法指定final修饰符可以让方法不可以被重写。如果指定了一个类为final则该类所有的方法都是final的。Java编译器会寻找机会内联所有的final方法内联对于提升Java运行效率作用重大具体可以查阅Java运行期优化相关资料此举能够使性能平均提高50%。2. 尽量重用对象特别是String对象的使用出现字符串连接时应该使用StringBuilder/StringBuffer代替。由于Java虚拟机不仅要花时间生成对象以后可能还需要花时间对这些对象进行垃圾回收和处理因此生成过多的对象将会给程序的性能带来很大的影响。3. 尽可能使用局部变量调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中速度较快其他变量如静态变量、实例变量等都在堆中创建速度较慢。另外栈中创建的变量随着方法的运行结束这些内容就没了不需要额外的垃圾回收。4. 及时关闭流Java编程过程中进行数据库连接、IO流操作时务必小心在使用完毕后及时关闭以释放资源。因此对这些大对象的操作会造成系统大的开销稍有不慎将会导致严重的后果。5.//性能不好list.size()会重复调用for(int i 0; i list.size(); i) {...}//建议替换为如下for(int i 0, length list.size(); i length; i) {...}//如上写法在list.size()很大的时候就减少了很多的消耗。6. 尽量采用懒加载的策略即在需要的时候才创建这个原则其实就是节约具体样例如下。//不好的示范String str aaa;if(i 1) {list.add(str);}//建议替换为如下if(i 1) {String str aaa;list.add(str);}7. 慎用异常异常对性能不利抛出异常首先要创建一个新的对象Throwable接口的构造函数调用名为filllnStackTrace()的本地同步方法filllnStackTrace()方法检查堆栈收集调用跟踪信息。只要有异常被抛出Java虚拟机就必须调整调用堆栈因为在处理过程中创建了一个新的对象。异常只要用于错误处理不应该用来控制程序流程。8. 当复制大量数据时使用System.arraycopy()命令这个肯定大家没有疑问的性能优化的实现而已。9. 乘法和除法使用移位操作。用移位操作可以极大地提高性能因为在计算机底层对位的操作是最方便、最快的但是移位操作虽然快 可能会使代码不太好理解因此更好加上相应的注释。//不好的示范for(val 0; val 100000; val 5) {a val * 8;b val / 2;}//建议修改实现for(val 0; val 100000; val 5) {a val 3;b val 1;}10. 循环内不要不断创建引用//不好的示范for(int i 1; i count; i) {Object obj new Object();}//上面这种做法会导致内存中有count份Object对象引用存在//count很大的话就耗费内存了建议为如下实现。Object obj null;for(int i 0; i count; i) {obj new Object();}//如上实现内存中只有一份Object对象引用//每次new Object()的时候Object对象引用指向不同的Object罢了//但是内存中只有一份这样就大大节省了内存空间了。11. 基于效率和类型检查的考虑应该尽可能使用array无法确定数组大小时才使用ArrayList。12. 尽量使用HashMap、ArrayList、StringBuilder除非线程安全需要否则不推荐使用Hashtable、Vector、StringBuffer后三者由于使用同步机制而导致了性能开销。13. 不要将数组声明为public static final。因为这毫无意义这样只是定义了引用为static final数组的内容还是可以随意改变的将数组声明为public更是一个安全漏洞这意味着这个数组可以被外部类所改变。14. 尽量在合适的场合使用单例。使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率但并不是所有地方都适用于单例简单来说单例主要适用于以下三个方面控制资源的使用通过线程同步来控制资源的并发访问控制实例的产生以达到节约资源的目的控制数据的共享在不建立直接关联的条件下让多个不相关的进程或线程之间实现通信15. 尽量避免随意使用静态变量因为当某个对象被定义为static的变量所引用那么gc通常是不会回收这个对象所占有的堆内存的。public class A {private static B b new B();}//此时静态变量b的生命周期与A类相同//如果A类不被卸载那么引用B指向的B对象会常驻内存直到程序终止。16. 及时清除不再需要的会话。为了清楚不再活动的会话许多应用服务器都有默认的会话超时时间一般为30分钟。当应用服务器需要保存更多的会话时如果内存不足那么操作系统会把部分数据转移到磁盘应用服务器也可能根据MRU(最近最频繁使用)算法把部分不活跃的会话转储到磁盘甚至可能抛出内存不足的异常。如果会话要被转储到磁盘那么必须要先被序列化在大规模集群中对对象进行序列号的代价是很昂贵的。因此当会话不再需要时应当及时调用HttpSession的invalidate()方式清楚会话。17. 实现RandomAccess接口的集合(比如ArrayList)应当使用最普通的for循环而不是foreach循环来遍历。这是JDK推荐给用户的JDK API对于RandomAccess接口的解释是实现RandomAccess接口用来表明其支持快速随机访问此接口的主要目的是允许一般的算法更改其行为从而将其应用到随机或连续访问列表时能提供良好的性能。实际经验表明实现RandomAccess接口的类实例假如是随机访问的使用普通for循环效率将高于使用foreach循环反过来如果是顺序访问的则使用Iterator会效率更高。//样板代码可以使用类似如下的代码作判断if(list instanceof RandomAccess) {for(int i 0; i list.size(); i) {}} else {Iterator? iterator list.iterable();while(iterator.hasNext()) {iterator.next()}}18. 使用同步代码块替代同步方法尽量使用同步代码块避免对那些不需要进行同步的代码也进行了同步影响了代码执行效率。19. 将常量声明为static final并以大写命名。这样在编译期间就可以把这些内容放入常量池中避免运行期间计算生成常量的值。另外将常量的名字以大写命令也可以方便区分出常量与变量。20. 不要创建一些不使用的对象不要导入一些不使用的类。这毫无意义如果代码中出现The value of the lical variable i is not used、“The import java.util is never used”那么请删除这些无用的内容虽说没啥影响但是有些时候编译期会报错譬如没import用到的类被删掉了。21. 程序运行过程中避免使用反射不建议在程序运行过程中使用除非万不得已尤其是频繁使用反射机制特别是Method的invoke方法如果确实有必要一种建议性的做法是将那些需要通过反射加载的类在项目启动的时候通过反射实例化出一个对象并放入内存用户只关心和对端交互的时候获取最快的响应速度并不关心对端的项目启动花多久时间。22. 使用数据库连接池和线程池这两个池都是用于重用对象的前者可以避免频繁的打开和关闭连接后者可以避免频繁的创建和销毁线程。23. 使用带缓冲的输入输出流进行IO操作带缓冲的输入输出流即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream这可以极大地提升IO效率。24. 顺序插入和随机访问比较多的场景使用ArrayList元素删除和中间插入比较多的场景使用LinkedList。25. 不要让public方法中有太多的形参。public方法即对外提供的方法如果给这些方法太多形参的话主要坏处是违反了面向对象的编程思想Java讲求一切都是对象太多的形参和面向对象的编程思想并不契合参数太多势必导致方法调用的出错概率增加。26. 字符串变量和字符串常量equals的时候将字符串常量写在前面 这样可以避免空指针。27. 建议使用if(i 1)而不是if(1 i)的方式因为有可能 会误写成 而在 C/C 中 if (i 1) 是会出问题的而 Java 会在编译时报错 “Type mismatch: cannot convert from int to boolean”但是尽管Java的 if (i 1) 和 if (1 i) 在语义上没有任何区别从阅读习惯上讲建议使用前者会更好些。28. 不要对数组使用toString()方法本意是想打印出数组内容却打出来的是对象信息甚至有可能因为数组引用为空而导致空指针异常。对于集合toString()是可以打印出集合里面的内容的因为集合的父类AbstractCollections重写了Object的toString()方法。29. 不要对超出范围的基本数据类型做向下强制转型这很明确譬如long转int是会存在潜在风险的。30. 公用的集合类中不使用的数据一定要及时remove掉如果一个集合类是公用的(也就是说不是方法里面的属性)那么这个集合里面的元素是不会自动释放的因为始终有引用指向它们。所有如果公用集合里面的某些数据不使用而不去remove掉它们那么将会造成这个公用集合不断增大使得系统有内存泄漏的隐患。31.把一个基本数据类型转为字符串基本数据类型.toString()是最快的方式、String.valueOf(数据)次之、数据最慢。因为String.valueOf()方法底层调用了Integer.toString()方法但是会在调用前做空判断Integer.toString()是直接调用了i底层使用StringBuilder实现先用append方法拼接再用toString()方法获取字符串。32. 使用最有效率的方式去遍历Map遍历Map的方式有很多通常场景下我们需要的是遍历Map中的Key和Value那么推荐使用的、效率最高的方式是entrySet()如果只是想遍历一下这个Map的key值则keySet()会比较合适一些。33. 对资源的close()建议分开操作虽然有些麻烦却能避免资源泄漏这其实和try-catch机制相关各自分开close各自的try-catch就会互不影响防止写在一个try-catch中因为一个异常了后面的释放不了。34. 对于ThreadLocal在线程池场景使用前或者使用后一定要先remove因为线程池技术做的是一个线程重用这意味着代码运行过程中一条线程使用完毕并不会被销毁而是等待下一次的使用而Thread类中持有ThreadLocal.ThreadLocalMap的引用线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在那么在下一条线程重用这个Thread的时候很可能get到的是上条线程set的数据而不是自己想要的内容。这个问题非常隐晦一旦出现这个原因导致的错误没有相关经验或者没有扎实的基础非常难发现这个问题因此在写代码的时候就要注意这一点这将给你后续减少很多的工作量。35. 切记以常量定义的方式替代魔鬼数字魔鬼数字的存在极大的降低代码可读性字符串常量是否使用常量定义可以视情况而定。36. long或者Long初始赋值时使用大写的L而不是小写的l因为小写l及易与数字1混合这点非常细节需要注意37. 所有重写的方法必须保留Override注解这么做可以清楚地知道这个方法由父类继承而来同时可以保证重写成功此外在抽象类中对方法签名进行修改实现类会马上报出编译错误。38. 推荐使用JDK7中新引入地Objects工具类来进行对象地equals比较直接a.equals(b)有空指针异常地风险。39. 循环体内不要使用进行字符串拼接而直接使用StringBuilder不断append因为每次虚拟机碰到这个操作符对字符串进行拼接地时候会new出一个StringBuilder然后调用append方法最后调用toString()方法转换字符串赋值给对象所以循环多少次就会new出多少个StringBuileder()来这对于内存是一种浪费。40. 不捕获Java类库中定义地继承自RuntimeException的运行时异常类异常处理效率低RuntimeException的运行时异常中绝大多数完全可以由程序员来规避比如ArithmeticException可以通过判断除数是否为空来规避NullPointerException可以通过判断对象是否为空来规避IndexOutOfBoundsException可以通过判断数组/字符串长度来规避ClassCastException可以通过instanceof关键字来规避ConcurrentModificationException可以使用迭代器来规避41. 静态类、单例类、工厂类将它们的构造函数置为private这是因为静态类、单例类、工厂类这种类本来我们就不需要外部将它们new出来将构造函数置为private之后保证了这些类不会产生实例对象。

更多文章