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

创建和销毁对象(JAVA)

    博客分类:
  • JAVA
阅读更多

总叙述:什么时候、如何创建对象;什么时候、如何避免创建对象;如何保证对象能够适时地销毁;对象被销毁之前如何管理各种清理工作。

item1:考虑用静态工厂方法代替构造函数

类可以提供一个公有的静态工厂方法,实际上只是一个简单的静态方法,它返回的是类的一个实例。
例如:Boolean类的简单例子<他把一个boolean原语值转换为一个Boolean对象引用。
public static Boolean valueOf(boolean b){
    return (b ? Boolean.TRUE : Boolean.FALSE);
}
类可以为他的客户提供一些静态工厂的方法,来替代构造函数,或者同时也提供一些构造函数。用静态工厂方法来代替公有的构造函数,就有好处也有不足之处。


静态工厂方法的一个好处是,与构造函数不同,静态工厂方法具有名字。如果一个构造函数的参数没有确切地描述被返回的对象,那么选用适当名字的静态工厂方法可以使一个类更易于使用,并且相应的客户代码更易于阅读。一个类只能有一个原型相同的构造函数。那么我们怎样绕开这个限制呢,他可以提供两个共造函数,它们的参数列表只是在参数类型的顺序上有所不同。这并不是一个好主意,面对这样的API,用户永远也记不住该用哪个构造函数,如果常常会调用到错误的构造函数上。并且,读到使用这样的构造函数的代码时往往会不知所云,除非去查看该类的文档。
因为静态工厂方法有自己的名字,所以它们没有构造函数那样的限制,对于给定的原型特征可以有不止一个静态工厂方法。如果一个类看起来需要多个构造函数,并且它们的原型特征相同,那么你应该考虑用静态工厂方法来代替其中一个或多个构造函数,并且慎重选择他们的名字以便明显地标出他们的不同。

 


静态工厂方法的第二个好处是,与构造函数不同,它们每次被调用的时候,不要求非得创建一个新的对象。这使得一些非可变类可以使用一个预先构造好的实例,或者把已经构造好的实例缓存起来,以后再把这些实例分发给用户,从而避免创建不必要的重复对象。Boolean.valueOf(boolean)方法说明了这项技术:它从来不创建对象。如果一个程序要频繁地创建相同的对象,并且创建对象的代价很昂贵,则这项技术可以极大地提高性能。
静态工厂方法可以为重复的调用返回同一个对象,这也可以被用来控制“在某一时刻哪些实例应该存在”。这样做有两个理由:第一,他使得一个类可以保证是一个 singleton。第二,他使非可变类可以保证“不会有两个相等的实例存在”,即当且仅当a==b的时候才有a.equals(b)为true。如果 一个类保证了这一点,那么它的客户就可以用==操作符来代替equals(Object)方法,其结果是实质性的性能提高。


静态工厂方法的第三个好处是,与构造函数不同,它们可以返回一个原返回类型的子类型的对象。使用这样的静态工厂方法,可以强迫客户通过接口来引用被返回的对象,而不是通过实现类来引用被返回的对象,这是一个很好的习惯。


公有的静态工厂方法所返回的对象的类不仅可以是非公有的,而且该类可以随着每次调用而发生变化,这取决于静态工厂方法的参数值,只要是已声明的返回类型的子类型,都是允许的,而且,为了增强软件的可维护性,返回对象的类型也可以随着不同的发行版本而不同。

静态工厂方法返回 的对象所属的类,在编写包含该静态工厂方法的类时可以并不存在。


静态工厂方法的主要缺点是,类如果不含公有的或者受保护的构造函数,就不能被子类化。

静态工厂方法的第二个缺点是,它们与其他的静态方法没有任何区别。

总的来说,静态工厂方法和公有的构造函数方法都有他们各自的用途,我们需要理解他们各自的长处。如果你正在权衡这两种选择,又没有其他因素强烈地影响你的选择,那么你最好还是简单地使用构造函数,毕竟他是语言提供的规范。

 

item2.使用私有构造函数强化singleton属性

