Effective Java -- 创建和销毁对象 -- 避免创建不必要的对象

第二章 创建和销毁对象

第六条 避免创建不必要的对象

最好能重用单个对象,而不是在每次需要的时候就创建一个相同功能的新对象。
如果对象是不可变的,它就可以始终被重用。

一 例子

先看一个极端的反例:

	//一个极端的反例
	String s = new String("bikini");//比基尼

该语句每次执行都会创建一个新的String对象,但是这是不必要的。
传递给String构造方法的参数"bikini"本身就是一个String对象。

改进后:

	//改进后的版本
    String s = "bikini";

这里只用了一个String对象,而不是每次执行都创建一个新对象。
而且,对于所有在同一台虚拟机中运行的代码,只要它们包含相同的字符串字面常量,该对象就会被重用(静态常量池)。

二 优先使用静态工厂方法

对于同时提供了构造方法和静态工厂方法的不可变类,优先使用静态工厂方法以避免创建不必要的对象。
例如:

	//静态工厂方法 更推荐使用
	Boolean.valueOf(String);
	
	//构造方法 在Java9中已经被废弃了
	Boolean(String);

三 将经常创建的对象缓存

如果需要重复地创建某类“昂贵的对象”,可以将它缓存下来重用。
下面是一个罗马数字校验器类,确定一个字符串是否为一个有效的罗马数字:

/*罗马数字校验器*/
public class RomanNumerals {
	//使用一个正则表达式
    static boolean isRomanNumeral(String s) {
        return s.matches("^(?=.)M*(C[MA]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }
}

虽然String.matches(String regex)方法最易于查看一个字符串是否与正则表达式相匹配,但并不适合在注重性能的情形中重复使用:

	//String.java 源码
	public boolean matches(String regex) {
        return Pattern.matches(regex, this);
    }

	//Pattern.java 源码
	public static boolean matches(String regex, CharSequence input) {
        Pattern p = Pattern.compile(regex); //问题所在
        Matcher m = p.matcher(input);
        return m.matches();
    }

每次调用这个方法都会创建一个Pattern对象,却只用了一次就被垃圾回收了,这样很浪费性能。

为了提升性能,应该将正则表达式缓存起来,便于重用:

/*罗马数字校验器*/
public class RomanNumerals {
	//有私有静态不可变的Pattern将正则表达式缓存起来
    private static final Pattern ROMAN = Pattern.compile("^(?=.)M*(C[MA]|D?C{0,3})" + "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

	//每次调用时会重用同一个ROMAN实例,大大提升性能
    static boolean isRomanNumeral(String s) {
        return ROMAN.matcher(s).matches();
    }
}

此处用的是饿汉式,即类加载完成时ROMAN就已经被初始化了。书中提到不推荐使用懒汉式,因为这样会使方法的实现更加复杂,从而无法将性能显著提高到超过已经达到的水平(详见第67条)(现在不是很懂)。

四 警惕无意识的自动装箱

自动装箱允许程序员将基本类型和装箱基本类型混用,按需要自动装箱和拆箱。
自动装箱使得基本类型和装箱基本类型之间的差别变得模糊起来,但是并没有完全消除。
例如下面是一个计算所有int正整数值总和的方法(反例):

	//计算所有int正整数值总和
	private static long sum() {
		//int无法容纳所有int正整数的总和,所以使用long类型
        Long sum = 0L; //所有int正整数的总和
      //↑问题所在(注意这里是Long不是long)
        for(long i = 0; i <= Integer.MAX_VALUE; i++) {
            sum += i;
        }
        return sum;
    }

在这个方法中,变量sum被声明成Long而不是long,意味着程序创建了大约231个多余的Long对象。这将导致运行时间大大增加,严重影响效率。
因此,要优先使用基本类型而不是装箱基本类型,要警惕无意识的自动装箱。

五 并非任何时候都要避免创建对象

通过创建附加的对象,提升程序的清晰性、简洁性和功能性是件好事;通过维护自己的对象池来避免创建对象会把代码弄得很乱,同时增加内存占用和损害性能。
现代JVM实现具有高度优化的垃圾回收器,其性能很容易就会超过轻量级对象池的性能。
必要时如果没能实施保护性拷贝,将会导致潜在的Bug和安全漏洞;而不必要地创建对象则只会影响程序的风格和性能。(详见第50条)

六 总结

一般应该避免创建不必要的对象,可以通过优先使用静态工厂方法、将经常创建的对象缓存、优先使用基本类型而不是装箱基本类型等方式来避免。
但是要注意并非任何时候都要避免创建对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值