EffectiveJava读书笔记三

第4章 类和接口

使类和成员的可访问性最小化

模块之间只通过它们的API进行通信, 一个模块不需要知道其他模块的工作情况,这称之为信息隐藏(information hiding)或封装(encapsualtion),是软件设计的基本原则之一。

灵活使用成员(域、方法、嵌套类和嵌套接口)的四种访问级别。

  • 私有的(private)
  • 包访问的(package-private)
  • 受保护的(protected)
  • 公有的(public)
    其中私有和包访问则是类的实现中的一部分即不会影响它的导出的API。
    受保护的成员的应该尽量少用。
    包含公有可变域的类并不是线程安全的。这一点主要说明的是我们应该多使用不可变的域,即多使用final来达到我们的目的。PS:final并不是万能的解决方案,即当final指向一个可变对象的引用,同样也会带来问题。

长度非零的数组总是可变的,所以,类具有共有的静态final数组域,或者返回这种域的访问方法,这几乎总是错误的。如下:

public static final Thing[] values = {...};

有如下的两种修正方法:

  1. 使公有数组变成私有的,并增加一个公有的不可变列表:
private static final Thing[] PRIVATE_VALUES = {...};
public static final List<Thing> VALUES = Collections.unmodifiableLIst(Arrays.asList(PRIVATE_VALUES));
  1. 使数组变成私有的,并添加一个公有方法,返回私有数组的一个备份:
private static final Thing[] PRIVATE_VALUES = {...};
public static final Thing[] values(){
    return PRIVATE_VALUES.clone();
}

总结:应该始终尽可能地降低可访问性。在仔细地设计了一个最小的公有API之后,应该防止把任何散乱的类、接口和成员变成API的一部分。除了公有静态final域的特殊情形之外,公有类都不应该包含公有域。并且要确保公有静态final域所引用的对象都是不可变的。

在公有类中使用访问方法而非公有域

如果类可以在它所在的包的外部进行访问,就提供访问方法,以保留将来改变该类的内部表示法的灵活性。

当域不可变的时候,在读取域时,可强加约束条件。

公有类永远都不应该暴露可变的域。虽然还是有问题,但是让公有类暴露不可变的域其危害比较小。但是,有时候会需要用包级私有的或者私有的嵌套类来暴露域,无论这个类是不变的还是不可变的。

是可变性最小化

Java平台类库中包含的不可变类,有String,基本类型的包装类,BigInteger和BigDecimal。不可变的类比可变类更加易于设计、实现和使用,不容易出错且更加安全。

为了使类成为不可变,要遵循下面五条原则:

  1. 不要提高任何会修改对象状态的方法。
  2. 保证类不会扩展
  3. 使所有的于都是final的。
  4. 使所有的域成为私有的。
  5. 确保对于任何可变组件的互斥访问。

采用函数的做法,即在方法中返回的是一个新的实例,而不是修改这个实例。

不仅可以共享不可变对象,甚至也可以共享它们的内部信息。如BigInteger类。
不可变对象为其他对象提供了大量的构建。
不可变类真正唯一的缺点是,对于每个不同的值都需要一个单独的对象。

为了确保不可变性,类绝对不允许自身被子类话,除了“使类成为final的”这种方法之外,还有另外一种更加灵活的办法可以做到这一点:让类的所有构造器都变成私有的或者包级私有的,并添加公有的静态工厂(static factory)来代替公有的构造器。

复合优先于继承

继承打破了封装性。换句话说,子类依赖于其超类中特定功能的实现细节。超类的实现有可能会随着发行版本的不同有所变化,如果真的发生了变化,子类可能遭到破坏,即使它的代码完全没有改变。

继承机制会把超类API中的所有缺陷传播到子类中,而复合则允许设计新的API来隐藏这些缺陷。

要么为继承而设计,并提供文档说明,要么就禁止继承

为了设计一个类的文档,以便它能够被安全地子类化 ,你必须描述清楚那些有可能未定义的实现细节。

为了允许继承,类还必须遵守其他一些约束。构造器不能调用可被覆盖的方法。

你可以机械地消除类中可覆盖方法的自用特性,而不改变它的行为。将每个可覆盖方法的代码体移到一个私有的"辅助方法(helper method)"中,并且让每个可覆盖的方法调用它的私有辅助方法。然后,用“直接调用可覆盖方法的私有辅助方法”来代替“可覆盖方法的每个自用调用”。

接口优于抽象类

Java只允许单集成,所以抽象类作为类型定义受到了极大的限制。而相对地,接口则有以下好处:

  • 现有的类可以很容易被更新,以实现新的接口。(抽象类则需要修改类层次,单继承也会给我们带来不小的困扰。)
  • 接口是定义mixin(混合类型)的理想选择。
  • 接口允许我们构造非层次结构的类型框架。

Sometime, 我们需要对接口来提供一个抽象的骨架实现类(skeletal implementation), 把接口和抽象类的优点结合起来。骨架实现通常会以 AbstractInterface的形式出现,如 Collections Framework中的AbstractCollection、AbstractSet、AbstractList和AbstractMap。

