effective java 第三版 条目6 避免创建不必要的对象

        这通常是很合适去重用一个单例而不是去再创建另一个每一次使用都相同的功能性对象。重用资源可以更快也更加流行。同时一个不可变得对象总是能够被重用!(条目17)

       这里有一个极端的例子,思考下面一条语句:

     String s = new String("bikini"); // DON'T DO THIS!

     这条语句每一次执行都创建了一个新的String实例,同时没有一个实例的创建是有必要的。这个String的构造器的参数"bikini"他自己就是一个String实例,功能上和所有调用构造器创建出来的实例是相同的。如果这种使用在一个循环的或者频繁地调用,成千上万的不必要的String实例就会被创建!

    升级版是这样:

String s = "bikini"

    这个版本使用了一个String实例而不是每一次就去创建一个String实例,除此之外,这样所会鼓励对象这个对象在其他运行在相同虚拟机的String对象能够被重用,因为虚拟机中能够包含这个字符串字面量!

    你可能经常通过使用静态工厂方法(条目1)而不是构造器,同时仅向外提供私有构造器,来创建不可变对象以避免创建不必要的对象。例如,使用Boolean.valueOf就比Boolean的构造(在JAVA9中已经被遗弃)更好。每一次使用构造器一定会创建一个对象。然而静态工厂方法在实际上从来不会这样做。除此之外,重用不可变对象,你可以也重用可变对象当你知道他不会变化!

    有一些对象的创建会更加昂贵。如果你需要重用这样一个昂贵的对象,他可能会因为重用被建议去做缓存!不幸的是,这不总是很明显当你创建一个对象。假设你想要写一个方法来决定一个字符串是否是一个有效的罗马数字,这时最简单的方式就是使用正则表达式。

// 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是最简单的方式来判断一个字符串是否匹配一个正则表达式,如果重复使用在性能临界区不合适,这个问题就会在本地为这个正则表达式创建一个正则表达式的模式实例,同时仅仅只会使用它一次。在它可以被垃圾回收器清理的时候,创建一个模式实例就会是很昂贵的了,因为模式需要编译正则表达式到优先的状态机。

    为了提高这个实现的性能,明确地去编译这个正则表达式到一个模式实例作为类初始化的一部分,缓存这个模式,同时在每一次调用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();

  }

}

    这个升级版提供了一个更好的性能如果它频繁地被调用,在我的机器上,这个原始的版本使用了1.1 µs在一个8字符的输入字符串,而升级版只使用了0.17 µs。不只是更快。而且可以证明,这样所更加清晰,让一个静态filnal字段去存储一个课件的模式实例允许我们给他一个命名,这样有更好的可读性在正则表达式上。

    如果这个类包含这个isRomanNumeral 的升级版是已经初始化过的,但是这个方法确实从来没有调用过,那么这个模式的字段将是一个没有必要去实例化的字段,我们有机会通过懒加载机制(条目83)来消除这个实例。他就编译一个没有可度量升级的实现。

      当一个对象是不可变的,它明显可以被安全的重用,但是也可能有时候并不是那么明显甚至违反我们的直觉。思考这样一个适配器的问题,又被称为视图(views),一个适配器代表一个提供可以替代接口的的支持对象,因为一个适配器除了他的支持对象就没有状态,这里不需要再创建超过一个对象来传给适配器,因为他们需要的对象是相同的!

    例如,Map接口的KeySet方法返回一个Map对象的Set视图,由所有在Map中的。natively,这看起来每一次调用KeySet都会不得不创建一个Set实例,但是每一次在Map接口上调用KeySet可能返回相同的Set实例,尽管返回的实例是一个典型的不可变化的对象,所有但会的对象功能上都是相同的。当一个返回的对象改变,所有其他的对象都会改变,因为他们都是来自于一个相同的Map实例。尽管通常在创建KeySet的时候多去创建一个实例是没有害处的,但是这样做其实也是没有必要也没有用处的。

      另一个方式去创建不必要的对象是自动拆装箱,它允许程序员包装原始的类型成一个引用类型,以及一个对应的引用类型自动拆成原始类型。在原始类型和包装类型上有一些明显的语法区别,同时也有一些不那么微妙的不同。思考一下这样一个方法,它需要计算所有原始int的和,为了做这件事,程序不得不得使用一个long类型来存储所有int的值因为int的大小不足以存储所有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;

}

     这段程序可以得到一个正确的答案,但是它比它应该做到的更慢,这就是一个由于一个Long的一个单字节L本该是小写而写成大写造成的。这样意味着程序将进行2的31次方的不必要的Long的实例的生成(粗略地计算每一次会for会产生一个实例,其实java会对一些小的值进行缓存)。在我的机器上,将这个变量的声明从Long变为long可以将这段程序的运行时间从6.3s降低到0.59秒!这个问题就很清楚了,优先使用原始类型而不是包装类型。同时也要注意到不小心的自动拆装箱。

   这个条目不应该被误解为暗示了对象创建时昂贵的、而是应该被避免!相反的是,一些小的对象的构造器只做了一些很少的事,这些对象的创建时很廉价的,尤其是在现代的JVM的实现上,创建一个多余的对象让程序更加清晰和简单,通常是一件更好的事。

  相反的是,保持一个对象池来避免多余的对象的创建通常是一个很坏的idea!除非这些在池中的对象的创建是一个花费非常高的过程。典型的需要对象池的例子就是数据库连接。建立一个连接真的比确定来重新使用这个对象的代价更高。通常来说,无论如何,保持一个的对象池,会扰乱你的代码,会提高内存的占用,同时也会有一些不好的表现。现代的JVM实现已经有很好的优化垃圾回收能力很容易处理一些轻量级的对象。

  这个条目的中心点时条目50的防止复制。现在的条目是说,“不要在你可以重用一个对象的时候创建一个新的对象。”记住,当不调用对象复制时,重复使用一个对象的惩罚是要远远好于创建一个不需要对象的的惩罚。如果防止对象出现失败的话,就可能会导致一个潜在的bug和安全问题,创建一个不必要的对象仅仅只会影响代码风格和性能。

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

推荐阅读更多精彩内容

  • 这通常是恰当的:重用单个对象,而不是每次需要的时候创建一个新的功能相同的对象。重用既更快又更有风格。如果不可变,一...
    tigershin阅读 256评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 对,这就是我对自己的形容,我奋力想要挣脱这个桎梏,可又没有赤裸裸走出去的勇气。现在的我每天做着想想就没有明天的工作...
    尼克松阅读 165评论 0 0
  • 中原女儿,游学金陵,看六朝古都风华茂,听秦淮河畔琵琶乐,遂作此篇,以记心中所感。 春秋史筑世代传,魏晋遗风黄钟语。...
    暮与晓阅读 370评论 0 1
  • 李白《独漉篇》 独漉水中泥,水浊不见月。不见月尚可,水深行人没。 越鸟从南来,胡鹰亦北渡。我欲弯弓向天射,惜其中道...
    萃丰阅读 880评论 9 22