singleton是这样的类,它只能实例化一次。singleton通常被用来代表那些本质上具有惟一性的系统组件,比如视频显示或者文件系统。

实现singleton有两种方法,这两种方法都要把构造函数保持为私有的,并且提供一个静态成员,以便允许客户能够访问该类惟一的实例:在第一种方法中,公有静态成员是一个final域:

public class Elvis{
    public static final Elvis INSTANCE =new Elvis();
    private Elvis(){
    }
}

私有构造函数仅被调用一次,用来实例化公有静态final域Elvis.INSTANCE。由于缺少公有的或者受保护的构造函数,所以保证了 Elvis的全局惟一性。一旦Elvis类被实例化之后,只有一个Elvis实例存在--不多也不少。客户的任何行为都不会改变这一点。


第二种方法提供了一个私有的静态工厂方法,而不是公有的静态final域:
public class Elvis{
   private static final Elvis INSTANCE =new Elvis();
   private Elvis(){}
   public static Elvis getInstance(){
       return INSTANCE;
   }
}
所有对于静态方法Elvis.getInstance的调用,都会返回同一个对象的引用,所以,不会有别的Elvis实例被创建。

第一种方法主要好处是:组成类的成员的声明很清楚地表明了这个类是一个singleton。公有的静态域是final的,所以该域将总是包含相同的对象引用。第一种方法可能在性能上稍微领先,但是在第二种方法中,一个优秀的JVM实现应该能够通过将静态工厂方法的调用内联化,来消除这种差别。

第二种方法的主要好处在于,他提供了灵活性:在不改变API的前提下,允许我们改变想法,把该类作成singleton,或者不做singleton.sigleton的静态工厂方法返回该类的惟一实例,但是,他也很容易被修改,比如说,为每个调用该方法的线程返回一个惟一的实例。

总而言之,如果你确信该类将永远是一个singleton,那么使用第一种方法是由意义的。如果你想保留一点余地,那么请使用第二种方法。

 

item3: 通过私有构造函数强化不可实例化的能力

由于只有当一个类不包含显式的构造函数的时候,编译器才会生成默认构造函数,所以,我们只要让这个类包含单个显式的私有构造函数,则它就不可被实例化了。

public class UtilityClass{
    private UtilityClass(){}
}

因为显式构造函数是私有的,所以在该类的外部它是不可被访问的。假设该构造函数不会被类自身从内部调用,就能保证该类永远不会被实例化。
这种习惯用法也有副作用,它使得一个类不能被子类化。所有的构造函数都必须要调用一个可访问的超类构造函数,无论显式或隐式地调用,在这种情况下,子类就没有可访问的构造函数来调用了。


item4:避免创建重复的对象
重复使用同一个对象,而不是每次需要的时候就创建一个功能上等价的新对象,通常前者更为合适。
例如:
String s=new String("silly");
该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作没有一个是真正必需的.传递给String构造函数的实参 ("silly")本身就是一个String实例,功能上等同于所有的构造函数创建的对象。如果这种用法是在一个循环中,或者是在一个被频繁调用的方法中,那么成千上万不必要的String实例会被创建出来。

改进版本:String s="no longer silly";

这个版本只使用一个String实例,而不是每次执行的时候创建一个新的实例。而且,它可以保证,对于所有在同一个虚拟机中运行的代码,只要它们包含相同的字符串常量,则该对象就会被重用。

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

item5:消除过期的对象引用


对于一种具有垃圾回收功能的语言,我们工作可以很容易,因为当我们用完了对象之后,它们会被自动回收,但就认为自己不再需要考虑内存管理的事情了,实际上这是不正确的。
例如:
public class Stack{
   private Object[] elements;
   private int size=0;
   
   public Stack(int initialCapacity){
       this.elements=new Object[initialCapacity];
   }
  
   public void push(Object e){
        ensureCapacity();
        elements[size++]=e;
   }

   public Object pop(){
       if(size==0) throw new EmptyStackException();
       return elements[--size];
   }

