读完Effective Java之后始感觉这本书中的知识多是一些零碎的小知识,这些小知识主要是围绕着类库设计,设计模式,api导出,升级,维护等方面的小tips。就像书中的一个一个小标题一个个的罗列出来一样,
我也如法炮制,一条条的列出来我读完本书之后消化了的一些tips, 和大家一起分享吧。谢谢。
1.在基本类型包装成对应包装类的时候尽量使用包装类上的静态工厂方法,而不要采用new关键字
究其原因, 是因为静态工厂方法会做一些优化处理。比如说Integer上默认会缓存-128-127的整数,Boolean类型会返回内部缓存的Boolean.TRUE或者Boolean.FALSE实例,而这两个实例都是妥妥的单例。这也正是利用了享元的思想,避免创建很多的对象,以提升程序运行速度。就像redis内部会默认缓存整数值一样。
下图为Integer.valueOf和Boolean.valueOf静态方法实现的主要源代码:
2。慎用可变参数。在设计api方法的时候,如果参数个数不确定,需要将api设计成携带可变参数列表的方法的话, 建议提供不同参数个数的重载,避免客户端调用到带有可变参数列表的api,因为可变参数列表,在方法调用的时候会在内部生成一个持有参数的数组,这样会降低性能。不说别的,我们的jdk类库中就有这种操作,比如说EnumSet。
从源码中可以看出,of方法被重载了6次,前5次提供了一个参数的api,两个参数的api,三个参数的api,四个参数的api,五个参数的api,只有最后一个api才是用到了可变参数列表的,所以经过上述重载之后,携带可变参数列表的api被调用到的概率就会小很多,以此保证了性能。
3.如果有使用到List,Map,Set,Quenue,Stack这样的数据结构和容器的时候,建议使用泛型,而且不推荐混用继承了统一上层类的对象,因为如果混用的话,会导致一些莫名其妙的错误,而调用者却浑然不知。比如
由于Timestamp继承自Date所以两者可以放在同一个容器里,而后remove(Object o)方法在遍历Element e的时候 是用 o.equal(e)来判等的,而Date.equal是不能区分出来e是不是Date子类的对象的,所以就导致了错误。
4.组合优于继承。就比如说上一点提到的java.sql.Timestamp, jdk在实现这个类的时候,采用了继承Date的方式, 所以就导致Timestap.equal(Date d) 和 Date.equal(Timestamp t)再难具有自反性。而如果采用
public class Timestamp{
private Date d;
private int nanos;
}
这种组合一个Date的实现方式,有选择的导出api, 就不会出现上述问题。
5.基本类型优于包装类。
由反编译得到的jvm指令集可知,在7处进入循环,循环当中调用了Integer上的实例方法intValue完成拆箱,之后调用iadd指令完成自增,再然后又调用了Integer上的valueOf完成自动装箱,生成了一个新的Integer对象。(之所以发生可这种行为, 是因为Integer被设计成了一个不可变类。)由于创建对象是一个耗时的操作,所以这段代码的性能相比较基本类型而言差距明显。
如图可见在循环一万次的情况下,使用Integer自增和使用int自增效率上是差一个数量级的。
6.lambda优先于匿名类。如果可以使用lambda就不要使用匿名类。因为匿名类每调用一次就创建一次该匿名类的对象。 而lambda采用invokeDynamic指令优化,内部会生成一个CallSite动态调用点持有MethodHandle,而lamdba生成的实例是缓存在MethodHandle上的, 所以lamdba不会生成多余的对象, 多次调用速度更快。
7.尽量少用反射,因为反射性能,要比直接调用要差,如果不得已需要调用反射api, 且确定clazz的上层类,且只调用上层类的api就足够满足使用场景了。可以如下优化。
A a = (A)clazz.newInstance();
a.methodA();
而不是
Method m = clazz.getDeclareMethod("methodA",new Class[0])
ReflectionUtils.makeAccessible(m);
m.invoke(clazz.newInstance());
8.采用私有或者包级内部类封装底层实现,如果一个功能不需要暴露具体实现,且随着版本推移,具体实现需要修改, 就可以采用内部类的形式。这种方式在jdk的代码库里能找到的例子就很多了。
比如说 Arrays.asList返回的其实是自己内部实现的一个只能读的ArrayList, 以及ArrayList下的Itr实现的迭代器,以及Collections下的诸多Unmodifiable开头的内部类一样。
9.try-with-resource优先于try-finally try-with-resource会确保资源关闭
10.for-each优先于传统for循环 这个可以自己反编译验证下 默认情况下 如果是数组这样的协变得数据结构 for-each会转化成for(int i=0;i