《Effective Java》学习笔记 第二章 创建和销毁对象

第二章 创建和销毁对象

何时以及如何创建对象,何时以及如何避免创建对象,如何确保他们能够适时地销毁,以及如何管理对象销毁之前必须进行的各种清理动作。

1.考虑用静态工厂方法代替构造器

一般在某处获取一个类的实例最常用的方法是提供一个共有的构造器,还有一种方法,就是提供一个共有的静态工厂(static factory method),他只是一个返回类的实例的静态方法。

例:

 public static Boolean valueOf(boolean b){
    return b ? Boolean.TRUE:Boolean.FALSE;
 } 

注意,静态工厂方法与设计模式中的工厂方法模式不同。类可以通过静态工厂方法来提供给它的客户端,而不是通过构造器,提供静态工厂方法而不是公有的构造器,这样做具有几大优势:

优势

  • 静态工厂方法,它们有名称
    • 例如构造器BIgInteger(int,int,Random)返回的BigInteter可能为素数,如果用名为BigInteger.probablePrime的静态工厂方法来表示,显然更为清楚。
  • 不必在每次调用它们的时候都创建一个新的对象
    • 这使得不可变类可以使用预先构建好的实例,或者将构建好的实例缓存起来,进行重复利用,从而避免常见不必要的重复对象,因为程序经常请求创建相同的对象,那么创建对象的代价会很高。Boolean.valueOf(boolean)方法说明了这项技术。静态工厂方法也经常用于实现单例模式。
  • 它们可以返回原返回类型的任何子类型的对象

灵活的静态工厂方法构成了服务提供者框架(Service Provider FrameWork)的基础,例如JDBC。
服务提供者框架:多个服务提供者实现一个服务,系统为服务提供者的客户端提供多个实现,并把他们从多个实现总解耦出来。三个组件:服务接口,这是服务提供者实现的;提供者注册API,这是系统用来注册实现,让客户端访问他们的;服务访问API,是客户端用来获取服务的实例的。
对于JDBC,Connection就是它的服务接口,DriverManager.registerDriver是提供者注册API,DriverManager.getConnection是服务访问API,Driver就是服务提供者接口。
例,下列简单的实现包含了一个服务提供者接口和一个默认提供者:

/**
 * Created by Newtrek on 2017/10/31.
 * 服务提供者接口
 */
public interface Provider {
//    提供服务实例,用服务接口返回
    Service newService();
}
/**
 * Created by Newtrek on 2017/10/31.
 * 服务接口
 */
public interface Service {
    // TODO: 2017/10/31  服务特有的方法 写在这儿
}

/**
 * Created by Newtrek on 2017/10/31.
 */
public class Services {
//    构造保护
    private Services(){}
//    provider映射表,保存注册的Provider
    private static final Map<String ,Provider> providers=new ConcurrentHashMap<>();
//    默认provider的名字
    public static final String DEFAULT_PROVIDER_NAME="<def>";
//    注册默认的Provider
    public static void registerDefaultProvider(Provider p){
        registerProvider(DEFAULT_PROVIDER_NAME,p);
    }
//    注册provider
    public static void registerProvider(String name,Provider p){
        providers.put(name,p);
    }

    /**
     * 静态工厂方法返回Service实例
     */
    public static Service newInstance(){
        return newInstance(DEFAULT_PROVIDER_NAME);
    }
    public static Service newInstance(String name){
        Provider p = providers.get(name);
        if (p==null){
            throw new IllegalArgumentException("No provider registered with name:"+name);
        }
        return p.newService();
    }
}
  • 在创建参数化类型实例的时候,它们是代码变得更加简洁

例:假设HashMap提供了这个静态工厂

 public static <K,V> HashMap<K,V> newInstance(){
    return new HashMap<K,V>();
 } 

那么就可以用下面简洁的代码获取实例了。

 Map<String,List<String>> m=HashMap.newInstance(); 

把这些方法放在自己的工具类中是很实用的。不过现在java7,java8已经实现了HashMap构造的类型参数推测

缺点

  • 类如果不含公有的或者受保护的构造器,就不能被子类化
  • 它们与其他的静态方法实际上没有任何区别
    • 在API文档中,它们没有像构造器那样在API文档中明确标识出来,因为,对于提供了静态工厂方法二不是构造器的类来说,要想查明如何实例化一个类,这是非常困难的。可以通过在类或者接口注释中关注静态工厂,并遵守标准的命名习惯,可以弥补这一劣势。下面是静态工厂方法的一些惯用名称。
      • valueOf:实际上是类型转换方法。
      • of:valueOf的一种更为简洁的代替
      • getInstance:返回的实例是通过方法的参数来描述的,但是不能够说与参数具有同样的值。对于Singleton来说,该方法没有参数,并返回唯一的实例。
      • newInstance:像getInstance一样,但newInstance能够确保返回的每个实例都与所有其它实例不同。
      • getType:像getInstance一样,但是在工厂方法处于不同的类中的时候使用。Type表示工厂方法所返回的对象类型。
      • newType:像newInstance一样,但是在工厂方法处于不同的类中的时候使用,Type表示工厂方法所返回的对象类型。

