避免创建不必要的对象
示例
- Item06Example01.java:确定一个字符串是否是一个有效的罗马数字。
说明
在每次需要时重用一个对象而不是创建一个新的相同功能对象通常是恰当的。重用可以更快更流行。如果对象是不可变的(条目 17),它总是可以被重用。
通过使用静态工厂方法(static factory methods(项目1),可以避免创建不需要的对象。例如,工厂方法Boolean.valueOf(String)
比构造方法Boolean(String
)更可取,后者在Java 9中被弃用。构造方法每次调用时都必须创建一个新对象,而工厂方法永远不需要这样做,在实践中也不需要。除了重用不可变对象,如果知道它们不会被修改,还可以重用可变对象。
一些对象的创建比其他对象的创建要昂贵得多。 如果要重复使用这样一个“昂贵的对象”,建议将其缓存起来以便重复使用。
缓存起对象以便重复使用
示例:Item06Example01.java:确定一个字符串是否是一个有效的罗马数字
如果包含isRomanNumeral
方法的改进版本的类被初始化,但该方法从未被调用,则ROMAN
属性则没必要初始化。 在第一次调用isRomanNumeral
方法时,可以通过延迟初始化( lazily initializing)属性(条目 83)来排除初始化,但一般不建议这样做。 延迟初始化常常会导致实现复杂化,而性能没有可衡量的改进(条目 67)。
自动装箱
自动装箱(autoboxing),它允许程序员混用基本类型和包装的基本类型,根据需要自动装箱和拆箱。
自动装箱模糊不清,但不会消除基本类型和装箱基本类型之间的区别。 有微妙的语义区别和不那么细微的性能差异(条目 61)。
示例:Item06Example02.java:计算所有正整数的总和
2305843008139952128
UseTime1:10817
2305843008139952128
UseTime2:1405
sumBad()
由于写错了一个字符,运行的结果要比实际慢很多。变量sum
被声明成了Long
而不是long
,这意味着程序构造了大约231不必要的Long
实例(大约每次往Long
类型的 sum
变量中增加一个long
类型构造的实例)。
sumGood()
把sum
变量的类型由Long
改为long
,在我的机器上运行时间从10.8 秒降低到0.14秒。
这个教训很明显:优先使用基本类型而不是装箱的基本类型,也要注意无意识的自动装箱。
维护自己的对象池
除非池中的对象非常重量级,否则通过维护自己的对象池来避免对象创建是一个坏主意。
对象池的典型例子就是数据库连接。建立连接的成本非常高,因此重用这些对象是有意义的。但是,一般来说,维护自己的对象池会使代码混乱,增加内存占用,并损害性能。现代JVM实现具有高度优化的垃圾收集器,它们在轻量级对象上轻松胜过此类对象池。
这个条目的对应点是针对条目 50的防御性复制(defensive copying)。 目前的条目说:“当你应该重用一个现有的对象时,不要创建一个新的对象”,而条目 50说:“不要重复使用现有的对象,当你应该创建一个新的对象时。”请注意,重用防御性复制所要求的对象所付出的代价,要远远大于不必要地创建重复的对象。 未能在需要的情况下防御性复制会导致潜在的错误和安全漏洞;而不必要地创建对象只会影响程序的风格和性能。