概述
一般来说,最好能重用单个对象,而不是在每次需要的时候就创建一个相同功能的新对象。一个极端的反例:
String s = new String("aaa");
改进后的写法:
String s = "aaa";
这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例。
假设你想写一个方法来确定一个字符串是否是一个有效的罗马数字。 以下是使用正则表达式完成此操作时最简单方法:
static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
这个实现的问题在于它依赖于 String.matches 方法。 虽然 String.matches 是检查字符串是否与正则表达式匹配的最简单方法,但它不适合在性能临界的情况下重复使用。 问题是它在内部为正则表达式创建一个 Pattern 实例,并且只使用它一次,之后它就有资格进行垃圾收集。 创建 Pattern 实例是昂贵的,因为它需要将正则表达式编译成有限状态机(finite state machine)。为了提高性能,作为类初始化的一部分,将正则表达式显式编译为一个 Pattern 实例(不可变),缓存它,并在 isRomanNumeral 方法的每个调用中重复使用相同的实例:
public class RomanNumerals {
private static final Pattern ROMAN = Pattern.compile( "^(?=.)M*(C[MD]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
如果经常调用, isRomanNumeral 的改进版本的性能会显著提升。 在我的机器上,原始版本在输入 8 个字符的字符串上需要 1.1 微秒,而改进的版本则需要 0.17 微秒,速度提高了 6.5 倍。 性能上不仅有所改善,而且更明确清晰了。 为不可见的 Pattern 实例创建静态 final 修饰的属性,并允许给它一个名字,这个名字比正则表达式本身更具可读性。
另一种创建多余对象的方法是自动装箱,自动装箱使得基本类型和装箱基本类型之前的差别变得模糊起来,但是并没有完全消除。考虑下面的方法,它计算所有正整数的总和。 要做到这一点,程序必须使用 long 类型,因为 int 类型不足以保存所有正整数的总和:
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
sum += i;
}
return sum;
}
这个程序的结果是正确的,但由于写错了一个字符,运行的结果要比实际慢很多。变量 sum 被声明成了 Long 而不是 long ,这意味着程序构造了大约 231 不必要的 Long 实例(大约每次往 Long类型的 sum 变量中增加一个 long 类型构造的实例),把 sum 变量的类型由 Long 改为 long ,在我的机器上运行时间从 6.3 秒降低到 0.59 秒。这个教训很明显:优先使用基本类型而不是装箱的基本类型,也要注意无意识的自动装箱。
最后
以上就是健壮羊为你收集整理的避免创建不必要的对象的全部内容,希望文章能够帮你解决避免创建不必要的对象所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复