`
guitar427
  • 浏览: 4722 次
  • 性别: Icon_minigender_1
  • 来自: 北京
最近访客 更多访客>>
社区版块
存档分类
最新评论

第5条--避免创建不必要的对象

 
阅读更多

先说几句题外话,重新看这本书,真的是有很多新的收获,而且明显地感觉比第一次学习时轻松很多,所以永远不要放弃学习,即使过程再艰难再晦涩,只要坚持去做去学总会有提升,而一点一点的提升最终就会产生一个巨大的质变,再回头去看的时候就会惊讶地发现其实自己已经走了很远。对于初学者来说,仔细读JDK的源码和一些优秀框架的源码对于编程能力的提高是非常有帮助的,先去读一些常用接口和类的源码,比如java.lang、java.util、java.io包下面的一些东西,学习java访问数据库时候可以参考java.sql包,学习网络编程可以参考java.net包去看,学习并发编程可以再去研究java.util.concurrent包,学习算法和设计模式等知识时也可以拿jdk甚至junit、spring这些优秀的设计来当例子看。我想在EffectiveJava的学习记录之后继续写几个源码解读的系列,希望对初学者有所帮助。

好了,进入正题吧,这一条我想把作者的讲解顺序做一个调整,我觉得这样大家会快速理解这一条的中心思想。
避免创建不必要的对象,不是说尽量少地创建对象,也不是说重用对象一定是好的选择,而是避免创建那些不需要创建的对象。
实际上,小对象的创建和回收是非常廉价的,通过创建小对象来提升程序的逻辑性、简洁性和功能,这通常是件好事;而通过维护对象池避免重复创建对象的做法只在极少的情况下是被鼓励的,除非创建对象的开销非常大,例如创建数据库连接这样的操作。另外,在某些情况下是必须创建新对象的,这和后面“保护性拷贝”所讲的内容相对应。在需要使用保护性拷贝的时候,复用对象会造成安全隐患。
那究竟哪些对象是不必要创建的呢?
我们来看一个极端反面的例子:
String s = new String("string");
很多公司的笔试题都会举这个例子,"string"本身就是一个实例,new String又会创建一个String的实例。String的域都是final修饰的,所以String类型的实例一旦创建后就是不可变的,对String的操作而产生的实例都是新创建的实例,如果这种操作非常频繁的话尽量使用StringBuffer或者StringBuilder来代替。


再来看另外一个例子,有一个Person类,它有一个isBabyBoomer方法,用来检验这个人是否为一个"baby boomer(生育高峰期出生的小孩)",换句话说,就是检验这个人是否出生于1946至1964年间:

public class Person {
    private final Date birthDate;
    
    public Person(Date birthDate){
        this.birthDate = 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中创建的两个Date对象虽然是可变的,但是在意义上是不可变的,所以完全没必要每次执行isBabyBoomer方法都创建对象,可以将boomStart和boomEnd作为常量在类的第一次实例化时初始化。

  上面的两个例子中的对象都是明显能够被重用的,还有些情况是不那么明显的,考虑适配器的情形。适配器是指这样一个对象:它把功能委托给一个后备对象(backing object),从而为后备对象提供一个可以替代的接口。由于适配器除了后备对象之外,没有其他的状态信息,所以针对某个给定对象的特定适配器而言,它不需要创建多个适配器实例。
  上面这段话不是太容易理解,我们来对照Map的keySet方法来分析,先来看看HashMap中keySet方法的实现:
   

public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

 
  我们发现keySet被实例化以后每次调用keySet返回的都是同一个KeySet实例,这里KeySet类就是一个适配器,它的所有方法只是对Map接口中方法的一种适配或者说是替代方案。所以,对于同一个Map来说,没必要创建多个KeySet的实例。
 
  JDK1.5中引入了一种新的机制,自动装箱(autoboxing),它允许基本类型和装箱类型的混用,按需要自动装箱和拆箱,对于这个机制,还是慎用为好,看看这个例子:
  

Long sum = 0L;
  for(long i=0; i<Integer.MAX_VALUE; i++){
    sum +=i;
  }

 
  这段程序只因sum的类型被设置成装箱类型Long,执行效率受到了很大的影响,当然这也是一个比较极端的例子。
  最后还是重复那句话,避免创建不必要的对象,不是说尽量少地创建对象,也不是说重用对象一定是好的选择,而是避免创建那些不需要创建的对象。而这里发现哪些是不需要创建的对象是重点。

分享到:
评论

相关推荐

    Python计划. 面向对象语法精讲面. 面向对象进阶-对象的引用.pdf

    这种行为在处理大型数据结构时尤其重要,因为它避免了不必要的内存复制,提高了效率。 总结一下,Python的面向对象编程涉及类的定义、对象的实例化、继承、封装和多态等概念。在对象的引用方面,Python采用引用计数...

    0原生js-面向对象-无缝轮播图.zip

    - **性能优化**:避免不必要的DOM操作,使用`requestAnimationFrame`进行动画更新,确保浏览器在重绘之前执行。 - **触屏支持**:添加对触摸事件的支持,以便在移动设备上正常工作。 - **浏览器兼容性**:考虑到...

    Java第27课---面向对象之类的设计(ArrayBox封装)_ArrayBox_

    为了提高性能,`ArrayBox`类可能包含一些优化措施,比如当数组未满时,避免不必要的扩容;或者在删除元素后,通过移动元素来避免“洞”(即空位)。 7. **泛型支持**: `ArrayBox`类可以设计为泛型类,允许存储...

    how-to-be-a-programmer-cn.pdf

    - 避免引入不必要的复杂性。 - **分解子任务:** - 将大问题拆分成一系列小问题。 - 确保每个子任务可独立解决。 **10. 如何处理无趣的问题** - **寻找乐趣:** - 从挑战中发现成长的机会。 - 将无趣任务视为...

    swift-基于MZTimerLabel修复内存问题去掉不必要的初始化

    在这个主题中,我们将深入探讨如何基于`MZTimerLabel`修复内存问题,并避免不必要初始化。 首先,我们需要理解Swift中的内存管理机制。Swift使用自动引用计数(Automatic Reference Counting, ARC)来跟踪和释放...

    第二十章-开发Delphi对象式数据管理功能(五)

    10. **性能优化**:理解如何优化查询性能,避免不必要的数据库交互,以及正确使用索引和缓存策略,对于开发高效的数据管理功能至关重要。 通过学习这些知识点,并结合实际的项目练习,开发者能够熟练地在Delphi中...

    Matlab面向对象编程

    通过抽象,我们可以忽略不必要的细节,只关注问题的关键部分。 2. 封装:封装是指隐藏对象的内部实现细节,只暴露操作接口供外界访问。在MATLAB中,通过类和对象实现封装。类定义了对象的属性和方法,而对象是类的...

    学习javascript面向对象 掌握创建对象的9种方式

    这样做可以减少不必要的输入,同时在视觉上更好地封装原型的功能。 以上介绍了JavaScript中创建对象的前六种方式。后续还有几种方式,但本文中未提及。读者可以继续探索其他创建对象的方式,如组合使用构造函数模式...

    61条Java面向对象设计的经验原则.

    - **解释**:类的公有接口应该仅包含用户真正需要使用的功能,避免加入不必要的方法或属性,从而保持接口的清晰和简洁。 #### 原则七:低耦合 - **原则**:类之间应该零耦合,或者只有导出耦合关系。 - **解释**:...

    JSON--List集合转换成JSON对象详解

    在将集合转换为JSON时,可能会有一些不必要的数据或属性需要过滤掉。文中提到使用JsonConfig来设置JsonPropertyFilter,这样可以在序列化过程中过滤掉不需要的属性或对象。这是一种更加精细的控制方式,可以避免在...

    第五章-MATLAB程序设计.ppt

    例如,使用vectorization和logical indexing提升计算效率,避免不必要的内存分配,使用profile分析性能瓶颈。 **5.10 应用程序接口** MATLAB可以与其他编程语言(如C、C++、Java)接口,通过MEX文件实现混合编程,...

    面向对象设计经验

    过早优化往往会带来复杂性和不必要的开销,反而降低了代码的可读性和可维护性。 ### 8. 明确类与对象的职责 - 类和对象应该具有明确的职责,避免职责混淆。如第31条原则:“在创建类时,类的职责应该是明确的。”...

    Flash-性能优化

    - **不必要的网络操作**:避免不必要的网络请求,减少带宽使用和等待时间。 #### 第六章:处理媒体 - **视频和音频**:优化视频和音频处理,提高播放质量和流畅度,减少卡顿。 #### 第七章:SQL数据库性能 - **...

    Java编程基础(2011-2012学年第一学期)复习提纲.doc

    - `&&` 和 `||` 是短路逻辑运算符,在某些情况下可以避免不必要的计算。 - `&&`: 如果左侧操作数为 `false`,则右侧操作数不会被计算。 - `||`: 如果左侧操作数为 `true`,则右侧操作数不会被计算。 - `&` 和 `|...

    【IT十八掌徐培成】Java基础第10天-03.List-集合框架-ArrayList.zip

    - `ArrayList(int initialCapacity)`: 创建一个指定初始容量的ArrayList,避免了不必要的扩容操作。 2. **ArrayList的主要方法**: - `add(E element)`: 向ArrayList末尾添加一个元素。 - `add(int index, E ...

    vs2008下C++对象内存布局

    - 合理规划数据结构,避免不必要的对齐开销。 - 尽量减少动态内存分配,尤其是在循环中,考虑使用容器(如`std::vector`)或预分配内存。 - 使用智能指针(如`std::unique_ptr`或`std::shared_ptr`)管理动态内存...

    JS对象与数组参考大全

    在处理大型数据结构时,理解对象和数组的内部工作原理至关重要,例如,避免不必要的遍历,合理使用引用和深拷贝,以及利用`Map`和`Set`等数据结构提高性能。 以上是JS对象与数组的基本概念和常用方法,掌握这些知识...

    面向对象程序设计的61条经验.pdf

    - **解释:** 避免创建过于庞大或复杂的类,这些类通常称为“上帝类”。这类类往往难以维护和理解,因为它们承担了太多的责任。 **原则14:** 对公共接口中定义了大量访问方法的类多加小心。 - **解释:** 大量的...

Global site tag (gtag.js) - Google Analytics