Effective Java 3rd 条目24 静态成员类优于非静态

嵌套类(nested class)是一个定义在另外一个类内部的类。嵌套类应该仅仅是为了服务外部类而存在。如果内嵌类在其他某些情形下有用,那么他应该是一个顶层类。有四种嵌套类:静态成员类(static member class)非静态成员类(nonstatic member class)匿名类(anonymous class)本地类(local class)。只有第一种是被认为是内部类。这个条目告诉你声明时候使用哪种嵌套类以及为什么。

静态成员类是嵌套类的最简单的种类。它最好被认为是一个普通类,它只是恰好声明在另外一个类的内部,而且可以访问外部类的所有成员,即使这些成员是声明为私有的。静态成员类是它的外部类的静态成员,而且就像其他静态成员一样遵从同样的访问规则。如果它是声明为私有的,那么他仅仅可以在外部类内部访问,诸如此类。

静态内部类的一个通常使用是作为一个公开协助类,仅仅是和它的外部类联合使用。例如,考虑一个枚举,它描述由计算器支持的操作 (条目34)。Operation枚举应该是Calculator类的公开静态成员。于是Calculator的客户端应该引用一些操作,这些操作使用像Calculator.Operation.PLUS和Calculator.Operation.MINUS这样的名字。

在语句构成上,静态和非静态成员类的唯一区别在于,静态成员类在它们的声明中有修饰符static。尽管语法上的相似性,但是这两种嵌套类是非常不同的。非静态成员类的每个实例与包含类的外部实例相关联。在非静态成员类的实例方法里面,你可以调用外部实例(enclosing instance)的方法,或者使用限定的(qualified)this结构体获得外部实例的引用 [JLS, 15.8.4]。如果嵌套类的实例的存在脱离它的外部类的实例,那么嵌套类必须是一个静态成员类:没有外部实例,创建一个非静态成员类是不可能的。

当成员类实例创建时,非静态成员类实例和它的外部实例的联系建立,而且在这之后不能改变。通常,这个联系是从外部类的实例方法内部,通过调用一个非静态成员类构造子自动建立的。手动使用enclosingInstance.new MemberClass(args)建立这个联系,也是可能的,虽然非常少见。就像你所预料的,这个联系在非静态成员类实例中占用了空间,而且它的构造过程增添了时间。

非静态成员类的一个通常使用是定义一个Adapter[Gamma95],它允许外部类的实例被看成是某个不相关类的实例。例如,Map接口的实现通常使用非静态成员类实现他们的集合视图(collection view),它们是由Map’s keySet、entrySet和values返回。相似地,集合接口,比如Set和List,它们的实现通常使用非静态成员类来实现它们的迭代器:

// 非静态成员类的典型使用 
public class MySet<E> extends AbstractSet<E> { 
    ... // 这个类的大部分省略

    @Override public Iterator<E> iterator() { 
        return new MyIterator(); 
    }

    private class MyIterator implements Iterator<E> { 
        ...
    }
}

如果你定义一个成员类,它不需要访问外部实例,那么永远把static修饰符放在它的声明中,让它成为一个静态而不是非静态成员类。如果你省略这个修饰符,那么每个实例将有一个它的外部实例的额外隐含引用。就像前面提到的,存储这个引用耗费时间和空间。更为严重的是,它可能导致外部实例留存,原本它是适合垃圾收集(条目7)。最终的内存泄漏可能是灾难性的。它通常很难监测到,因为这个引用是不可见的。

私有静态成员类的一个通常使用是代表对象的组件,这个对象由它们外部类表示。例如,考虑Map实例,它把键和值联系起来。许多Map实例,对于映射中每个键值对,有一个内部Entry实例。虽然每个entry和一个映射相联系,但是entry的方法(getKey、getValue和setValue)不需要访问映射。所以,使用非静态成员类来表示entry是很浪费的:私有静态成员类是最好的。如果你在entry声明中不慎忽略了static修饰符,这个映射仍然是工作的,但是每个entry将包含一个多余的对映射的引用,这就浪费了空间和时间。

如果正在讨论的类是一个导出类的公开或者受保护成员,在一个静态和非静态成员类之间正确地选择是非常重要的。在这种情况下,成员类是一个导出API元素,而且在后续发布中不能把它从非静态改变为静态成员类,而不会违反向后兼容性。

就像你所预料的,匿名类没有名字。它不是它的外部类的一个成员。不是和其他成员一起声明,它是在使用时同时声明和实例化的。代码中一个表达式合法的任何地方,匿名类也是允许的。当且仅当它们在非静态环境中发生,匿名类有外部类。但是即使它们在静态环境中发生,它们也不能够有任何静态成员,除了常数变量(constant variable),它是初始化为常数表达式的final原始或者字符串域,[JLS, 4.12.4]。

匿名类的应用有许多限制。除非在它们被声明时,你不能够实例化它们。你不能进行instanceof测试,或者做需要你命名这个类的任何其他事情。你不能声明一个匿名类来实现多个接口,或者同时扩展一个类和实现一个接口。使用匿名类的客户端不能够调用任何成员,除了从它的超类继承而来的那些成员。因为匿名类发生在表达式之中,它们应该保持简短(大约十行或者更少),否则可读性将会受损。

在Java添加lambda(第6章)之前,匿名类是随手创建小函数对象(function object)处理对象(process object)的优选方式,而且,但是lambda现在是更优的(条目42)。匿名类的另外一个通常使用是静态工厂方法的实现(参考条目20中intArrayAsList)。

本地类是四种内嵌类中最少使用。一个本地类实际上可以在一个本地变量可以声明的任何地方声明,而且遵从相同的作用域规则。本地类和其他类型的嵌套类有许多相同的特质。像成员类,它们有名字,而且可以重复使用。像匿名类,只有当它们在非静态情形中定义时,它们有外部实例,而且它们不能保护静态成员。而且像匿名类,为了不伤害可读性,它们应该保持简短。

简要概括,有四种不同嵌套类,而且每种有它的位置。如果一个嵌套类需要在单个方法的外部可见,或者太长了而不适合在一个方法内部,那么使用成员类。如果成员类的每个实例需要它外部实例的引用,那么把它变成非静态;否则,把它变成静态。假设类属于一个方法的内部,如果你需要仅仅从一个位置创建实例,而且有描述这个类的特性的已存类,那么把它变成匿名类;否则,把它变成本地类。

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

推荐阅读更多精彩内容

  • 概念 嵌套类(nested class) 指被定义在另一个类的内部的类。嵌套类存在的目的应该只是为他的外围类(en...
    NekoJiang阅读 1,250评论 0 2
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 目录 第二章 创建和销毁对象 1 考虑用静态工厂方法替代构造器 对于代码来说, 清晰和简洁是最重要的. 代码应该被...
    高广超阅读 1,436评论 0 12
  • 语文: 1.拼音本:ong写一行(前3个不带声调,后四个带声调)!ong的拼读音节(song.zhong各写3个!...
    瑞睿家阅读 155评论 0 0
  • 条条丝丝理不清,烦烦乱乱催人命。现在处在一个好尴尬的时候,想找工作,但是又即将面临答辩,数据理不清,文章写不出,备...
    阿哲手作阅读 198评论 0 0