Effective Java笔记(15)使类和成员的可访问性最小化

        区分一个组件设计得好不好,唯一重要的因素在于,它对于外部的其他组件而言,是否隐藏了其 内 部数据和其他实现细节 。 设计良好的组件会隐藏所有的实现细节, 把 API 与实现清晰地隔离开来 。 然后,组件之间只通过 AP I 进行通信,一个模块不需要知道其他模块 的内部工作情况 。 这个概念被称为信息隐藏封装,是软件设计的基本原则之一。

        信息隐藏之所以非常重要有许多原因,其中大多是因为 : 它可以有效地解除组成系统的各组件之间的藕合关系,即解耦,使得这些组件可以独立地开发、 测试、优化 、使用、理解和修改 。 因为这些组件可以并行开发,所以加快了系统开发的速度 。 同时减轻了维护的负担,程序员可以更快地理解这些组件,并且在调试它们的时候不影响其他的组件 。

        Java 提供了许多机制( facility )来协助信息隐藏 。 访问控制( access control )机制决定了类、接口和成员的可访问性( accessibility ) 。 实体的可访问性是由该实体声明所在的位置,以及该实体声明中所出现的访问修饰符( private 、protected 和 public)共同决定的 。 正确地使用这些修饰符对于实现信息隐藏是非常关键的 。

        规则很简单 :尽可能地使每个类或者成员不被外界访问 。 换句话说,应该使用与你正在编写的软件的对应功能相一致的 、 尽可能最小的访问级别 。

        如果一个包级私有的顶层类(或者接口) 只是在某一个类的内部被用到,就应该考虑使它成为唯一使用它的那个类的私有嵌套类 。 这样可以将它的可访问范围从包中的所有类缩小到使用它的那个类 。 然而,降低不必要公有类的可访问性,比降低包级私有的顶层类的可访问性重要得多:因为公有类是包的 API 的一部分,而包级私有的顶层类则已经是这个包的实现的一部分 。

        对于成员(域、方法、嵌套类和嵌套接口)有四种可能的访问级别,下面按照可访问性的递增顺序罗列出来:

  • 私有的( private )——只有在声明该成员的顶层类内部才可以访问这个成员 。
  • 包级私有的( package-private ) 一一声明该成员的包内部的任何类都可以访问这个成员 。 从技术上讲,它被称为“缺省”( default )访问级别,如果没有为成员指定访问修饰符,就采用这个访问级别(当然,接口成员除外,它们默认的访问级别是公有的) 。
  • 受保护的( protected ) 一一声明该成员的类的子类可以访问这个成员(但有一些限制),并且声明该成员的包内部的任何类也可以访问这个成员 。
  • 公有的( public ) ——在任何地方都可以访问该成员 。

        有一条规则限制了降低方法的可访问性的能力 。 如果方法覆盖了超类中的 一个方法,子类中的访问级别就不允许低于超类中的访问级别。 这样可以确保任何可使用超类的实例的地方也都可以使用子类的实例(里氏替换原则) 。 如果违反了这条规则,那么当你试图编译该子类的时候,编译器就会产生一条错误消息 。 这条规则有一个特例:如果一个类实现了一个接口,那么接口中所有的方法在这个类中也都必须被声明为公有的 。

        公有类的实例域决不能是公有的。 如果实例域是非 final 的,或者是一个指向可变对象的 final 引用, 那么一旦使这个域成为公有的,就等于放弃了对存储在这个域中的值进行限制的能力;这意味着,你也放弃了强制这个域不可变的能力 。 同时,当这个域被修改的时候,你也失去了对它采取任何行动的能力 。因此, 包含公有可变域的类通常并不是线程安全的 。 即使域是 final 的,并且引用不可变的对象,但当把这个域变成公有的时候,也就放弃了“切换到一种新的内部数据表示法”的灵活性 。

        注意,长度非零的数组总是可变的,所以让类具有公有的静态 final 数组域,或者返回这种域的访问方法,这是错误的 。 如果类具有这样的域或者访问方法,客户端将能够修改数组中的内容。 这是安全漏洞的一个常见根源:

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

        要注意 ,许多 IDE 产生的访问方法会返回指向私有数组域的引用 ,正好导致了这个问题 。 修正这个问题有两种方法 。 可以使公有数组变成私有的,并增加一个公有的不可变列表:

private static final Thing[] PRIVATE_VALUES = { ... };
public static final List<Thing> VALUES=
    Collections.unmodifiablelist(Arrays.aslist(PRIVATE_VALUES));

        另一种方法是,也可以使数组变成私有的,并添加一个公有方法,它返回私有数组的一个拷贝:

private static final Thing[] PRIVATE.VALUES = { ... };
public static final Thing[] values() {
    return PRIVATE_ VALUES.clone();
}

        总而言之,应该始终尽可能(合理)地降低程序元素的可访问性 。 在仔细地设计了一个最小的公有 API 之后,应该防止把任何散乱的类、接口或者成员变成 API 的一部分 。 除了公有静态 final 域的特殊情形之外(此时它们充当常量 ),公有类都不应该包含公有域,并且要确保公有静态 final 域所引用的对象都是不可变的 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值