Effective Java(3rd)-Item6:避免创建没有必要的对象

  通常我们重用一个单独对象而不是创建一个新的有着相同功能的对象。重用可以更快并更优雅。一个对象总是可以被重用,只要它是不可变的(item17)
  作为一个极端的例子,思考一下以下的声明:

image.png

  这个声明在每次执行时创建了一个新的String类型的实例,但是这些实力的创建是没有必要的。String的构造方法(“bikini”)本身就是一个实例,在功能上与所有用构造方法创建的实例完全相同。如果这种用法发生在一个循环中或一个频繁调用的方法中,数以百万的String实例会被没有必要地创建出来。
  改进的版本很简单,如下:
image.png

  这个版本使用一个单一的String实例,而不是在每次执行的时候都创建一个新的。此外,能够保证该对象将被在同一虚拟机内的任意代码重复使用当他们包含相同的字符时。
  通常你可以利用静态工厂方法(static factory method)(item1) 而不是提供两者的不可变类的构造方法来避免创建没有必要的对象。比如,工厂方法Boolean.valueOf(String)比构造方法Boolean(String)要好,后者在Java9中已被弃用。
image.png

  实际上,构造方法在每次调用的时候都必须创建一个新的对象,但是工厂方法从来都不需要这样,以后也不需要。除了重用不可变对象以外,你也可以重用可变对象,只要你知道他们将不会被修改。
  一些对象的创建的开销很大。如果你反复需要这样的“高开销对象”,建议将其缓存以便重复使用。不幸的是,当你创建这样的对象并不总是明显。假设你想要编写一个方法来确定一个字符串是否是一个有效的罗马数字,最简单的方法就是使用正则表达式,如下:
image.png

  这样的实现的问题在于这个方法依赖于String.matches方法。虽然String.matches是最简单的方式来检查一个字符串是否匹配一个正则表达式,但是它不适合在高性能需要的情况下重复使用。因为这个方法为了正则表达式在内在创建了一个Pattern的实例并且只使用一次,之后就被垃圾回收器回收了。创建一个Pattern实例是高开销的,因为它需要将正则表达式编译为有限状态机。
  为了提高性能,显式编译正则表达式为Pattern实例(不可变)作为类初始化的一部分,对其进行缓存,每次调用isRomanNumeral方法时重用相同实例。
image.png

  isRomanNumeral的升级版在频繁调用的情况下显著地提升了性能。在我的机器上,原始版本在输入8个字符的字符串后消耗了1.1 µs,升级版消耗了 0.17µs,是6.5倍的提升。不仅是性能提高了,而且可以认为这个方法更清晰了。使一个静态final字段并且外部不可见的Pattern实例拥有了给与了命名,它比正则表达式本身更有可读性。
  如果类初始化包含了isRomanNumeral方法的改进版,但是这个方法从未被调用,ROMAN初始化得没有必要。通过懒初始化字段(lazily initializing)(item83)在isRomanNueral方法第一次调用时建立初始化是可行的,但是这是不推荐的。与延迟初始化通常的情况一样,它会使实现复杂化,而没有可测量的性能提升 (item67)
  当一个对象是不可变的,明显它可以被安全地复用。但是在其他情况下不那么明显,甚至是反常的。考虑适配器(adapters)的情况,它也叫做views。一个适配器是一个委托背后的对象的对象,提供了一个可替代的接口。因为一个适配器没有状态多于它背后的对象的情况,所以没有必要为给定的对象创建多于一个给定的适配器实例。
   举个例子,Map接口的keySet方法返回了这个Map对象的一个Set视图,这个视图包含了这个map的所有key。你可能会天真地认为每次调用keySet方法都将不得不创建一个新的Set实例,但是每次调用keySet方法时只会返回这个Map对象的相同Set实例。虽然返回的Set实例一般来说是可变的,但是所有返回对象在功能上都是相同的:当返回对象中的一个改变了,所有其他对象也改变了,因为他们都基于相同的Map实例。虽然创建大量keySet实例视图对象很大程度上是无害的,但是这么做说没有必要的,也没有更好好处。
  另一个创建了不必要对象的方式是自动装箱,这允许程序员混合原始类型和原始类型的包装类,自动装箱和拆箱是有必要的。自动装箱模糊但并未擦除了原始类型和包装类的区别。他们有着微妙的语义区别但是没有那么微妙的性能差异(item61)。考虑到下面的方法,计算所有int类型的正值总和。为了做到这个方法的功能,程序不得不使用long类型计算,因为一个int类型不足以保有所有int正值的总和:
image.png

  这个程序得到了正确的回答,但是它比它本该有的效率要更慢,仅仅是因为一个字符的印刷错误。变量sum被声明为Long而不是long,这意味着程序构造了 2^31个没有必要的Long实例(大概每一次long i被add到Long sum)。改变sum的声明从Long到long,在我的机器上运行时间从6.3秒降低到了0.59秒。这个教训很明显:使用原始类型而不是装箱类型,并且要关注于无意识的自动装箱。

image.png

image.png

  这个条目不应该被误解为创建对象开销很大而应该避免使用。相反,创建和回收小对象时,其构造方法做一点明确工作时的开销很小,特别在现代JVM的实现上。创建额外的对象来提高可读性,更简单,程序更有力量,这通常是个好办法。
  相反,除非池中的对象非常重量级,否则通过维护自己的对象池来避免创建对象是一个坏主意。一个经典的例子举例一个对象的确需要对象池就是数据库连接。它建立连接的开销相当大,所以重用这些对象是有意义的。一般来说,维护自己的对象池使得代码混乱,增加内存占用,并损害性能。现代JVM的实现高度优化了垃圾回收机制,在轻量级对象上轻松胜过此类对象池。
  这个条目的对比是item50 防御性复制。本条目描述的是:“不要创建一个新的对象当你重用一个已存在的对象的时候”。条目50说的是,“当你创建一个新对象时,不要重用已存在的对象”。请注意,调用防御性复制时重用对象的代价远远大于不必要地创建重复对象的代价。未在必要时制作防御性副本会导致潜在的bug和安全漏洞,而创建不必要的对象仅仅影响样式和性能。
本文写于2018.11.27,历时44天

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容