为什么android API 中有很多对象的创建都是使用new关键字

为什么android API 中有很多对象的创建都是使用new关键字?
比起工厂方法、builder模式,java 中不提倡直接使用构造方法创建对象(new),为什么android API 中还是有很多对象的创建都使用构造方法 ?

这只是个草稿

首先,谢邀。
其次,是怎么找到我知乎账号的,我隐藏的这么深(脸红了)
最后,加入了自己的总结概括,让然也可以当成读书笔记来看。

我会很认真,很认真地回答问题哒,毕竟这是第一次回答专业相关的提问 : )

最近在温习《Effective Java》这本书,真的是每一次都有新的收获和认识。从第二章《创建和销毁对象》开始,就涉及了“静态工厂方法”,“构造器创建对象”等概念,篇幅不长,但实用性极强,且概括性极强,可谓句句精辟。

那么回到问题本身,其实在Java中,并不是不提倡直接使用构造函数来创建对象,而是在某些情况下,很难区分究竟调用哪个构造函数来初始化对象,或者说当函数签名类似时,一不小心就使用了错误的构造函数,从而埋下难以发现的隐患,最后付出程序崩溃的代价,等等一系列“眼一花,手一滑”所导致的后果,或多或少给人们带来“使用new关键字直接创建对象不靠谱”的错觉,其实这种结论有些片面了,为什么呢?因为所有的用例都有一个场景约束,一旦脱离适用场景,强制使用总是很牵强的。OK,让我们来再来细致的了解一下,或者说回顾一下。

考虑使用静态工厂方法代理构造函数

假设你已经知道了这里的“静态工厂”与设计模式中的“工厂模式”是两码事。

静态工厂方法可以有突出的名称

我们不能通过给类的构造函数定义特殊的名称来创建具备指定初始化功能的对象,也就是说我们必须通过参数列表来找到合适的构造函数,即便文档健全但仍很烦人,而且一旦使用了错误的构造函数,假如编译期不报错,一旦运行时奔溃,那就说明我们已经离错误发生的地方很远了,而且错误的对象已经被创建了,不过谢天谢地,它崩溃了,如果不崩溃,我们将更难找到问题所在。所以,这个时候我们就需要使用“静态工厂方法”了,因为有突出的名称,因此它很直观,易读,能够帮助我们避免这种低级错误的发生。当然,它的适用场景是存在多个构造函数,如果你只有一个构造函数,且希望被继承,则完全可以使用new来创建对象。

静态工厂方法可以使用对象池,避免对象的重复创建

反正这也应该是细节隐藏的,因此我们可以在“静态工厂方法”的背景下,在类的内部维护一个对象缓存池。这使得不可变类可以使用预先构件好的实例,或者将构建好的实例缓存起来,重复利用,从而避免创建不必要的对象。

可以像Boolean.valueOf(boolean)那样,使用预先创建好的实例。


  public static final Boolean TRUE = new Boolean(true);
  public static final Boolean FALSE = new Boolean(false);

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

它从不创建新的对象,而且Boolean自身的不变性,因此能够很好的使用预先创建好的实例。

或者像Parcel.obtain()那样,在类的内部维护一个数组结构的缓存池


private static final int POOL_SIZE = 6;
private static final Parcel[] sOwnedPool = new Parcel[POOL_SIZE];

/**
 * Retrieve a new Parcel object from the pool.
 */
public static Parcel obtain() {
    final Parcel[] pool = sOwnedPool;
    synchronized (pool) {
        Parcel p;
        for (int i=0; i<POOL_SIZE; i++) {
            p = pool[i];
            if (p != null) {
                pool[i] = null;
                if (DEBUG_RECYCLE) {
                    p.mStack = new RuntimeException();
                }
                return p;
            }
        }
    }
    return new Parcel(0);
}

也可以像Message.obtain()那样,使用一个链表结构的缓存池


private static Message sPool;

/**
 * Return a new Message instance from the global pool. Allows us to
 * avoid allocating new objects in many cases.
 */
public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

需要注意的是,为这些对象添加一个正确的回收逻辑。

在这些场景下,我们能够轻松的控制究竟使用缓存实例,还是创建新的对象,或者设计成单例,它完全是可控的,属于“实例受控类”的范畴。相反地,如果你在设计类的时候考虑到,既不需要缓存,也不可能成为单例,那么你同样可以,以直接new的方式来创建对象。

使用静态工厂方法可以返回“原返回”类型的任何子类型

这样,我们在选择返回对象的类时就有了更大的灵活性。

这种灵活性的一种场景是,API可以返回对象,同时又不会使对象的所对应的类变成共有的。以这种方式隐藏实现类会使API变得非常简洁。如Collections.unmodifiableList(list)