   private void ensureCapacity(){
       if(elements.length==size){
           Object[] oldElements=elements;
           elements=new Object[2*elements.length + 1];
           System.arraycopy(oldElements,0,elements,0,size);
       }
   }
}

分析:这个程序没有很显然的错误。无论你如何测试,它都会成功地通过你的每一项测试,但是,这个程序中潜伏着一个问题。不严格地讲,这个程序有一个 “内存泄露”,随着垃圾回收器活动的增加,或者由于不断增加的内存占用,程序性能的降低会逐渐表现出来。那么,程序中哪里会发生内存泄露呢?如果一个栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当做垃圾回收,即使使用栈的客户程序不再应用这些对象,它们也不会被回收。这是因为,栈内部维护着这些对象的过期引用。所谓过期引用,是指永远也不会被解除的引用。在本例中,凡是在elements数组的“活动区域”之外的引用都是过期的,elements的活动区域是指下标小于size的那一部分。

在支持垃圾回收的语言中,内存泄露是很隐蔽的,如果一个对象引用被无意识地保留起来了,那么,垃圾回收机制不仅不会处理这个对象,而且也不会处理被这个对象引用的所有其他对象。即使只有少量的几个对象引用被无意识地保留下来,也会有很多的对象被排除在垃圾回收机制之外,从而对性能造成潜在的重大影响。

要想修复这一类问题也很简单:一旦对象引用已经过期,只需清空这些引用即可。在上述例子的Stack类中,只要一个单元被弹出栈,指向它的引用就过期了。pop方法的修订版本如下所示:
public Object pop(){  
     if(size==0) throw new EmptyStackException();
     Object result=elements[--size];
     elements[size]=null;//Eliminate obsolete reference
     return result;
}

清空过期引用的另一个好处是,如果它们在以后又被错误地解除引用、则程序会立即抛出NullPointerException异常,而不是悄悄地错误运行下去。尽可能早地检测出程序中的错误总是有益的。


对于一个对象的引用,一旦程序不再使用到它,就把他清空。这样做既没必要,也不是我们所期望的,因为这样做会把程序代码弄得很乱,并且可以想象还会降低程序的性能。“清空对象引用”这样的操作应该是一种例外,而不是一种规范行为。消除过期引用最好的方法是重用一个本来已经包含对象引用的变量,或者让这个变量结束其生命周期。如果你是在最紧凑的作用域范围内定义每一个变量,则这种情形就会自然而然地发生。应该注意到,在目前的JVM实现平台上,紧紧退出定义变量的代码是不够的,要想使引用消失,必须退出包含该变量的方法。

那么,何时应该清空一个引用呢?Stack类的哪个方面特性使得它遭受了内存泄露的影响?简而言之,问题在于,Stack类自己管理内存。存储池包含了elements数组的元素。数组的活动区域中的元素是已分配的,而数组其余部分的元素是自由的。但是垃圾回收器并不知道这一点;对于垃圾回收器而言,elements数组中的所有对象引用都同等有效,只有程序员知道数组的非活动区域是不重要的。程序员可以把这个情况告知垃圾回收器,做法很简单:一旦数组元素变成了非活动区域的一部分,程序员就手工清空这些元素。


一般而言,只要一个类自己管理它的内存,程序员就应该警惕内存泄露问题,一旦一个元素被释放掉,则该元素中包含的任何对象引用应该被清空。

内存泄露的另一个常见来源是缓存。一旦你把一个对象引用放到一个缓存中,它就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。对于这个问题,有两种可能的解决方案。如果你正巧要实现这样的缓存:只要在缓存之外存在对某个条目的键的引用,该条目就有意义,那么你可以使用 WeakHashMap来代表缓存;当缓存中的条目过期之后,它们会自动被删除。而更为常见的情形是,“被缓存的条目是否有意义”的周期并不很容易确定,其中的条目会在运行的过程中变得越来越没有价值。在这样的情况下,缓存应该时不时地清除掉无用的条目。这项清除工作可以由一个后台线程来完成,或者也可以在加入新条目的时候做清理工作。


item6:避免使用终结函数


