-
使用数据库连接池和线程池
这两个池都是用于重用对象的,前者可以避免频繁地打开和关闭连接,后者可以避免频繁地创建和销毁线程。
-
使用带缓冲的输入输出流进行IO操作
带缓冲的输入输出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,这可以极大地提升IO效率。
-
顺序插入和随机访问比较多的场景使用ArrayList,元素删除和中间插入比较多的场景使用LinkedList
这个需要理解ArrayList和LinkedList的原理。
-
避免让public方法中有太多的形参
public方法即对外提供的方法,如果这些方法有太多形参主要有两点坏处:
-
违反面向对象的编程思想,Java讲究一切都是对象,太多形参和面向对象的编程思想不契合
-
参数太多势必导致方法调用的出错率增加
参数最好控制在两个以内,比如我们用jdbc写一个insertStudent方法,有10个学生信息字段需要插入Student表中,可以把这10个参数封装在一个实体类中,作为insertStudent方法的形参。
-
-
字符串变量和字符串常量equals的时候将字符串常量写在前面
这个技巧比较常见,例如:
String str = "123"; if (str.equals("123")) { code... }
建议修改为:
String str = "123"; if ("123".equals(str)) { code... }
这么做主要可以避免空指针异常。
-
在java中if (i == 1)和if (1 == i)是没有区别的,但从阅读习惯上讲,建议使用前者
-
不要对数组使用toString()方法
看一下对数组使用toString()打印出来的是什么:
public static void main(String[] args) { int[] arr = new int[] {1, 2, 3}; System.out.println(arr.toString()); }
结果是:
[I@18a992f
本意是想打印出数组内容,但却可能因为数组引用arr为空而导致空指针异常。不过虽然对数组使用toString()方法没有意义,但是对集合使用该方法可以打印出集合里面的内容的,因为集合的父类AbstractCollections<E>重写了Object的toString()方法。
-
不要对超出范围的基本数据类型做向下强制转型
例如一下代码不会得到想要的结果:
public static void main(String[] args) { long l = 12345678901234L; int i = (int) l; System.out.println(i); }
我们可能期望得到其中的某几位,但结果却是:
1942892530
解释一下,在Java中long型数据是8个字节64位,所以12345678901234在计算机中的表示应该是:
0000 0000 0000 0000 0000 1011 0011 1010 0111 0011 1100 1110 0010 1111 1111 0010
一个int型数据时4个字节32位,从低位取出上面这串二进制数据的前32位是:
0111 0011 1100 1110 0010 1111 1111 0010
这串二进制表示为十进制1942892530,也就是我们上面控制台的输出内容。
注:整型默认的数据类型为int,浮点型默认的数据类型为double。
-
公用的集合类中不使用的数据要及时remove掉
-
把一个基本数据类型转为字符串,基本数据类型.toString()是最快的方式,String.valueOf(数据)次之,数据+""最慢
-
String.valueOf()方法底层调用了Integer.toString()方法,但在调用前会做空判断
-
Integer.toString()方法直接调用
-
数据 + ""的底层使用了StringBuilder实现,先用append()方法进行拼接,再用toString()方法获取字符串
-
-
使用最有效率的方式去遍历Map
遍历Map的方式有很多,通常情景下我们需要的是遍历Map中的Key和value,以下推荐使用的效率最高的方式:
public static void main(String[] args) { HashMap<String, String> map = new HashMap<String, String>(); map.put("111", "222"); Set<Map.Entry<String, String>> entrySet = map.entrySet(); Iterator<Map.Entry<String, String>> iterator = entrySet.iterator(); while (iterator.hasNext()) { Map.Entry<String, String> entry = iterator.next(); Sysout.out.println(entry.getKey() + "\t" + entry.getValue()); } }
如果只是遍历这个Map的key值,则用"Set<String> keySet = map.keySet();"会比较合适。
-
对资源的close()建议分开操作
比如有如下代码:
try { XXX.close(); YYY.close(); } catch (Exception e) { code... }
建议修改为:
try { XXX.close(); } catch (Exception e) { code... } try { YYY.close(); } catch (Exception e) { code... }
虽然麻烦点,却能避免资源泄露,试想,如果用没有修改过的代码,万一XXX.close()抛异常了,那么就进入catch块中了,YYY.close()则不会执行,YYY这块资源就不会回收了,一直占用着,这样的代码一多,可能引起资源句柄泄漏。而改为修改过的代码,就保证了无论如何XXX和YYY都会被close掉。
-
切忌以常量定义的方式替代魔鬼数字,魔鬼数字的存在将极大地降低代码可读性,字符串常量是否使用常量定义可以视情况而定
-
long或者Long初始赋值时,使用大写的L而不是小写的l,因为字母l极易与数字1混淆,这个细节需要注意
-
所有重写的方法必须保留@Override注解
这么做有三点原因:
-
清楚地知道这个方法由父类继承而来
-
getObject()和get0bject()方法,前者第四个字母是"O",后者第四个字母是"0",加了@Override注解可以马上判断是否重写成功
-
在抽象类中对方法签名进行修改,实现类会马上报出编译错误
-
-
推荐使用JDK7中新引入的Objects工具类来进行对象的equals比较,直接a.equals(b)有空指针异常的风险
-
循环体内不要使用“+”进行字符串的拼接,而直接使用StringBuilder不断append
每次虚拟机碰到“+”这个操作符对字符串进行拼接的时候,会new出一个StringBuilder,然后调用append方法,最后调用toString方法转换为字符串,也就是循环多少次,就会new出多少个StringBuilder来,这对于内存来讲是一种浪费。
-
不捕获Java类库中定义的继承自RuntimeException的运行时异常类
异常处理效率较低,RuntimeException的运行时异常,其中绝大多数完全可由程序员来规避,比如:
-
ArithmeticException 可通过判断除数是否为空来规避
-
NullPointerException 可通过判断对象是否为空来规避
-
IndexOutOfBoundsException 可通过判断数组/字符串长度来规避
-
ClassCastException 可通过instanceof关键字来规避
-
ConcurrentModificationException 可使用迭代器来规避
-
-
避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降,JDK7之后可以使用ThreadLocalRandom来获取随机数
-
静态类、单例类、工厂类将它们的构造函数置为private
这是因为静态类、单例类、工厂类这种类本来就不需要我们从外部将它们new出来,将构造函数置为private之后,保证了这些类不会产生实例。
-
对于TreadLocal使用前或者使用后一定要先remove
线程池技术做的是一个线程重用,意味着,在代码运行过程中,一条线程使用完毕,并不会销毁而是等待下一次的使用,在Thread类中,持有ThreadLocal.ThreadLocalMap的引用,线程不销毁意味着上条线程set的ThreadLocal.ThreadLocalMap中的数据依然存在,那么在下一条线程重用这个Thread的时候,很可能get到的是上条线程set的数据而不是自己想要的内容。
Java 代码优化(下)
最新推荐文章于 2024-04-03 18:10:13 发布