`
TonyLian
  • 浏览: 402023 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

【第4条】避免创建不必要的对象

阅读更多

此条在中文版第二版中被译为了“避免创建不必要的对象”,用此更加严谨了。

 

此条认为重复使用同一对象,比每次需要时都创建一个功能上相等价的新对象更好。如果对象是非可变的(见【第13条】),那么他总是可以被重用的。

 

一例子是:

String s1 = "Hello World !";
........  // 其他一些列代码
String s2= "Hello World !";

 虽然看起来s2是新创建的一个新对象,但是由于String是非可变的,所以 String s2= "Hello World !";
 等同于 String s2 = s1; 实际内存中并没有新开辟一块空间,而是将s2的地址指针指向s1的空间地址。

 

但是如果写成  String s2 = new String(s1); 则系统会为s2新开辟一块内存空间,这是我们所不希望看到的。

进而举个更为极端的反例: String s = new String("Hello !");

由于实参"Hello !"本身就是一个String的实例,这时候实际上已经创建了一个String的实例了,再用之作为new String()的参数,就再次创建了一个String实例给s,于是内存的消耗就Double了!

 

    对于提供了静态工厂方法(见【第1条】)和构造函数的非可变类,你通常可以利用静态工厂方法而不是构造函数,以避免重复创建对象。例如,Boolean.valueOf(String)几乎总是优先于构造函数Boolean(String)的。因为构造函数每次被调用都会创建一个新的对象,而静态工厂方法从来不要求这样做。

 

    再有就是那些已知不会被修改的对象,它们一旦被计算出来就不再变化,可以随时使用,而不必每次使用前再次计算。例如,有一个SystemInfo类,里面有保存和取得系统信息的域和方法。为了简单我们认为只有一个用于获取和保持CPUID的方法和域。如果这个类写的不好的话,可能在每次调用 getCPUID()时,都要重新创建SystemInfo中的cpuId,重新获取,甚至重新创建一个SystemInfo类;而好的代码,是在第一次调用时获取,并保持起来,之后再次调用时只是返回就可以了。

以下是我的一段真实代码中的截取:

public class SystemInfo {

  private static final SystemInfo INSTANCE = new SystemInfo();
  private static List<String> NULL_STRING_LIST;   // 应该是一个final常量,但由于这里的执行顺序在构造函数之后,所以要在被构造函数调用的方法中使用这个常量就不可以了,所以才这样写

   // 是用List是因为CPU可能不止一个
   private List<String> cpuIds;

  /**
   * 这是一个私有的构造函数 目的是禁止此类被外部实例化,因为这是一个单例模式
   */
  private SystemInfo() {
     NULL_STRING_LIST = new ArrayList<String>();
    this.cpuIds = getCPUIDs();
  }

  public static SystemInfo getInstance() {
    return INSTANCE;
  }

  public List<String> getCPUID() {
    return this.cpuIds;
  }
  
  private List<String> getCPUIDs() {
    try {
      return Arrays.asList(...);
    } catch (Exception e) {
      return NULL_STRING_LIST; // 见【第27条】返回零长度的数组而不是null,推想一下ArrayList也是同理
    }
  }
}

   

使用方法: String myCpu = SystemInfo.getInstance().getCPUID();

这个例子综合了【前4条】的知识,并且还包括了【第27条】,是我能想出的比较好的例子了。

【第1条】它提供了静态工厂方法,用于代替公有构造函数

【第2条】它使用私有构造函数来强化它是个单例模式

【第3条】它使用私有构造函数来强化不可实例化的能力(但与第3条不完全相同,它允许内部实例化一次)

【第27条】异常时返回零长度的数组而不是null,避免使用者需要过多的保护性代码

 

 

 进而这段代码的一个细微变形版本如下,不再是单例模式,而是不可实例化的工具类了。(是【第2条】的进一步,完全满足【第3条】

public class SystemInfo {

  private static final SystemInfo INSTANCE = new SystemInfo();
  private static final List<String> NULL_STRING_LIST = new ArrayList<String>();

   // 是用List是因为CPU可能不止一个
   private static List<String> cpuIds;

  /**
   * 这是一个空的私有的构造函数 目的是禁止此类被实例化,因为这是一个工具类  */
   private SystemInfo() {
   }

   static{
      cpuIds = getCPUIDs();
   }

  public static SystemInfo getInstance() {
    return INSTANCE;
  }

  public List<String> getCPUID() {
    return cpuIds;
  }
  
  private static List<String> getCPUIDs() {
    try {
      return Arrays.asList(...);
    } catch (Exception e) {
      return NULL_STRING_LIST; // 见【第27条】返回零长度的数组而不是null,推想一下ArrayList也是同理
    }
  }
}

 

以上两段哪种写法更好呢?可能仁者见仁吧,实际中我使用的是后者,而且是把这个类实现了一个接口,因为考虑到可能会有不同版本的“取得系统信息”的方法(Win32的、Linux的、JNI的...)。

而且当使用Spring的注入后,getInstance()方法 和 INSTANCE 常量也不再是必须的了(Spring的注入不在现在的讨论之内)

 

 

 

【Effective Java 学习笔记】系列连载专题请见:
http://tonylian.iteye.com/categories/64208

 

分享到:
评论

相关推荐

    effective Java(第3版)各章节的中英文学习参考(已完成).zip

    5 条优先依赖注入而不是硬连线资源(依赖注入硬件连接资源)第 6 条避免创建不必要的对象(避免创建不必要的对象)第 7 项消除过时的对象引用(排除时的对象引用)第8条避免使用终结器和清除器(避免使用终结器和...

    解决实例化时自动补全不必要的单词问题

    - **禁用不必要的插件**:部分第三方插件可能会对IDE性能造成影响,关闭不必要的插件可以减轻这种影响。 3. **手动干预**: - **使用快捷键取消补全**:大多数IDE都提供了取消自动补全的快捷键,如Esc键等。 - *...

    第四章示例代码__对象的作用域

    在编程领域,对象的作用域是理解面向对象编程(OOP)的关键概念之一。它涉及到一个变量或对象在程序中的可见性和生命周期,对于...通过对第四章示例代码的学习,开发者将能更好地理解和应用这些原则,提升编程技能。

    Matlab面向对象编程

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

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

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

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

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

    JS对象与数组参考大全

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

    面向对象设计经验

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

    java设计 原则61条

    第23页强调了类之间关系的重要性,尤其是在定义继承关系时,必须清楚地认识到子类与父类之间的关系,以避免出现不必要的耦合。 **原则12:统一异常处理策略** 在第30页提到,整个系统应当采用一致的异常处理机制,...

    Visual.C++面向对象与可视化程序设计(第2版)

    9. **性能优化**:探讨C++和MFC中的性能优化策略,如减少内存分配、避免不必要的对象复制等。 10. **实践项目**:通过实例项目将所学知识应用于实际,提升编程技能。 书中的"更多免费资源编程教程.url"可能是指向...

    vs2008下C++对象内存布局

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

    面向对象c++题库 答案

    - **抽象性**:指从现实世界中提取出对象的主要特征,忽略不必要的细节。例如,当我们讨论汽车时,关注的是它的颜色、品牌和速度等特性,而不是其制造过程。 - **封装性**:将数据和对数据的操作绑定在一起,并对...

    VC++面向对象与可视化程序设计,黄维通

    8. **性能优化**:面向对象编程可能会带来一定的性能开销,因此书中也会涉及一些优化策略,如合理设计类结构、避免不必要的对象创建和销毁,以及使用STL(Standard Template Library)提高效率。 通过学习《VC++...

    根据不同的模式创建单实例应用程序(3kb)

    3. **懒汉式单例模式**:也称为延迟初始化,只在首次需要时才创建实例,这可以避免不必要的资源消耗。然而,如果不考虑线程安全,懒汉式单例可能会导致多个实例的创建。 4. **饿汉式单例模式**:在程序启动时就立即...

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

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

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

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

    第04章 面向对象(上) 13 单例设计模式

    3. **双检锁/双重校验锁(DCL,线程安全)**:这种方式在多线程环境下也能确保单例的正确性,同时避免了不必要的同步开销。 ```java public class Singleton { private volatile static Singleton instance; ...

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

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

    通过面向上下文属性获取面向上下文对象的调用情况(日志)

    总结来说,要实现“通过面向上下文属性获取面向上下文对象的调用情况(日志)”,你需要: 1. 定义`IContextAttribute`接口,定义上下文记录的相关方法。 2. 创建一个日志服务,使用内置的`System.Diagnostics....

Global site tag (gtag.js) - Google Analytics