终结函数通常是不可预测的,常常也是很危险的,一般情况下是不必要的。使用终结函数会导致不稳定的行为、更差的性能,以及带来移植性问题。当然终结函数也有可用之处,经验告诉我们:应该避免使用终结函数。

时间关键的任务不应该由终结函数来完成。因为从一个对象变得不可达到开始,到它的终结函数被执行,这段时间的长度是任意的、不确定的。

我们不应该依赖一个终结函数来更新关键性的永久状态。

 


//考虑用静态工厂方法代替构造函数
import java.util.*;
// Provider framework sketch
public abstract class Foo {
     // Maps String key to corresponding Class object
     private static Map implementations = null;

     // Initializes implementations map the first time it's called
     private static synchronized void initMapIfNecessary() {
         if (implementations == null) {
             implementations = new HashMap();
             // Load implementation class names and keys from
             // Properties file, translate names into Class
             // objects using Class.forName and store mappings.
             // ...
         }
     }
     public static Foo getInstance(String key) {
         initMapIfNecessary();
         Class c = (Class) implementations.get(key);
         if (c == null)
             return new DefaultFoo();
         try {
             return (Foo) c.newInstance();
         } catch (Exception e) {
             return new DefaultFoo();
         }
     }
     public static void main(String[] args) {
         System.out.println(getInstance("NonexistentFoo"));
     }
}
class DefaultFoo extends Foo {
}


//使用私有构造函数来强化singleton属性的两种方法
public class Elvis {
     public static final Elvis INSTANCE = new Elvis();
     private Elvis() {        
     }    

     public static void main(String[] args) {
         System.out.println(Elvis.INSTANCE);
     }
}

 

public class Elvis {
     private static final Elvis INSTANCE = new Elvis();

     private Elvis() {
     }

     public static Elvis getInstance() {
         return INSTANCE;
     }
     public static void main(String[] args) {
         System.out.println(Elvis.INSTANCE);
     }
}


//为了让一个singleton类变成可序列化的,仅在声明中加上"implements Serializsble"是不够的,为了维护singleton性,必须也要

提供一个readResolve方法。否则的话,一个序列化的实例在每次反序列化的时候,都会导致创建一个新的实例。
import java.io.*;
public class Elvis {
     public static final Elvis INSTANCE = new Elvis();
     private Elvis() {
         // ...
     }
     // ...   // Remainder omitted
     // readResolve method to preserve singleton property
     private Object readResolve() throws ObjectStreamException {
         /*
          * Return the one true Elvis and let the garbage collector
          * take care of the Elvis impersonator.
          */
         return INSTANCE;
     }
     public static void main(String[] args) {
         System.out.println(Elvis.INSTANCE);
     }
}

 

//通过私有强化不可实例化的能力
public class UtilityClass {

     // Suppress default constructor for noninstantiability
     private UtilityClass() {
         // This constructor will never be invoked
     }

     // ...   // Remainder omitted
}

 

//消除过期的对象引用
import java.util.*;
// Can you spot the "memory leak"?
public class Stack {
     private Object[] elements;
     private int size = 0;

     public Stack(int initialCapacity) {
         this.elements = new Object[initialCapacity];
     }

     public void push(Object e) {
         ensureCapacity();
         elements[size++] = e;
     }

     public Object pop() {
         if (size==0)
             throw new EmptyStackException();
         Object result = elements[--size];
         elements[size] = null; // Eliminate obsolete reference
         return result;
     }

     /**
      * Ensure space for at least one more element, roughly
      * doubling the capacity each time the array needs to grow.
      */
     private void ensureCapacity() {
         if (elements.length == size) {
             Object[] oldElements = elements;
             elements = new Object[2 * elements.length + 1];
             System.arraycopy(oldElements, 0, elements, 0, size);
         }
     }

     public static void main(String[] args) {
         Stack s = new Stack(10);
         for (int i=0; i<args.length; i++)
             s.push(args[i]);
         for (int i=0; i<args.length; i++)
             System.out.println(s.pop());
     }
}

分享到:
评论