public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
    }

static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E>
                                              implements RandomAccess{

  UnmodifiableRandomAccessList(List<? extends E> list) {
      super(list);
  }
    ...
}

static class UnmodifiableList<E> extends UnmodifiableCollection<E>
                                  implements List<E> {

   final List<? extends E> list;

   UnmodifiableList(List<? extends E> list) {
       super(list);
       this.list = list;
   }
    ...
}

就像描述中的一样,由于访问域的限制,我们“永远”无法在Collections类的外部直接初始化UnmodifiableRandomAccessListUnmodifiableList实例。

不过这也有个限制,我们只能通过接口"List"来引用被返回的对象,而不是通过它的实现类来引用,值得一提的是,通过接口或者抽象来引用被返回的对象,理应成为一种良好的习惯。

静态工厂方法在创建参数化类型实例的时候,它们使代码变得更加简洁。

在调用参数化构造器时,即使类型参数很明显,也必须指明。这通常需要连续两次提供类型参数


  Map<String, List<String>> map = new HashMap<String, List<String>>();

  /*使用静态工厂方法,编译器会通过“类型推导”,找到正确的类型参数*/
  Map<String, List<String>> map1 = newInstance();
  public static <K, V> HashMap<K, V> newInstance() {
    return new HashMap<K, V>();
  }

不过现在编译器或者说IDE已经足够智能,上面第一个例子完全允许写成:

Map<String, List<String>> map = new HashMap<>();

不必连续两次提供类型参数。

上面提到的大都是使用“静态工厂方法”相较于其他(创建对象方式)的优势,那么我们再来看看它有什么限制。

静态工厂方法,类如果不含共有的或者受保护的构造器,就不能子类化

因为子类需要在构造函数中隐式调用父类的无参构造函数或者显式调用有参构造函数,这和把类修饰成final所表达的效果一致。而一旦类中存在公有构造函数,也就是说客户端可直接通过构造函数创建对象,也就弱化了静态工厂方法约束性。

静态工厂方法,它和其他静态方法实际上没有任何区别

一旦考虑使用“静态工厂方法”,就必须考虑简单,直观,完善的命名,这的确是个头疼的事 : (

遇到多个构造器参数时考虑使用构建器

其实,静态工厂方法和构造函数都有局限性:“他们都不能很好的扩展到大量的可选参数”

在《Effective Java》举了这样一个经典的例子:

考虑用一个类表示包装食品外面显示的营养成分标签。这些标签中有几个域是必需的:每份含量,每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇,钠等等。

如果这种情况下依然坚持使用构造函数或者静态工厂方法,那么要编写很多重叠构造函数,而且对于那么多的可选域而言,这些重叠函数简直就是噩梦!

避免代码难写,难看,难以阅读,有两种办法可以解决。

JavaBeans模式

使用JavaBeans模式,把必需域作为构造函数的参数,可选域则通过setter方法注入。

我们都知道JavaBeans模式自身存在着严重的缺陷。因为构造过程可能被分到几个调用中,在构造过程中JavaBean可能处于不一致状态。类无法通过检验构造参数的有效性来保证一致性。而试图使用处于不一致状态的对象,将会导致失败,这种失败与包含错误代码大相径庭,因此调试起来十分困难。与此相关的另一点不足在于,JavaBeans模式阻止了了把类做成不可变的可能,这就需要程序员付出额外的努力来确保它的线程安全。

Builder模式

幸运地是,Builder模式既能保证像重叠模式那样的安全性,也能保证JavaBeans模式那么好的可读性。而且也能够对参数进行及时的校验,一旦传入无效参数或者违反约束条件就应该立即抛出IllegalStateException异常,而不是等着build的调用,从而创建错误的对象。

那么我们真的需要把创建对象的方式更改为Builder吗?
答案是,否定的。

我们可以在可选域多样化的条件下,考虑使用这种模式,而且我们应该注意:不要过度设计API。

其实看完这些总结和经验,我想你心里一定有明确的答案了,那就让我们再来一句总结:
如果你的类足够简单,那么完全可以使用new来直接创建!切记过犹不及的API设计

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,292评论 18 399
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,151评论 0 4
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,036评论 29 470
  • 目录 第二章 创建和销毁对象 1 考虑用静态工厂方法替代构造器 对于代码来说, 清晰和简洁是最重要的. 代码应该被...
    高广超阅读 1,396评论 0 12
  • 今天开始,要进入爸爸全陪阶段,我要发奋加班了。 第一天还是回家旁观指导。真是好爸爸,对孩子一百二十个放心,把书丢给...
    Hisi阅读 130评论 0 0