Effective Java 第三版——6. 避免创建不必要的对象
Tips
《Effective Java, Third Edition》一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将近8年的时间,但随着Java 6,7,8,甚至9的发布,Java语言发生了深刻的变化。
在这里第一时间翻译成中文版。供大家学习分享之用。
6. 避免创建不必要的对象
在每次需要时重用一个对象而不是创建一个新的相同功能对象通常是恰当的。重用可以更快更流行。如果对象是不可变的(条目 17),它总是可以被重用。
作为一个不应该这样做的极端例子,请考虑以下语句:
String s = new String("bikini"); // DON'T DO THIS!
语句每次执行时都会创建一个新的String实例,而这些对象的创建都不是必需的。String构造方法(“bikini”)
的参数本身就是一个bikini
实例,它与构造方法创建的所有对象的功能相同。如果这种用法发生在循环中,或者在频繁调用的方法中,就可以毫无必要地创建数百万个String实例。
改进后的版本如下:
String s = "bikini";
该版本使用单个String实例,而不是每次执行时创建一个新实例。此外,它可以保证对象运行在同一虚拟机上的任何其他代码重用,而这些代码恰好包含相同的字符串字面量[JLS,3.10.5]。
通过使用静态工厂方法(static factory methods(项目1),可以避免创建不需要的对象。例如,工厂方法Boolean.valueOf(String)
比构造方法Boolean(String
)更可取,后者在Java 9中被弃用。构造方法每次调用时都必须创建一个新对象,而工厂方法永远不需要这样做,在实践中也不需要。除了重用不可变对象,如果知道它们不会被修改,还可以重用可变对象。
一些对象的创建比其他对象的创建要昂贵得多。 如果要重复使用这样一个“昂贵的对象”,建议将其缓存起来以便重复使用。 不幸的是,当创建这样一个对象时并不总是很直观明显的。 假设你想写一个方法来确定一个字符串是否是一个有效的罗马数字。 以下是使用正则表达式完成此操作时最简单方法:
// Performance can be greatly improved!
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
方法的每个调用中重复使用相同的实例:
// Reusing expensive object for improved performance
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();
}
}
如果经常调用,isRomanNumera
l的改进版本的性能会显著提升。 在我的机器上,原始版本在输入8个字符的字符串上需要1.1微秒,而改进的版本则需要0.17微秒,速度提高了6.5倍。 性能上不仅有所改善,而且更明确清晰了。 为不可见的Pattern实例创建静态final修饰的属性,并允许给它一个名字,这个名字比正则表达式本身更具可读性。
如果包含isRomanNumeral
方法的改进版本的类被初始化,但该方法从未被调用,则ROMAN
属性则没必要初始化。 在第一次调用isRomanNumeral
方法时,可以通过延迟初始化( lazily initializing)属性(条目 83)来排除初始化,但一般不建议这样做。 延迟初始化常常会导致实现复杂化,而性能没有可衡量的改进(条目 67)。
当一个对象是不可变的时,很明显它可以被安全地重用,但是在其他情况下,它远没有那么明显,甚至是违反直觉的。考虑适配器(adapters)的情况[Gamma95],也称为视图(views)。一个适配器是一个对象,它委托一个支持对象(backing object),提供一个可替代的接口。由于适配器没有超出其支持对象的状态,因此不需要为给定对象创建多个给定适配器的实例。
例如,Map接口的keySet方法
返回Map对象的Set视图,包含Map中的所有key。 天真地说,似乎每次调用keySet都必须创建一个新的Set实例,但是对给定Map对象的keySet
的每次调用都返回相同的Set实例。 尽管返回的Set实例通常是可变的,但是所有返回的对象在功能上都是相同的:当其中一个返回的对象发生变化时,所有其他对象也都变化,因为它们全部由相同的Map实例支持。 虽然创建keySet
视图对象的多个实例基本上是无害的,但这是没有必要的,也没有任何好处。
另一种创建不必要的对象的方法是自动装箱(autoboxing),它允许程序员混用基本类型和包装的基本类型,根据需要自动装箱和拆箱。 自动装箱模糊不清,但不会消除基本类型和装箱基本类型之间的区别。 有微妙的语义区别和不那么细微的性能差异(条目 61)。 考虑下面的方法,它计算所有正整数的总和。 要做到这一点,程序必须使用long
类型,因为int
类型不足以保存所有正整数的总和:
// Hideously slow! Can you spot the object creation?
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秒。这个教训很明显:优先使用基本类型而不是装箱的基本类型,也要注意无意识的自动装箱。
这个条目不应该被误解为暗示对象创建是昂贵的,应该避免创建对象。 相反,使用构造方法创建和回收小的对象是非常廉价,构造方法只会做很少的显示工作,,尤其是在现代JVM实现上。 创建额外的对象以增强程序的清晰度,简单性或功能性通常是件好事。
相反,除非池中的对象非常重量级,否则通过维护自己的对象池来避免对象创建是一个坏主意。对象池的典型例子就是数据库连接。建立连接的成本非常高,因此重用这些对象是有意义的。但是,一般来说,维护自己的对象池会使代码混乱,增加内存占用,并损害性能。现代JVM实现具有高度优化的垃圾收集器,它们在轻量级对象上轻松胜过此类对象池。
这个条目的对应点是针对条目 50的防御性复制(defensive copying)。 目前的条目说:“当你应该重用一个现有的对象时,不要创建一个新的对象”,而条目 50说:“不要重复使用现有的对象,当你应该创建一个新的对象时。”请注意,重用防御性复制所要求的对象所付出的代价,要远远大于不必要地创建重复的对象。 未能在需要的情况下防御性复制会导致潜在的错误和安全漏洞;而不必要地创建对象只会影响程序的风格和性能。
Effective Java 第三版——6. 避免创建不必要的对象的更多相关文章
- 《Effective Java 第三版》新条目介绍
版权声明:本文为博主原创文章,可以随意转载,不过请加上原文链接. https://blog.csdn.net/u014717036/article/details/80588806前言 从去年的3月份 ...
- Effective Java 第三版——1. 考虑使用静态工厂方法替代构造方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——3. 使用私有构造方法或枚类实现Singleton属性
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——10. 重写equals方法时遵守通用约定
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——11. 重写equals方法时同时也要重写hashcode方法
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——14.考虑实现Comparable接口
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——18. 组合优于继承
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——19. 如果使用继承则设计,并文档说明,否则不该使用
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
- Effective Java 第三版——21. 为后代设计接口
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...
随机推荐
- 基于 Aspose.Cells与XML导入excel 数据----操作类封装
前言 导入excel数据, 在每个项目中基本上都会遇到,第三方插件或者基于微软office,用的最多的就是npoi,aspose.cells和c#基于office这三种方式,其中各有各的优缺点,在这也 ...
- python调用c代码
Linux环境下使用python调用C的printf例子: #!/usr/bin/env python2.7 #-*- coding:utf-8 -*- from ctypes import * de ...
- JavaScript数组对象方法
数组的方法:首先,数组的方法有数组原型方法,也有从object对象继承来的方法.这里只详细说明一下数组的原型方法. (1)join:把数组中的所有元素放入一个字符串.元素通过指定的分隔符进行分隔. 例 ...
- Android drawText 做到文字绝对居中
我们在android中经常会遇到自定义一些组件,因为现有的android组件是往往不能满足当下的需求的,今天就给大家介绍一下在自定义组建过程中用到的drawText不居中的问题的解决方案 首先大家看一 ...
- 【魅族Pro7】——BootStrap/JQuery/Canvas/PHP/MySQL/Ajax爬坑之项目总结(一)
前言:这个项目是我们小组团体合作完成的学习项目,项目使用魅族GUI设计和图片素材,响应式重构Pro7官网的首页.子页.商城及购物车,并加入一些创新.我主要负责的是[画屏子页]的项目,这里作为温故知新, ...
- 逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式
逆向知识第十四讲,(C语言完结)结构体在汇编中的表现形式 一丶了解什么是结构体,以及计算结构体成员的对其值以及总大小(类也是这样算) 结构体的特性 1.结构体(struct)是由一系列具有相同类型或不 ...
- vscode调试html页面,及配置说明
一.效果目的 1.在VSCode里,直接F5打开html页面,并且可以在编辑器里,进行断点调试js代码: 二.工具准备 1.VSCode 软件 2.一个js项目 3.VSCode 上装一个插件:Deb ...
- struts2 action接收请求参数和类型转换
1,action接收请求参数 在struts2中action是什么?(struts2是一个mvc框架) V:jsp M:action C:action ...
- unity中调用其他脚本函数的方法(小白之路)
第一种,被调用脚本函数为static类型,调用时直接用 脚本名.函数名().很不实用-- 第二种,GameObject.Find("脚本所在物体名").SendMessage(& ...
- P1156 垃圾陷阱
题目描述 卡门――农夫约翰极其珍视的一条Holsteins奶牛――已经落了到“垃圾井”中.“垃圾井”是农夫们扔垃圾的地方,它的深度为D(2<=D<=100)英尺. 卡门想把垃圾堆起来,等到 ...