简而言之,静态工厂方法和公有构造器都各有用处,我们需要理解他们各自的长处。静态工厂通常更加合适,因此切忌第一反应就是提供公有的构造器,而不先考虑静态工厂。

2.遇到多个构造器参数时要考虑用构建器

这个就是Builder设计模式

3.用私有构造器或者枚举类型强化Singleton属性

这个是单例模式的注意事项,选择最好的单例模式实现

4.通过私有构造器强化不可实例化的能力

有时候需要编写一些只包含静态方法和静态域的类,这些类的名声很不好,因为有些人在面向对象的语言中滥用这样的类来编写过程化的程序,尽管如此,他们也确实有它们的好处,比如常见的工具类java.lang.Math等,都是这样。方正这样不可以实例化的类,最好把他的构造器设置为私有。

例如:

public class UtilityClass{
  private UtilityClass(){
      throw new AssertionError();
  }
}

5.避免创建不必要的对象

一般来说,最好能重用对象而不是再每次需要的时候就创建一个相同功能的新对象,如果对象是不可变的,他就始终可以被重用。
简单的例子:字符串

String s = new String("stringtest");//不要这样做,因为该语句每次执行的时候都会创建一个新的String实例,没必要
// 改进后的版本,这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例。对于再同一台虚拟机中运行的代码,只要它们包含相同的字符串自字面常量,该对象就可以被重用。
String s = "stringtest";

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

优先使用基本类型,而不是装箱基本类型,当心无意识的自动装箱。

也不要错误地认为本条目暗示着“创建对象的代价非常昂贵,我们应该尽可能地避免创建对象”,相反,由于小对象地构造器制作很少量地显示工作,所以,小对象地创建和回收动作是非常廉价地。

通过维护自己的对象池来避免创建对象比不是一种好的做法,除非池中的对象是非常重量级的,一般数据库连接池常用。

6 消除过期的对象引用

不要以为Java有垃圾回收机制,能自动管理内存,自动回收垃圾,就可以不管了,其实不然。
内存泄漏的例子


public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_CAPACITY = 16;

    public Stack(){
        elements = new Object[DEFAULT_CAPACITY];
    }

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

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

    private void ensureCapacity(){
        if (elements.length == size){
            elements = Arrays.copyOf(elements,size*2+1);
        }
    }

}

这段程序并没有明显的错误,如果是栈先是增长,然后再收缩,那么,从栈中弹出来的对象将不会被当作垃圾回收,因为里面的数组里引用着它,栈内部维护着这些对象的过期引用,过期引用就是指永远也不会再被解除的引用。

这类问题的修复方法很简单:一旦对象引用已经过期,只需清空这些应用即可。

没必要对于每一个对象引用,一旦程序不再用到它,就把它清空。清空对象引用应该是一种例外,而不是一种规范行为,消除过期引用最好的办法是让包含该对象的变量结束其生命周期。一般而言,只要类是自己管理内存,程序员就应该警惕内存泄漏问题,一旦元素被释放掉,则该元素中包含的任何对象引用都应该被清空。

内存泄漏的另一个常见来源是缓存,一旦你把对象放在缓存中,他就很容易被遗忘掉,从而使得它不再有用之后很长一段时间内仍然留在缓存中。可以用WeakHashMap

内存泄漏的第三个常见来源是监听器和其他回掉,一般都要取消注册,或者用弱引用

内存泄漏通常不会表现成明显的失败,所以他们可以再一个系统中存在很多年,往往通过仔细检查代码,借助于Heap刨析工具才能发现内存泄漏问题。

7 避免使用终结方法

终结方法(finalizer)通常是不可预测的,也是很危险的,一般情况下是不必要的。

C++的析构器是回收一个对象所占用资源的常规方法,是构造器所必须的对应物,也可以用来回收其他的非内存资源,而在Java中,一般用try-finally块来完成类似的工作

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • 第2章 创建和销毁对象 第1条:考虑用静态工厂方法代替构造方法 静态工厂方法与构造方法的不同 优点: 静态工厂方法...
    QM阅读 579评论 0 51
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,103评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,296评论 18 399
  • Shaoerting (芯妈)@2017.11.02周四 1、感受:欢喜、紧张、坦然 2、进步: 芯芯试穿...
    Shaoerting阅读 188评论 1 2
  • 锤子便签怎么了?我无意中随时会不小心就把以前写的内容删除,那就没有一点记录积累了,昨天把丢失的整理了一下,不全,还...
    依盈阅读 131评论 0 0