Effective.Java 读书笔记(5)复用对象

5.Avoid creating unnecessary object

大意为 避免创建非必要的对象

通常来说我们每次重复使用一个对象是比重新创建一个功能上相等的对象更为合适的,复用可以更快并且更加优雅,当一个对象是不变的(Immutable)时候可以被经常重用

举一个极端的例子,考虑下列代码

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

这个语句执行的时候创建了一个String实例并且这个对象的创建是没有必要的,试想一下在一个循环或者频繁使用的方法里面这样用,会出现许多非必要的String实例

其实简单这样写就可以了

String s = "stringette";

这样写,我们只用了一个String实例而不是创建一个新的,而且,这保证了对象会被同一虚拟机中的其他任意的代码来复用

使用静态工厂方法你可以经常避免这种非必要的对象的创建,举个例子,静态的工厂方法,Boolean.valueOf(String)比起构造方法Boolean(String)是更加合适,构造方法每次会创建一个新的对象,然而静态工厂方法永远不会去这样做

对于复用不可变的对象,如果你知道哪些对象不会被修改你也可以复用那些可变的对象,这里略微有一点微妙,并且十分常见的例子关于不要去做的事,它调用了可变的Data对象,这个对象它的值一旦被计算出来后从未被更改,这个类对人建模,有着isBabyBoomer的方法,并且这个方法能告诉我们这个人是否是babyboomer,换句话说,就是这个人是否是出生在1946和1964年之间

public class Person {
  private final Date birthDate;
  // Other fields, methods, and constructor omitted
  // DON'T DO THIS!
  public boolean isBabyBoomer() {
  // Unnecessary allocation of expensive object
    Calendar gmtCal =
            Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
    Date boomStart = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
    Date boomEnd = gmtCal.getTime();
    return birthDate.compareTo(boomStart) >= 0 &&
    birthDate.compareTo(boomEnd) < 0;
  }
}

这里的isBabyBoomer方法中,调用的时候创建了一个新的Calendar和TimeZone还有两个Date实例,这是没有必要的,下面利用静态的初始化类给出避免这种情况的代码,

class Person {
  private final Date birthDate;
  // Other fields, methods, and constructor omitted
  /**
  * The starting and ending dates of the baby boom.
  */
  private static final Date BOOM_START;
  private static final Date BOOM_END;
  static {
    Calendar gmtCal =
         Calendar.getInstance(TimeZone.getTimeZone("GMT"));
    gmtCal.set(1946, Calendar.JANUARY, 1, 0, 0, 0);
    BOOM_START = gmtCal.getTime();
    gmtCal.set(1965, Calendar.JANUARY, 1, 0, 0, 0);
    BOOM_END = gmtCal.getTime();
  }
  public boolean isBabyBoomer() {
      return birthDate.compareTo(BOOM_START) >= 0 &&
                birthDate.compareTo(BOOM_END) < 0;
  }
}

这个版本的Person类当它初始化的时候,创建Calendar,TimeZone和Date实例只有一次,而不是每次调用isBabyBoomer方法时都会创建新的,

这样的方法使得一些频繁调用的方法获得更加优越的表现,同时也使得代码更加清晰,增加了可读性

当然如果Calendar的实例的创建的代价十分重,通过这样的优化效果可能并不是很理想

如果Person类被初始化当时isBabyBoomer这个方法一直没有被调用的化那么BOOM_START和BOOM_END就会非必要地初始化了

消除这种非必要的初始化是有可能的,我们在isBabyBoomer方法第一次调用的时候可以使用懒初始化(lazily initializing)这些域,但这个并不推荐,经常使用懒加载会使得实现上更加复杂并且表现的提升可能没有我们所期望的那样

通过前面的例子我们知道当一个类在初始化之后不会被修改,那么我们很明显可以复用它,那么还有别的情况我们可以复用吗?当然了,我们的Adapter适配器就是一个例子,学习过Android的同学可能比较了解,ListView或者RecycleView就需要Adapter,一个Adapter在给定对于的对象的时候没有必要创建超过一个的实例