抽象类的演变比接口的演变要容易得多。在抽象类中增加新的方法,则该抽象类的所有现有实现都将提供这个新的方法。对于接口,这样做是行不通的。

总结: 接口通常是定义允许多个实现的类型的最佳途径。一个例外就是,当演变的容易性比灵活性和功能更为重要的时候,这种情况下,应该使用抽象类,但前提是必须理解并且可以接受这些局限性。如果导出了一个重要的接口,就应该坚决考虑同时提供骨架实现类。最后,应该尽可能谨慎地设计所有的公有接口,并通过编写多个实现来对它们进行全面的测试。

接口只用于定义类型

接口应该只被用来定义类型,不应该被用来导出常量。常量接口模式是对接口的不良使用。应该使用不可实例化的工具类(utility class)来导出这些常量。

如果大量利用工具类导出的常量,可以通过利用静态导入(static import)机制,避免用类名来修饰常量名,(静态导入机制是在Java1.5中才引入的)。

类层次优于标签类

标签类是指带有两种设置更多风格的实例的类,并包含实例风格的标签(tag)域。
它有着有多缺点,其中充斥着样板代码,包括枚举声明、标签域及条件语句。一句话:标签类过于冗长,容易出错,并且效率低下。

解决方法:使用子类型化(subtyping)为每种原始标签类定义根类的具体子类。

类层次的另一种好处:可以反映类型之间本质上的层次关系,有助于增强灵活性,并进行更好的编译时类型检查。

用函数对象表示策略

常用的比较器函数就代表一种为元素排序的策略。

总结:函数指针的主要用途就是实现策略(Strategy)模式。为了在Java实现这种模式,要声明一个接口来表示该策略,并且为每个具体策略声明一个实现了该接口的类。当一个具体策略只被使用一次时,通常使用匿名类来声明和实例化这个具体策略类。当一个具体策略是设计用来重复使用过的时候,它的类通常就要被实现为私有的静态成员类,并通过公有的静态final域被导出,其类型为该策略接口。

优先考虑静态成员类

嵌套类(nested class)是指被定义在另一个类的内部的类。其有四种:静态成员类(static member class)、非静态成员类(nonstatic member class)、匿名类(anonymous class)、和局部类(local class),后三种都被成为内部类(inner class)。

静态成员类可以访问外围类的所有成员,包括哪些声明为私有的成员。静态成员类是外围类的一个静态成员,与其他的静态成员一样,也遵守同样的可访问性规则。如果被声明为私有的,它则只能在外围类的内部才可以被访问。
常见的地方:Map的集合视图(collection view)keySet、entrySet和Values;Set和List集合接口中的迭代器(iterator)。

非静态成员类的每个实例都隐含着与外部类的一个外围实例(enclosing instance)相关联。这种关联关系需要消耗非静态成员类实例的空间,并且增加了构造的时间开销。

当且仅当匿名类出现在非静态的环境中,它才有外围实例。当出现在静态的环境中,则不会用于任何静态成员。常用用法是动态地创建行数对象(function object);创建过程对象(process object)如,Runnable, Thread 或 TimerTask实例;在静态工厂方法的内部。

局部类有名字,可以被重复地使用。与匿名类一样,只有在非静态环境中定义的时候,才有外围实例,它们也不能包含静态成员。

** 总结:** 四种嵌套类各有用途。若果一个嵌套类需要在单个方法之外仍然是可见的,或者太长了,不适合在方法内部,就应该使用成员类。如果成员类的每个实例都需要一个指向其外围实例的引用,就要把成员类做成非静态的;否则,就做成静态的。假设这个嵌套类属于一个方法的内部,如果你只需要在一个地方创建实例,并且已经有了一个预置的类型可以说明这个类的特征,就要把它做成匿名类;否则就做成局部类。

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

推荐阅读更多精彩内容

  • 类与接口是Java语言的核心,设计出更加有用、健壮和灵活的类与接口很重要。 13、使类和成员的可访问性最小化 设计...
    Alent阅读 670评论 0 2
  • 1.使类和成员的可访问性最小化 访问修饰符: private protected public 顶层的(非嵌套)类...
    666真666阅读 830评论 0 1
  • 一:java概述:1,JDK:Java Development Kit,java的开发和运行环境,java的开发工...
    ZaneInTheSun阅读 2,627评论 0 11
  • 回答 卑鄙是卑鄙者的通行证, 高尚是高尚者的墓志铭, 看吧,在那镀金的天空中, 飘满了死者弯曲的倒影。 冰川纪过去...
    水乡醉客阅读 600评论 1 5
  • 简介:遭到初恋的背叛,在职场的洗礼,与蓝颜扯不清理还乱的纠葛,看豪放女路小莫的成长经历。难以自拔的初恋,让路小莫学...
    hi羽佳阅读 339评论 0 1