6 避免创建不必要的对象
从字面意思上来看,大家肯定都知道创建不必要的对象是错误的做法。但这一节其实主要是提醒我们避免无意识的创建不必要对象的代码写法。
例1:
String s = new String("abc");
是错误的写法,正确的写法应该是:
String s = "abc";
原因是第一种写法每次被执行的时候都会创建一个新的String实例,但这些全都是重复的!
例2:
我们要优先使用静态工厂方法而不是构造器来避免创建不必要的对象,如Boolean.valueOf(String)总是要优先于构造器Boolean(String)使用。因为构造器每次被调用都会创建一个新对象,静态工厂不这样。
例3:
创建成本昂贵的对象时,应该将其缓存起来。
例如正则表达式匹配的代码中,String.matches方法内部创建了一个Pattern实例,这个创建的成本很高,因为需要将正则表达式编译成有限状态机,所以应该将其缓存起来:
public class RomanNumerals { private static final Pattern ID = Pattern.compile("^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$"); static boolean isRomanNumeral(String s){ return ID.matcher(s).matches(); } }
这样一来,每次调用isRomanNumeral时都会重用同一个ID实例
例4:
上面的Pattern实例是不变的,但在某些场景下实例是可变的,这时就可以考虑适配器。适配器是这样一个对象:它将功能委托给一个后备对象,为后备对象提供一个替代前面功能的接口。
例如Map接口的KeySet方法,每次调用返回的都是同一个Set实例,虽然Set实例是可变的,但其中一个变化时其他的也会跟着变,因为他们本身就是一个。
例5:
优先使用基本类型而不是装箱类型,原因在于下面这个例子:
private static long sum(){ Long sum = 0L; for(long i = 0; i <= Integer.MAX_VALUE; i ++) sum += i; return sum; }
这段程序执行起来没有任何问题,但实际情况会慢一点,因为sum的类型是Long而不是long,所以程序构造了大约2^31个Long实例。
这一点在我记忆中和工作里的要求不一致,为此我专门去翻阅了阿里巴巴Java开发手册,里面是这样描写的:
可见公司在这个问题的考虑上是业务优先了,所以小伙伴们可以斟酌使用时的取舍,我个人还是推荐使用包装类型的。
避免一个误区:
不要看完这一章节就陷入了创建对象的代价非常昂贵的逻辑怪圈里去了,反之维护自己的对象池来避免创建对象是一种错误的做法。因为现代JVM的实现里有高度优化的垃圾收集器,其性能很容易就超过了轻量级对象池的性能。
一个正确的示例是数据库连接池,因为建立一个数据库的连接是非常昂贵的。