本文是《Effective Java》一书的整理笔记
使类和成员的可访问性最小化
设计良好的模块会隐藏所有的实现细节,把它的API与它的实现清晰地隔离开来。然后,模块之间只通过它们的API进行通信,一个模块不需要知道其他模块的内部工作情况,这一概念被称为信息隐藏(information hiding)或封装(encapsulation)
封装的好处
它可以有效地解除组成系统各模块之间的耦合关系,使得这些模块可以独立的开发、测试、优化、使用、理解和修改。这样可以加快系统开发的速度,因为可以并行开发。
减轻维护的负担,因为程序员可以更快的理解这些模块,并在调试的时候可以不影响其他模块的负担。
虽然信息隐藏不会带来更好的性能,但可以有效的调节性能:通过剖析确定了那些模块影响了性能。
提高了软件的可重用性
降低了构建大型系统的风险,因为即使整个系统不可用,但这些独立的模块却有可能是可用的。
封装的规则
- 尽可能的使每个类或成员不被外界访问
- 如果一个包级私有的顶层类只在某一类的内部被用到,应该考虑使它成为唯一使用它的那个类的私有嵌套类
成员变量不能是公开的,常量是例外。
可以使用
public static final
来修饰常量
另外常量要么包含基本类型,要么指向不可变对象的引用。虽然引用不能被修改,但引用的对象却是可以被修改的。长度为非0的数组总是可变的,因此用
public static final
修饰的数组容易带来潜在的安全漏洞。
有两种解决方式,一是将数组变成私有,并添加一个公有的不可变列表
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();
}
使用方法访问类成员变量
使用setter和getter方法访问类成员变量,有以下好处
* 可以添加约束条件
* 可以灵活的修改该类的内部表示方式
如何设计不可变类
不可变类的每个实例包含的所有信息都必须在创建该实例的时候就提供,>并在对象的整个生命周期内固定不变。
不可变类遵循的规则
- 不要提供任何会修改对象属性的方法
- 保证类不会扩展
- 使所有域都是
final
的 - 所有的域都成为私有的
- 确保对于任何可变组件的互斥访问
如果类中有指向可变对象的变量,要确保不会被该类的客户端获得指向这些对象的引用。在构造器、访问方法和
readObject
方法中使用保护性拷贝。
不可变类的设计
不可变对象本质上是线程安全的,它们不要求同步。
可以提供一个静态工厂将该类常用的实例缓存起来,基本包装类和
BigInteger
都有这样的静态工厂。不要用为不可变对象提供
clone
方法或拷贝构造器不可变类的缺点是对于不同的值都需要一个单独的对象。
可以提供一个配套的公有的可变配套类,避免在大规模操作时造成的性能损耗,例如
String
和StringBuilder
声明构造器为
private
可以防止该类被继承。- 许多不可变类拥有多个非
final
域,当它们第一次被请求计算时,把一些开销昂贵的结果缓存到这些域中,以便下次再次请求同样的计算,就直接返回这些缓存的值。 - 如果需要让不可变类实现
Serializable
接口,就必须显示的提供一个readObject
或者readResolve
方法,或者使用ObjectOutputStream.writeUnshared
和ObjectInputStream.readUnshared
方法。 - 如果一个类不能被设计成不可变的,应当尽量限制其可变性。
组合优于继承
《Java编程思想》称为组合,《Effective Java》称为复合
4.1 继承的缺点
这里所说的缺点不包括,接口继承以及完全为继承而设计的超类
- 继承打破了封装性,子类依赖超类中特定功能的实现细节,但超类的实现有可能随着发行版本的不同而有所变化。
- 如果超类在后续版本中添加了一个新的方法,该方法与子类中的某一方法签名相同但返回值不同,将导致编译错误。
取代继承的方式
- 使用转发的方式代替继承(装饰器模式)
- 采用组合方式代替继承
什么时候需要继承
- 只有当两者之间确实存在“is-a”关系的时候
- 当超类的API设计有缺陷时,采用继承机制会传播缺陷,复合则允许新的API隐藏这种缺陷
要么为继承而设计,并提供文档说明,要么禁止继承
可覆盖的方法是指非
final
的,public
或protected
的方法
- 该类必须有文档说明它可覆盖的方法的自用性
也就是说必须精确的描述覆盖每个方法所带来的影响。
对于可覆盖的方法或非private
构造器,必须指明它调用了那些可覆盖的方法,是以什么顺序调用的,每个调用结果又是如何影响后续的处理过程。那些情况下它会调用可覆盖的方法 - 构造器不能调用可被覆盖的方法
超类构造器将在子类构造器之前被调用,所以子类中覆盖的版本方法将在子类构造器之前被调用,如果该方法依赖构造器所执行的初始化方法,那么将有可能导致程序失败。 - 对于为继承而设计的类中实现
Cloneable
或者Serializable
接口,无论clone
还是readObject
都不可以调用可覆盖的方法,无论是间接还是直接。 - 对于为继承而设计的类中实现
Serializable
,并且该来有readResolve
或者writeReplace
方法,就必须使其成为protected
,防止子类忽略这两个方法。 - 对于不专门为继承而设计的类,最好要禁止子类化
接口优于抽象
- 现有类可以很容易被更新,以实现新的接口
- 接口是定义mixin的理想选择
mixin是指这样的类型,类除了实现它的“基本类型”之外,还可以实现这个mixin类型,以表明它提供了某些可供选择的行为。
接口之所以被称为mixin,是因为它允许任选的功能可被混合到类型的主要功能中。 - 接口允许我们构造非层次结构的类型框架
- 接口可以安全的增强类的功能
- 可以将接口和抽象类结合起来,提供一个抽象的骨架实现。例如
AbstractCollection
类
接口只用于定义类型
接口应该只被用来定义类型,不应该用于声明常量优先考虑静态成员类
- 嵌套类(nested class)是指定义在另一个类的内部的类,包括静态成员类、非静态成员类、匿名类、局部类。
- 嵌套类存在的目的应该只是为它的外围类提供服务。
- 如果一个嵌套类在单个方法之外仍然可见,或者它太长了,不适合放在方法内部,就应该使用成员类。
- 如果成员类的每一个实例都需要一个指向外围实例的引用,就要把成员类声明为非静态的。否则就要声明为静态的。
- 如果这个嵌套类属于一个方法的内部,并且你只需要在一个地方创建实例,而且已经有一个预置的类型可以说明这个类的特征,就要把它做成匿名类。
静态成员类
- 静态成员类是外围类的一个静态成员,与其他静态成员一样,遵守同样的可访问性规则。
- 静态成员类的常见用法是作为公有的辅助类,仅当与它的外部类一起使用时才有意义。
- 私有静态成员类的另一个常见用法是作为外围类所代表对象的一个组件。
非静态成员类
- 非静态成员类的一个常见用法是定义一个
Adapter
,它允许外部类的实例被看作另一个不相关的类的实例。例如Map
接口中的集合视图。 - 非静态成员类的每个实例都将包含一个额外的指向外围对象的引用,这需要消耗额外的空间和时间,并且会导致外围实例在符合垃圾回收时依然得以保留,因此除非必须访问外围实例,否则要始终把成员类声明为
static
。
匿名类
- 匿名类要尽量简短,否则会影响程序的可读性。10行或者更少。
- 匿名类的一个常见用法是动态的创建函数对象,例如匿名
Comparator
实例。 - 匿名类的另一个常见用法是创建过程对象,例如
Runnable
、Thread
。
欢迎大家访问我的博客,转载请注明出处
http://blog.youkuaiyun.com/abyss521