`
chenweinjupt
  • 浏览: 5631 次
  • 性别: 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编程中,创建和销毁对象是至关重要的操作,它们直接影响到程序的性能和内存管理。本文将深入探讨Java中创建对象的几种方法以及对象的销毁。 首先,我们来看看创建对象的基本方式——构造器。构造器是每个类都...

    Java 中的一个文件临时创建和销毁的例子

    这个方法接受两个参数:前缀和后缀,返回一个`File`对象表示新创建的临时文件。例如: ```java import java.io.File; import java.io.IOException; public class TempFileExample { public void createTempFile()...

    Java中对象的销毁方法分析

    在Java编程语言中,对象的生命周期和销毁是一个关键的概念,它涉及到内存管理和性能优化。下面我们将详细讨论Java中对象的创建、使用以及销毁的过程。 首先,对象的创建通常通过`new`关键字完成,这会在堆内存中...

    TheadPool 线程池源码 自动管理线程的创建和销毁

    在Java、C++等编程语言中,线程池被广泛应用,用于管理线程的生命周期,避免频繁创建和销毁线程带来的开销。本文将基于提供的"ThreadPool.cpp"和"ThreadPool.h"文件,深入解析线程池的实现原理及其自动管理线程创建...

    JAVA 教学 PPt(接口,类和对象尤其详尽)

    - 对象的创建、初始化和销毁 - GUI组件的使用和布局管理 - 事件处理和监听器 - 实例代码和解释,帮助理解概念 通过深入学习这个PPT,无论你是初学者还是有一定经验的开发者,都能进一步提升Java编程技能,特别是...

    用new创建对象和直接定义的区别

    2. **生命周期**:`new`创建的对象由程序员控制释放(如Java和C++需要手动`delete`,C#有垃圾回收机制)。栈对象由编译器自动管理,当离开其作用域时自动销毁。 3. **初始化**:`new`创建的对象通常通过构造函数...

    基于Java的对象池管理系统.zip

    本项目是一个基于Java的对象池管理系统,旨在通过对象池技术减少频繁创建和销毁对象所带来的开销,从而提高系统性能和资源利用率。对象池技术允许在需要时从池中获取已存在的对象,而不是每次都创建新对象,使用完毕...

    Java对象池实现源码

    这有助于减少对象的创建和销毁带来的开销,特别是在频繁创建和销毁对象的场景下,如数据库连接、线程、Socket等。本篇文章将深入探讨Java对象池的实现原理,以及如何借鉴"Jakarta Commons Pool"组件来设计一个轻量级...

    JAVA中销毁一个对象的方法.doc

    如 Java 语言平台中,创建对象主要有两种方式,分别为利用 NEW关键字来创建(这是对象创建的主要方式)与不适利用 NEW 关键字来创建。假如 Java 语言中某些对象不是利用 new 关键字为对象在内存中分配一块存储区域,...

    关于java对象池的例子代码

    Java对象池是一种优化资源管理的技术,它通过复用已经创建并初始化过的对象,避免了频繁地创建和销毁对象带来的性能开销。在Java中,对象池通常用于数据库连接、线程、Socket等昂贵资源的管理。下面我们将深入探讨...

    Java对象池技术的原理及其实现

    1. **减少创建与销毁开销**:频繁地创建和销毁对象会导致大量的CPU时间消耗在内存分配和垃圾回收上,而对象池能够有效减少这种开销。 2. **节省内存资源**:对象池限制了对象的最大数量,避免了大量临时对象占用内存...

    Java对象池技术的原理

    - **对象重用**:通过避免频繁地创建和销毁对象来提高性能。 - **资源管理**:有效地管理对象生命周期,包括对象的创建、使用和回收过程。 #### 对象池技术的重要性 对象池技术的核心优势在于能够显著降低对象创建...

    Java基础:减少对象的创建提高java性能

    3. 使用池化资源:对于一些昂贵的资源,如数据库连接、线程等,可以使用池化技术,复用已创建的对象,减少创建和销毁的成本。 4. 优化API设计:如`BetterRegExpMatcher`所示,设计API时考虑减少对象的传递和返回,...

    java对象的 生命周期

    Java对象的生命周期是一个关键概念,涉及到对象从创建到销毁的整个过程。理解这一过程对于高效地编写和管理Java程序至关重要。 #### 创建对象的方式 对象的创建是生命周期的起点,通常通过以下几种方式实现: 1. ...

    支持复合对象的Java虚拟机内存管理技术研究.pdf

    因此,在Java虚拟机中,对象是基本的内存管理单元,对象的创建、管理和销毁都是内存管理的重要组成部分。 本文研究了一种支持复合对象的Java虚拟机内存管理技术,通过修改Dalvik虚拟机,使其支持复合对象的创建和...

    java对象池化技术[参照].pdf

    1. **PoolableObjectFactory**:这是一个接口,定义了创建、初始化、激活、钝化、验证和销毁对象的方法。它是对象生命周期管理的关键,确保对象在池中的状态是可控的。 2. **ObjectPool**:这是另一个接口,代表...

    Integer创建对象

    这个对象池的存在是因为对于小整数(-128到127之间),`Integer`对象通常会被频繁地创建和销毁,这可能导致不必要的内存开销。因此,当尝试创建一个新的`Integer`对象时,如果其值在-128到127之间,JVM会直接从对象...

    java面向对象

    静态成员不会因为对象的创建和销毁而受到影响,它们在程序运行期间一直存在。 总的来说,Java面向对象编程提供了一种强大的工具集,允许开发者构建复杂、可扩展且易于维护的软件系统。掌握这些核心概念和机制,是...

Global site tag (gtag.js) - Google Analytics