第二章、创建和销毁对象

第一条:考虑静态工厂方法代替构造器

静态工厂方法与构造器相比的优势:

  1. 有名称;
  2. 不必再每次调用他们的时候都创建一个新对象;
  3. 可以返回原返回类型的任何子类型的对象;
  4. 在创建参数化类型实例时,代码更加简洁。

静态工厂方法的缺点

  1. 类如果不含公有的或者受保护的构造器,就不能被子类化;
  2. 它们与其他的静态方法实际上没有任何区别,无法标记(通过命名规则来标记)。

第二条:遇到多个构造器参数时考虑用构建器Builder

当构造器有多个参数时常用的方法:

  1. 重叠构造(telescoping constructor):缺点在于客户端代码难以编写且难阅读

  2. JavaBean模式,调用一个无参构造器来创建对象,然后调用setter方法来设置必要的参数。
    缺点在于构造过程被分到了多个调用中,可能存在不一致的状态,安全性能不能保证(需付出额外的努力);且对象不能是不可变的。

  3. Builder模式:安全性和可读性并存,缺点在于开销大,可能影响性能
    Builder模式:不直接生成对象,让客户端利用所有必要的参数调用构造器,得到一个builder对象,然后客户端在builder对象上调用类似于setter方法来设置每个相关的可选参数,最后,调用无参的build方法来生成不可变的对象。

下面是一个Builder方法的实例

/**
 * Created by laneruan on 2017/6/2.
 * 遇到多个参数考虑使用构建器Builder
 * Java传统的抽象工厂实现是Class类
 * 优点:安全,直观
 * 缺点:开销略大,注重性能情况下需考虑。
 */
public class BuilderPattern {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static class Builder{
        private final int servingSize;
        private final int servings;

        private int calories = 0;
        private int fat = 0;
        private int carbohydrate = 0;
        private int sodium = 0;

        public Builder(int servingSize,int servings){
            this.servingSize = servingSize;
            this.servings = servings;
        }
        public Builder calories(int val){
            calories = val;
            return this;
        }
        public Builder fat(int val){
            fat = val;
            return this;
        }
        public Builder sodium(int val){
            sodium = val;
            return this;
        }
        public Builder carbohydrate(int val){
            carbohydrate = val;
            return this;
        }
        public BuilderPattern build(){
            return new BuilderPattern(this);
        }
    }
    private BuilderPattern(Builder builder){
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat  = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
    BuilderPattern cocaCola = new Builder(240,8).calories(100).sodium(35).calories(27).build();
}

第三条:用私有构造器或枚举类型来强化Singleton属性

Singleton指仅仅被实例化一次的类,通常被用来代表那些本质上唯一的系统组件。如:窗口管理器或文件系统。

实现Singleton的方法:

  1. 构造器保持为私有,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例。公有域方法的主要好处在于:组成类的成员声明很清楚地表明这个类是一个Singleton:公有的静态域是final的。

     public class Singleton {
         public static final Singleton INSTANCE = new Singleton();
         
         private Singleton(){}
         
         public void leaveTheBuilding(){}
     }
    

    私有构造器只用了一次,用来实例化共有的静态final域Singleton.INSTANCE。

  2. 第二种对应于第一种方法公有的成员是个静态工厂方法Singleton.getInstance()。

     public class Singleton{
    
         private static final Singleton INSTANCE = new Singleton();
     
         private Singleton(){}
     
         public static Singleton getInstance(){ return INSTANCE;}
    
         public void leaveTheBuilding(){}
     }
    

    注意:前两种方法实现的Singleton的类序列化的时候不仅仅加上implements Serializable,为了维护并保证Singleton,必须声明所有的实例域都是瞬时的(transient)并且提供一个readResolve方法,否则每次反序列化一个序列化的实例时,都会创建一个新的实例。

     //readResolve preserve singleton property
     
     private Object readResolve(){
     
         return INSTANCE;
     
     }
    
  3. 第三种方法:编写一个包含单个元素的枚举类型(java 1.5后),更加地简洁,无偿地提供了序列化机制

     public enum Singleton{
    
         INSTANCE;
    
         public void leaveTheBuilding(){}
    
     }
    

第四条、通过私有构造器强化不可实例化的能力

有时候需要编写只包含静态方法和静态域的类,这样的工具类utility class不希望被实例化,然而,在缺少显式构造器的情况下,编译器会自动提供一个共有的,无参的缺省构造器,因此,我们只需让这个类包含私有构造器,它就不能被实例化。由于显式的构造器是私有的,所以不能再该类的外部访问它。

副作用:使一个类不能被子类化,因为子类没有可访问的超类构造器可调用。


第五条、避免创建不必要的对象

  1. 最好能重用对象而不是在每次需要的时候就创建一个相同功能的新对象。重用方式既快速又流行,如果对象是不可变的(immutable),它就始终可以被重用。

  2. 对于同时提供静态工厂方法和构造器的不可变类,通常可以使用静态工厂方法而不是构造器,以避免创建不必要的对象。

