本篇笔记对应原书条目6,介绍几个避免创建不必要对象的方法。
1. 采用更合适的API或工具类减少对象的创建
如果一个方法中的一些对象,每次调用都起到相同的作用,那么就可以被重用。
比如,我们不应该用如下的方式来创建一个String对象:
String str = new String("aaa");
因为当我们往构造方法里传入aaa的时候,其实这个aaa就是一个String实例了。我们等于是创建了两个String实例。
我们应该直接这么写:
String str = "aaa";
根据jdk文档,上述方式实际上等同于:
char data[] = {'a', 'a', 'a'};
String str = new String(data);
传入一个字符数组来创建String,避免了创建重复对象。
再举一个常见的例子[2],我们有时希望遍历一个list,将其中的元素存到一个字符串里,并用逗号分隔。我们可能会用下面这种最low的办法:
public static String listToString(List<String> list) {
String str = "";
for (int i = 0; i < list.size(); i++) {
str += list.get(i);
if (i < list.size() - 1) {
str += ",";
}
}
return str;
}
这样其实在每次+=
的时候都会重新创建String对象,极大地影响了性能。
我们可以修改一下,采用StringBuilder的方式来拼接list:
public static String listToString(List<String> list) {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < list.size(); i++) {
stringBuilder.append(list.get(i));
if (i < list.size() - 1) {
stringBuilder.append(",");
}
}
return stringBuilder.toString();
}
这种方式每次只会生成两个实例——StringBuilder
和最后返回的String
。
那有没有更好的方法呢?我们可以采用Google Guava的Joiner
,这样每次只用生成一个实例,如下所示:
public static String listToString(List<String> list) {
return Joiner.on(",).join(list);
}
2. 重用相同功能的对象
有时候我们提供的API中有一些每次调用都具备相同功能的对象,那么就可以把这些对象变成静态的不可变对象,只需实例化一次即可。比如下面这个类似书中的例子[1]:
public static boolean isNumeral(String s) {
return s.matches("^[0-9]*$");
}
这个例子用String.matches()
方法来判断字符串是否为数字。每次调用matches()
方法,里面都会创建一个Pattern
对象,这会对性能造成影响。
因为每次调用isNumeral
实际上都会生成一个功能完全相同的Pattern
对象,所以我们可以把它抽出来,变成一个静态不可变对象,如下所示:
public static final Pattern NUMBER = Pattern.compile("^[0-9]*$");
public static boolean isNumeral(String s) {
return NUMBER.matcher(s).matches();
}
上面我们谈到了一个不可变对象的重用,接下来我们再看看可变对象的重用。可变对象的重用可以通过视图(views)来实现。比如,Map的keySet()
方法就会返回Map对象所有key的Set视图。这个视图是可变的,但是当Map对象不变时,在任何地方返回的任何一个keySet都是一样的,当Map对象改变时,所有的keySet也会相应的发生改变。[1]
3. 小心自动装箱(auto boxing)
自动装箱允许程序员混用基本类型和包装类型[1],在两者相计算时,程序会构造出基本类型的包装类型实例。例如,我们看书中的这个例子:
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
这个例子里的sum += i
处用Long
型和long
型相加,这样每次都会实例化一个值为i
的包装类型,一共实例化了约231个不必要的实例,极大地降低了系统的性能。所以我们在日常开发中,方法内尽量用基本类型,只在入出参的地方用包装类型。多留心,切忌无意识地使用到自动装箱。[1]
其他
如果涉及到对象池的应用,除非池中的对象非常重,类似数据库连接,否则最好不要去自己维护一个对象池,因为这样会很复杂。另外,有时考虑到系统的安全性,那么我们需要进行防御性复制,这个在后面会讲到。此时,重复创建对象就是有意义的,因为比起隐含错误和安全漏洞,重复创建对象带来的性能损失是可以接受的。[1]
总结
这个条目要求我们平时多审视我们的代码,在能不重复创建对象的地方,就不要重复创建。不要无脑写代码,而是要多留点心。这是一种非常好的编码习惯,也可以为系统带来性能上的优势。
声明
本文仅用于学习交流,请勿用于商业用途。转载请注明出处,如果涉及任何版权问题,请及时与我联系,谢谢!
参考资料
- 《Effective Java(第3版)》
- Java之避免创建不必要的对象 https://www.jianshu.com/p/420...
- java代码优化——避免创建不必要的对象 https://www.jianshu.com/p/83a...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。