Chapter 4 Class and Interface
Item 15: Minimize the accessibility of classes and members
最小化类和成员的访问。
组件隐藏了其实现细节,并将API暴露给外部。组件之间的通信依靠API,但是对API的实现细节一无所知,这种概念称为信息隐藏或者封装(information hiding or encapsulation),为软件的基本设计原则。
some rules of thumb
- 尽可能地让每个类不可访问。 或者,使用与需求一致的最小访问级别。
- 如果一个顶级类或接口可以被设置为包私有的,那么就一定要设置为包私有的(default)
- 如果一个包私有顶级类或者接口只被一个类使用,那么可以考虑将其设置为静态内部类。
- 公共类的实例不应该设置为
public,公共可变属性的类通常是线程不安全的。
坑
使用public static final修饰非0长度数组,并给外界访问非常有风险。因为final并不能保证数组中的元素是不可变的,反之,public修饰符反而给了外部修改数组的机会。
可以使用如下的解决方案:
private static final Thing[] PRIVATE_VALUES = { ... };
public static final Thing[] values() {
return PRIVATE_VALUES.clone();
}
外界访问的时候,返回一个数组的拷贝。
Item 16: In public classes, use accessor methods, not public fields
在公共类中,使用访问器,而不是公共字段
If a class is accessible outside its package, provide accessor methods to perserve the flexibility to change the class's internal representation. 如果一个类可以被包外访问,应该提供访问方法来保留更改内部表示的灵活性。
If a class is package private or is a private nested class, there is noting inherently wrong with exposing its data fields. 如果一个类是包私有的或者是私有的内部类,那么暴露其属性没有本质上的错误。
Item 17: Minimize mutability
最小化可变性
Java中不可变类型的类有:String、基本类型的包装类、BigInteger | BigDecimal
The rules to make a class immutalbe:
- Do not provide methods that modif the object's state. 不要提供任何会修改对象状态的类。
- Ensure that the class can not be extended. 保证类不会被继承。
- Make all fields final and private. 将所有的字段置为
final并且是私有的 - Ensure exclusive access to any mutable components. 确保对任何可变组件的访问都是互斥的。
Some advantages of immutable objects
- Immutable objects are simple. 不可变对象只有一种状态,就是其被创建时候的状态,简单并且容易维护。
- Immutable objects are inherently thread-safe; they require no synchronization. 不可变对象本质上是线程安全的,不需要同步。
- Never have to make defensive copies of immutables. 不需要进行保护性拷贝。
- **Immutable objects make great building blocks for other objects. **
- Provide failure atomicity for free. 无需任何代价就能够获得对于失败的原子性。
The only disadvantage of immutable obejcts
Require a separate obejct for each distinct value. 对于每个不同的值都需要一个单独的对象。
Other schemes for a immutable class
Make all of its constructors private or package-private and add public static factories in place of the public constructors.
让所有的构造器都变为私有的或者包私有的,使用公共的静态工厂方法来取代公共构造器。
Some useful tips
- No methods may produce an extenally visible change in the objects'state.
- Provide an explict
readObjectorreadResolvemethod when a immutable class containing one or more mutable fields implementsSerializable. - Resist the urge to write a setter for every getter.
- If a class can not be made immutable, limit its mutability as much as possible.
- Constructors should create fully initialized objects with all of their invariants established.
Item 18: Favor composition over ineritance.
组合优先于继承。
Problems produced by extends
- Extends violates encapsulation. 继承打破了封装性。
为了解决这种问题,可以在新类中增加一个私有属性,引用现有类的一个实例。这种设计被称作为:组合。
什么时候适用于继承(extends)呢?
当且仅当,子类是父类的子类型的时候,适合使用继承,即存在is a的关系。
Item 19: Design and document for inheritance or else prohibit it
设计继承时提供文档说明,否则禁止继承。
如何设计一个继承,那么类的文档必须精确描述覆盖每个方法所带来的影响。文档需要指明,此方法或者构造器调用了那些可以覆盖的方法,是以什么顺序调用的,调用之后产生了什么样的结果。
类必须精心选择受保护的方法,或者实例、受保护的字段等来提供可以进入其内部工作的钩子(hook)方法。
构造器绝对不能调用可以被覆盖的方法,无论是直接调用,还是间接调用。通过构造器调用私有方法、final方法和静态方法都是安全的,这些都是无法被覆盖的。
如果想要一个超类去实现Serializable接口,并且该类中有一个readResolve或者writeReplace方法,就必须将这两个方法设置为protected,否则子类会忽略掉这些方法。
对于那些并非为了继承而设计的普通类,禁止进行子类化。
- 将类声明为
final - 所有的构造器变为
private或者package-private的,使用静态工厂方法类替代构造器
如何去测试超类呢?唯一的测试方法就是,编写子类。 通常情况下,3个子类就可以测试一个用于继承的类。必须在类发布之前去完成子类的编写测试工作。
Item 20: Prefer interfaces to abstract classes
接口优先于抽象类
在Java中,一个类可以通过选择继承某个抽象类或者实现某个接口来扩展本类的功能。但是继承和实现的区别在于,Java中只允许单继承,可以多实现。实现接口相较于继承抽象类,具有诸多的好处。
一个类可以通过实现某个接口来轻易地进行改进,但是很少有类通过继承某个抽象来来进行类的改进的。
接口是定义混合类型的理想选择。什么是混合(Mixin)类型?对于一个混合类型的类,除了它自身的本类型,它还具有其他的可扩展的行为。例如,一个实现了Comparable接口的类Book,它不仅具有Book的基本类型,还拥有根据bookName按照字典顺序进行排序的行为。
此外,接口可以实现非层级类型框架的构建。
接口可以通过包装类来实现安全、强大的功能增强。
可以考虑将抽象类和接口结合起来一起使用,提供一个抽象的骨架实现类。接口用来定义类型,可以提供一些默认的方法,骨架实现类在原始接口方法的顶层实现了剩余非原始接口方法。继承抽象的骨架实现类的主要工作就是去实现一个接口。
Item 21: Design interfaces for posterity
为后代设计接口
Java 8 之后,接口可以添加缺省方法(default method)。缺省方法有一个默认的实现,在所有实现接口的类中都可以使用此默认方法。
建议避免利用缺省方法在现有的接口上添加新的方法。即使在一些特殊的情况下,也应该考虑如下的问题:
- 缺省方法的实现是否会破坏现有接口的实现?
总而言之,尽量不要为一个接口添加缺省方法。
Item 22: Use interfaces only to define types
接口只用于定义类型
有一种接口被称为常量接口(constant interface),这种接口只包含静态的常量值。
常量接口模式是对接口的不良使用。
- 如果在某一天类不需要修改这些常量了,它依然需要去实现这个接口,以确保二进制兼容性【?】
- 如果一个非
final实现了常量接口,其子类的命名空间也会被接口中的常量所污染
如何以优雅的方式导出常量呢?
- 如果常量与某个类或者接口紧密相关,则直接将常量添加到类或者接口中:例如
Integer.MAX_VALUE - 常量可以被看作是枚举成员,则使用枚举类型(enum type)
- 使用不可实例化的工具类(utility class)
Item 23: Prefer class hierarchies to tagged class
类层次优先于标签类
什么是标签类呢?举个例子,有一个可以表示圆形或者矩形的类:
// Tagged class - vastly inferior to a class hierarchy!
class Figure {
enum Shape { RECTANGLE, CIRCLE };
// Tag field - the shape of this figure
final Shape shape;
// These fields are used only if shape is RECTANGLE
double length;
double width;
// This field is used only if shape is CIRCLE
double radius;
// Constructor for circle
Figure(double radius) {
shape = Shape.CIRCLE;
this.radius = radius;
}
// Constructor for rectangle
Figure(double length, double width) {
shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}
double area() {
switch(shape) {
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
思考一下上面这个类有什么缺点?
- 多个不同的实现出现在一个类中,类变得很臃肿
- 拓展性差,如果将来要添加三角形(triangle)的功能,则会添加一些混乱的实现
如何改造这个类呢?
将标签类转换为层级类,为标签类中的每个方法都定义一个包含抽象方法的抽象类,标签类的行为依赖于标签值。
// Class hierarchy replacement for a tagged class
abstract class Figure {
abstract double area();
}
class Circle extends Figure {
final double radius;
Circle(double radius) { this.radius = radius; }
@Override
double area() { return Math.PI * (radius * radius); }
}
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() { return length * width; }
}
层级类的好处:
- 避免了不相干字段的污染
- 对于父类声明的每个抽象方法都会确保一个实现
- 增加了扩展性
Item 24: Favor static memeber classes over nonstatic
**静态成员类优先非静态成员类
嵌套类(nested class)是指定义在一个类的内部的类。嵌套类存在的唯一目的就是为了给外部类(enclosing class)提供服务。 嵌套类可以分为如下四种:
- 静态成员类(static member class)
- 非静态成员类(non-static member class)
- 匿名类(anonymous class)
- 局部类(local class)
后面三种又被统称为内部类(inner class)。
静态成员类最常见的用法就是作为公有类的辅助类,只有与她的外部类一起使用才有意义。
静态成员类和非静态成员类的区别是什么?
- 静态成员类是使用
static修饰 - 静态成员类的实例可以独立于外围类的实例而存在
- 非静态成员类的实例都与外围类的外围实例关联,可以通过
Enclosing.this来访问外部类的实例 - 非静态成员类的实例方法内部,可以调用外围实例上的方法
非静态成员类最常见的用法就是定义一个适配器(adapter)。
- 例如
Map接口的实现使用keySet | entrySet |values等非静态成员类实现集合视图(collection view) Set | List使用非惊天成员类实现他们的迭代器(iterator)
对于非静态类,每个实例都会包含一个额外指向外围对象的引用,增加空间的开销,并且可能会导致外围实例在符合垃圾回收时仍然得以保留,因而造成内存的泄露。
私有静态成员类的一种用法为代表外围类所代表的的对象的组件。例如Map与Entry之间的关系,每一个Entry需要关联一个Map对象,但是Entry实例并不需要访问该Map实例,因此不需要使用非静态成员类来表示entry。
匿名类并不是外围类的一个成员,而是在需要使用的地方进行声明和实例化。
- 匿名类无法实现多个接口或者继承某个类
- 除了从超类中继承得到成员之外,匿名类无法调用任何成员
- 匿名类必须保持简短
局部类定义在方法、构造方法或者代码块内部的类,只在定义他的作用域之内可用。相较于匿名类,局部类有自己的名字,并且可以进行多次实例化。
局部类常用的使用场景为:
- 封装方法的复杂逻辑
- 在方法内部处理事件或者实现接口
- 简化代码,避免将一次性逻辑提取到外部类
Item 25: Limit source files to a single top-level class
限制源文件为当个顶级类
不要将多个顶级类编写在单个顶级类中!!!