- 浏览: 5631 次
- 性别:
- 来自: 南京
最近访客 更多访客>>
最新评论
-
老马睡不醒:
直接设置为0的方式不可取。可能造成套接字缓冲区里的数据还来不及 ...
解决Mina的传输中造成的TIME_WAIT过多的问题(不能立即断开连接)
总叙述:什么时候、如何创建对象;什么时候、如何避免创建对象;如何保证对象能够适时地销毁;对象被销毁之前如何管理各种清理工作。
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中创建对象的几种方法以及对象的销毁。 首先,我们来看看创建对象的基本方式——构造器。构造器是每个类都...
这个方法接受两个参数:前缀和后缀,返回一个`File`对象表示新创建的临时文件。例如: ```java import java.io.File; import java.io.IOException; public class TempFileExample { public void createTempFile()...
在Java编程语言中,对象的生命周期和销毁是一个关键的概念,它涉及到内存管理和性能优化。下面我们将详细讨论Java中对象的创建、使用以及销毁的过程。 首先,对象的创建通常通过`new`关键字完成,这会在堆内存中...
在Java、C++等编程语言中,线程池被广泛应用,用于管理线程的生命周期,避免频繁创建和销毁线程带来的开销。本文将基于提供的"ThreadPool.cpp"和"ThreadPool.h"文件,深入解析线程池的实现原理及其自动管理线程创建...
- 对象的创建、初始化和销毁 - GUI组件的使用和布局管理 - 事件处理和监听器 - 实例代码和解释,帮助理解概念 通过深入学习这个PPT,无论你是初学者还是有一定经验的开发者,都能进一步提升Java编程技能,特别是...
2. **生命周期**:`new`创建的对象由程序员控制释放(如Java和C++需要手动`delete`,C#有垃圾回收机制)。栈对象由编译器自动管理,当离开其作用域时自动销毁。 3. **初始化**:`new`创建的对象通常通过构造函数...
本项目是一个基于Java的对象池管理系统,旨在通过对象池技术减少频繁创建和销毁对象所带来的开销,从而提高系统性能和资源利用率。对象池技术允许在需要时从池中获取已存在的对象,而不是每次都创建新对象,使用完毕...
这有助于减少对象的创建和销毁带来的开销,特别是在频繁创建和销毁对象的场景下,如数据库连接、线程、Socket等。本篇文章将深入探讨Java对象池的实现原理,以及如何借鉴"Jakarta Commons Pool"组件来设计一个轻量级...
如 Java 语言平台中,创建对象主要有两种方式,分别为利用 NEW关键字来创建(这是对象创建的主要方式)与不适利用 NEW 关键字来创建。假如 Java 语言中某些对象不是利用 new 关键字为对象在内存中分配一块存储区域,...
Java对象池是一种优化资源管理的技术,它通过复用已经创建并初始化过的对象,避免了频繁地创建和销毁对象带来的性能开销。在Java中,对象池通常用于数据库连接、线程、Socket等昂贵资源的管理。下面我们将深入探讨...
1. **减少创建与销毁开销**:频繁地创建和销毁对象会导致大量的CPU时间消耗在内存分配和垃圾回收上,而对象池能够有效减少这种开销。 2. **节省内存资源**:对象池限制了对象的最大数量,避免了大量临时对象占用内存...
- **对象重用**:通过避免频繁地创建和销毁对象来提高性能。 - **资源管理**:有效地管理对象生命周期,包括对象的创建、使用和回收过程。 #### 对象池技术的重要性 对象池技术的核心优势在于能够显著降低对象创建...
3. 使用池化资源:对于一些昂贵的资源,如数据库连接、线程等,可以使用池化技术,复用已创建的对象,减少创建和销毁的成本。 4. 优化API设计:如`BetterRegExpMatcher`所示,设计API时考虑减少对象的传递和返回,...
Java对象的生命周期是一个关键概念,涉及到对象从创建到销毁的整个过程。理解这一过程对于高效地编写和管理Java程序至关重要。 #### 创建对象的方式 对象的创建是生命周期的起点,通常通过以下几种方式实现: 1. ...
因此,在Java虚拟机中,对象是基本的内存管理单元,对象的创建、管理和销毁都是内存管理的重要组成部分。 本文研究了一种支持复合对象的Java虚拟机内存管理技术,通过修改Dalvik虚拟机,使其支持复合对象的创建和...
1. **PoolableObjectFactory**:这是一个接口,定义了创建、初始化、激活、钝化、验证和销毁对象的方法。它是对象生命周期管理的关键,确保对象在池中的状态是可控的。 2. **ObjectPool**:这是另一个接口,代表...
这个对象池的存在是因为对于小整数(-128到127之间),`Integer`对象通常会被频繁地创建和销毁,这可能导致不必要的内存开销。因此,当尝试创建一个新的`Integer`对象时,如果其值在-128到127之间,JVM会直接从对象...
静态成员不会因为对象的创建和销毁而受到影响,它们在程序运行期间一直存在。 总的来说,Java面向对象编程提供了一种强大的工具集,允许开发者构建复杂、可扩展且易于维护的软件系统。掌握这些核心概念和机制,是...