相关推荐

    JAVA创建和销毁对象的方法

    本篇文章主要介绍了JAVA创建和销毁对象的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    监听session的创建到销毁

    这是一个用springMVC的项目 是一个监听器 用于监听session的创建、销毁、移除。会触发相应的事件处理

    Java线程池技术详解

    在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一...

    java教学笔记之对象的创建与销毁

    面向对象的编程语言使程序能够直观的反应客观世界的本来面目,并且使软件开发人员能够运用人类认识事物所采用的一般思维方法进行软件开发,是当今计算机领域中软件开发和应用的主流技术。

    java对象实体类属性字段对比变化过程

    这种变化过程通常涉及到对象的创建、使用和销毁等阶段。 在对象的创建阶段,Java对象实体类会根据预设的属性字段和初始值进行初始化。这些属性字段通常定义在类的声明中,并使用关键字进行描述。在实例化一个对象时...

    java高效编程指南

    JAVA高效编程指南 1 创建和销毁对象 2 对象定义 3 类和接口 4 结构的替代

    [java]读书笔记整理:一切都是对象

    创建程序时,java编译器必须知道存储在堆栈内所有数据的确切大小和声明周期,因为它必须生成相应的代码,以便上下移动堆栈指针。这一约束限制了程序的灵活性,所以虽然某些java数据存储于堆栈中——特别是对象引用,...

    Java支持库2.0#0版

    使用时,可直接通过“Java本地接口”的各类方法访问Java类库(即与“Java虚拟机”通信),由支持库本身自动完成“Java虚拟机”的创建和销毁。如果需要特别指定“用户类路径”或“本地库路径”,则必需在使用“Java...

    Effective Java第三版1

    《Effective Java》第三版中文版目录 第一章 介绍 1 第二章 创建和销毁对象 4 1 考虑用静态工厂方法替换构造器 4 2 当遇到多个构造器参

    Java支持库2.0#0版(静态版)

    使用时,可直接通过“Java本地接口”的各类方法访问Java类库(即与“Java虚拟机”通信),由支持库本身自动完成“Java虚拟机”的创建和销毁。如果需要特别指定“用户类路径”或“本地库路径”,则必需在使用“Java...

    Thinking in java4(中文高清版)-java的'圣经'

    2.1 用引用操纵对象 2.2 必须由你创建所有对象 2.2.1 存储到什么地方 2.2.2 特例:基本类型 2.2.3 Java中的数组 2.3 永远不需要销毁对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 创建新的数据类型:类 2.4.1 域和方法 ...

    java 完整版PPT课件

    第01章 HelloWorld,第02章 变量,第03章 操作符与表达式,第04章 语句,第05章 数组,第06章 类,第07章 类的方法,第08章 当前对象,第09章 访问控制与封装,第10章 对象的创建与销毁,第11章 继承,第12章 包,第...

    Java 基础核心总结 +经典算法大全.rar

    节点流和处理流 Java IO 的核心类 File Java IO 流对象 字节流对象InputStream OutputStream 字符流对象Reader Writer 字节流与字符流的转换新潮的 NIO 缓冲区(Buffer)通道(Channel) 示例:文件拷贝案例 BIO 和 NIO ...

    thinking in Java 第四版中文 part1

    2.3 永远不需要销毁对象 2.3.1 作用域 2.3.2 对象的作用域 2.4 创建新的数据类型:类 2.4.1 域和方法 2.4.2 基本成员默认值 2.5 方法、参数和返回值 2.5.1 参数列表 2.6 构建一个Java程序 2.6.1 名字可见性 2.6.2 ...

    从使用到原理学习Java线程池

    在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个...

    java逻辑思维笔试题-effective-java-3rd-edition:有效的Java第3版注释

    创建和销毁对象 03 - 所有对象通用的方法 04 - 类和接口 05 - 泛型 06 - 枚举和注释 07 - Lambda 和流 08 - 方法 09 - 通用编程 10 - 例外 11 - 并发 12 - 序列化 第 2 章 - 创建和销毁对象 第 1 项 - 考虑静态工厂...

Global site tag (gtag.js) - Google Analytics