  3. 也可以重用那些已知不会被修改的可变对象,可以用一个静态的初始化器(initializer)避免调用方法就创建对象效率低下的情况。

  4. 适配器adapter(有时候也叫作视图(view)):它把功能委托给一个后备对象,从而为后备对象提供一个可以替代的接口。由于适配器除了后备对象之外,没有其他的状态信息,所以针对某个给定对象的特定适配器而言,它不需要创建多个适配器实例。比如Map接口的keySet方法返回该Map对象的Set视图,其中包含该Map中所有的键。粗看起来,仿佛每次调用keySet都应该创建一个新的Set实例,但是对于给定的Map对象,所有返回的对象在功能上是相同的:当其中一个返回对象发生变化的时候,所以其他的对象也要发生变化,因为它们是由同一个Map实例支撑的。

  5. 自动装箱(autoboxing):它允许程序员将基本类型和装箱基本类型混用,按照需要自动装箱和拆箱。这样使得两者之间的差别变得模糊起来,但是,两者在性能上具有明显的区别。要优先使用基本类型而不是装箱类型(如long和Long在多重循环中开销不同)

静态初始化器用法的实例:

import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

/**
 * Created by laneruan on 2017/6/5.
 */
public class staticInitializer {
    //this is a person class
    private final Date birthDate = new Date();
    //other fields,methods and constructor omo=itted
    //Don't do this!
    public boolean isBabyBoomer(){
        //每次被调用时,会新建一个Calendar,一个TimeZone和两个Date类,这是不必要的
        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;
    }

    //改进后的类只需在初始化的时候创建各实例一次,而不是在每次调用方法的时候都创建这些实例。
    //而且将对象从局部变量改为final静态域,使得代码更易于理解。
    private static final Date BOOM_START;
    private static final Date BOOM_END;
    static{
        Calendar gmtCal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
        gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
        BOOM_START = gmtCal.getTime();
        gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
        BOOM_END = gmtCal.getTime();
    }
    public boolean isBabyBoomer2(){
        return birthDate.compareTo(BOOM_START) >= 0 &&
                birthDate.compareTo(BOOM_END)<0;
    }
}

第六条、消除过期的对象引用

  1. 内存泄漏的第一个常见来源:过期引用

    如果一个栈先增长,然后再收缩,那么从栈中弹出来的对象将不会被当作垃圾回收,即使使用栈的程序不再引用这些对象,它们也不会被回收。因为栈内部维护着对这些对象的过期引用(obsolete reference)。如果一个对象的引用被无意识地保留起来了,那么垃圾回收机制将不会处理这个对象,而且也不会处理这个对象所引用的其他对象。即使只有少量的几个对象被无意识地保留下来,也会有许许多多的对象被排除在GC之外。
    修复这种方法很简单:一旦对象引用已经过期,只需清空这些引用即可(这只是一种例外,而不是规范的行为)。
    清除过期引用的另一个好处是如果以后又被错误地解除引用,程序就会立即抛出NullPointerException,而不是默默地错误运行下去。

    何时应该清空引用呢?因为Stack类自己管理内存,数据池包含了elements数组的元素,数组活动区域中的元素是已分配的,而其余部分的元素则是自由的,但是垃圾回收器并不知道这一点,elements数组中的所有对象引用都同等有效,所有一旦数组元素变成了非活动部分的一部分,程序员就手动清除这些数组元素。只要类是自己管理内存,就应该警惕内存泄露的问题。

  2. 内存泄露的另一个常见来源是缓存

    一旦把对象引用放入缓存中,很容易遗忘。
    解决方案:当缓存过期后它们就会自动被清除。

  3. 内存泄露的第三个常见来源是监听器和其他回调

    如果你实现了一个API,客户端在这个API中注册回调,却没有显式地取消注册,那么除非你采取某些行动,否则它们就会集聚,确保回调立即被当做垃圾回收的最佳方法是只保存它们的弱引用,例如,只将它们保存成WeakHashMap中的键


第七条、避免使用终结方法finalizer

为何避免使用终结方法:

finalizer通常是不可预测的,也是危险的,一般情况下是不必要的。可能会导致行为不稳定,降低性能,以及可移植的问题。

终结方法的主要缺点在于不能保证会被及时地执行。从一个对象变得不可达开始到执行它的终结方法,所花费的时长是任意的。Java的语言规范不仅不保证终结方法会被及时地执行,而且根本就不能保证它们会被执行!

提供一个显式的终结方法:如InputStream、OutputStream和java.sql.Connection上的close方法。显式的终结方法通常与try-finally结构结合使用,确保及时终止。

终结方法的优点:

第一种用途是充当安全网,迟一点释放关键资源总比永远不释放要好。

第二个合理的用途是与对象的本地对等体(native peer)有关。本地对等体是一个本地对象,普通对象通过本地方法委托给一个本地对象,因为本地对等体不是一个普通的对象,所以垃圾回收器不会知道他,当他的java对等体被回收,它不会被回收。当本地对等体并不具有关键资源的前提下,终结方法正是执行这种任务的最合适工具。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271

推荐阅读更多精彩内容