这里有种新的方式去创建一个非必要的对象,叫做autoboxing(自动封装),它允许程序员去混合原始的和封装后的原始类型,按照需求自动封装或者不封装,自动封装比较模糊但是不用去清除原始类型和封装后的原始类型的区别,他们有微妙的语义区别并且不那么微妙的表现差异,考虑一下下面计算正数的和的代码

     // Hideously slow program! Can you spot the object creation?
public static void main(String[] args) {
  Long sum = 0L;
  for (long i = 0; i < Integer.MAX_VALUE; i++) {
       sum += i;
     }
     System.out.println(sum) 
}

使用Long纪录sum是因为int不够大来存放 这段代码很简单,可以得到正确的答案但是可能太慢了,原因是单字符排版错误,sum变量被声明成Long而不是long,这就意味着程序需要构造大概2^31个没有必要的Long实例,大概需要43秒,然后你改成long只需要6.8秒,机器差异不算的话,两者的表现实在相差太大了,我们更推荐使用原始类型而不是封装后的类型,小心不是我们所意的自动封装

这个我们在使用创建代价比较大的类的时候需要仔细注意,尽量避免,于此相反,创建一些小的对象,这些对象的构造方法十分轻松,特别是在当今JVM的实现上,创建额外对象去增强代码的清晰性,简单性或者程序的能力上绝对是好的

反过来,避免通过维持你自己的对象池(Object pool)来进行对象的创立,这是糟糕的想法除非你的对象在池中有着极端的权重,使用对象池的传统的例子是数据库的连接,建立连接的代价比较高,并且复用这些对象是有意义的,当然你的数据库证书可能会由于确定的连接的数目来限制你,总而言之,维持一个对象池会使你的代码杂乱,增加内存的开销并且降低表现,今天的JVM实现有着高度优化的垃圾收集器,它能够轻易地表现出更好的性能比起低权重对象的对象池

后话,重复创建不需要的对象仅仅影响风格和表现,如果涉及一些Defensive Copying防御性复制的话,那么安全漏洞和阴险的bug是更加重要的

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏轮子工厂

8. 一花一世界,while for 循环?

wo这周有点懒啊,这才第 2 篇,个人有点事情,本来预计可以更新……1 篇的,︿( ̄︶ ̄)︿

972
来自专栏恰同学骚年

你必须知道的指针基础-3.指针的移动及指针的危险

  指针每次加一就是指针向前移动指针类型对应的字节数。下面通过一个int指针来指向一个int数组,看看指针的加法运算到底是个什么鬼?

832
来自专栏编程

程序员C语言C加加新手小白入门基础最容易犯的17种错误,你中了几个?

相信这么努力的你 已经置顶了我 C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考...

2195
来自专栏杨建荣的学习笔记

任务调度并行算法的Java简单实现

今天下午抽空写了下并行调度算法的Java版本,是想把这个思路先实现了,后面改写Python版作为参考,调试这个版本之后,再来写Python版,发现差别还不小。 ...

3446
来自专栏noteless

java集合框架容器 java框架层级 继承图结构 集合框架的抽象类 集合框架主要实现类

java集合框架  框架设计理念  容器 继承层级结构 继承图 集合框架中的抽象类  主要的实现类 实现类特性   集合框架分类 集合框架并发包 并发实现类

1482
来自专栏IT派

程序员必知的 Python 陷阱与缺陷列表

我个人对陷阱的定义是这样的:代码看起来可以工作,但不是以你“想当然”的方式。如果一段代码直接出错,抛出了异常,我不认为这是陷阱。比如,Python程序员应该都遇...

1084
来自专栏逸鹏说道

Python3 与 C# 基础语法对比(String专栏)

Python3 与 C# 基础语法对比:https://www.cnblogs.com/dotnetcrazy/p/9102030.html

982
来自专栏深度学习自然语言处理

【干货】python正则表达式应用笔记

正则表达式 (Regular Expression) 又称 RegEx, 是用来匹配字符的一种工具. 在一大串字符中寻找你需要的内容. 它常被用在很多方...

3148
来自专栏PHP实战技术

你真的懂let和const吗?

在ES6之前我们脑海里应该只存在全局作用域和函数级作用域,没有块级作用域。那么为什么要引入块级作用域呢?

42211
来自专栏绿巨人专栏

学习Scala: 初学者应该了解的知识

2814

扫码关注云+社区