第四章 类和接口

类和接口在Java编程语言中处于核心的地位。他们是Java语言抽象概念的基本单元。 Java提供了很多非常强大的可以用来设计雷和接口的元素。 本章包含了帮助你使你最好的方式使用这些元素的指导原则,方便你的类和接口是可用的、健壮的和灵活的。

15. 将类和成员的可访问性降到最低

Information hide

The single most important factor that distinguishes a well-designed component from a poorly designed one is the degree to which the componenet hides its internal data and other implementation details from other components.

一个好的设计的组件阴曹所有实现细节,干净地将它的API从它的实现分开。然后组件只通过它们的API进行通信,并且不知道彼此的内部工作方式。 这个被称为信息隐藏或者封装的概念,是软件设计的基本信条。

信息隐藏很重要,有很多原因。

  1. 大部分源于它能将组成一个系统的组件都解耦,允许他们单独开发,测试,优化,使用,理解和修改。这加快了系统开发,因为组件可以被并行开发。
  2. 它减轻了维护的重担,因为组件可以被更快的理解和调试或替换,而不必担心会损害其他最贱。
  3. 虽然信息隐藏本身并不能带来良好的性能,但是它可以提高性能优化的效率:一旦一个系统完成,并且概要分析确定了哪些组件会造成性能问题,这些组件可以在不影响其他组件的正确性下被优化。
  4. 信息隐藏增加了软件重用性,因为没有紧密耦合的组件通常证明在其他除了他们开发的上下文中是有用的。
  5. 信息隐藏降低了构建大型系统的风险,因为即时在系统不能工作的情况下,拆分的组件可能能证明是可以使用的。

Java有许多设施来帮助信息隐藏。访问控制机制具体说明了类、接口和成员的的可访问性。

THe accessibility of an entity is determined by the location of its declaration and by which, if any, of the access modifiers(private, protected and public) is present on the declaration. Proper use of these modifiers is essential to information hiding.

The rule of thumb is simple: make each class or member is inaccessible as possible.

换句话说。使用和足够你正在写的软件方法使用一致的最低可能访问级别。

Modifier of class or interface

对于最高级别(非嵌套)的类和接口,只有两个可能访问等级: 包内私有公开。如果你用public修饰符声明一个顶级的类或者接口,那么它将是公共的;否则它是包内私有的。如果一个顶级的类或者接口可以被做成包内私有的,那就应该是包内私有的。通过使其保内私有化,你可以将其作为实现的一部分而不是公开的API,而且你可以修改,代替或者在后续的版本中删除它,不用担心伤害已经存在的客户端。如果你使其公共化,你有义务永远维持它的兼容性。

如果包内私有的顶级类或接口只在一个类中被使用,考虑将顶级类设置成它唯一的私有静态嵌套类。这将减少从它的包下所有类到使用它的类的可访问性。

Modifier of member

对于成员(字段,方法,嵌套类和嵌套接口),有4中可访问级别,根据可访问性列出:

  • private -- THe member is accessible only from the top-level class where it is delared.
  • package-private -- The member is accessible from any class in the package where it is declared. Technically known as default access, this is the access level you get if no access modifier is specified (except for interface members, which are public by default)
  • protected -- The member is accessible from subclasses of the class where it is declared (subject to a few retrictions) and from any class in the package where it is declared.
  • public -- The member is accessible from anywhere

在仔细地设计了你的类的公共API后,你本能反应应该是使所有其他成员都是私有的。 只有在同一个包下的其他类真的需要访问一个成员时,你应该去掉private修饰符,让这个成员变成包内私有的。

对于公共类的成员来说,当访问级别从包内私有到受保护的时,会出现很大的可访问性的提高。

A protected member is part of the class's exported API and must be supported forever. Also, a protected member of an exported class represents a public commitment to an implementation detail.

有一个关键规则限制了减少方法可访问性的能力。

If a method overrides a superclass method, it cannot have a more restrictive access level in the subclass than in the superclass.

必须确保子类的实例可以再父类实例的任何地方都是可用的。

It is not acceptable to make a class, interface, or member a part of a pack-age's exported API to facilitate testing.

public class

Instance fields of public classes should rarely be public.

如果一个实例字段是非final的或者是对可变对象的引用,然后使它变成公共的:

  • give up the ability to limit the values that can be stored in the field.
  • give up ability to enforce invariants involving the filed.
  • give up the ability to take any action when the field is modified, so classes with public mutable fields are not generally thread-safe.

即使一个字段是final并且引用了不可变对象,让它变成公共的:

give up the flexibility to switch to a new internal data representation in which the field does not exist.

相同的建议应用在静态字段上,只有一个例外。

You can expose(揭露) constants via public static final fields, assuming the constants form an integral part of the abstraction provided by the class.

通常来说,这样的字段的名字包含了用下划线分割的大写字母。 重要的是,这些字段要么包含原始值,要么包含对不可变对象的运用。

A field containing a reference to amutable object has all the disadvantages of a nonfinal field.

虽然引用不能被修改,但是被应用的对象可以被修改 -- 这会导致灾难性的后果。

Note that a nonzero-length array is always mutable, so it is wrong for a class to have a public static final array field, or an accessor that returns such a field. If a class has such a field or accessor, clients will be able to modify the contents of the array. This is a frequent source of security hole.

// Potential security hole!
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();
}

在Java9中,作为模块系统(module system) 的一部分, 还引入了另外两个隐式访问级别。

A module is a grouping of packages, like a package is a grouping of classes. A module may explicitly export some of its packages via export declarations in its module declaration (which is by convention contained in a source file named module-info.java). Public and protected members of unexported packages in a module are inaccessible outside the module; within the module, accessibility is unaffected by export declarations.

Summary

  • 应该尽可能减少程序元素的可访问性(在合理范围内)
  • 谨慎地设计最小公共API后,应该组织任何流浪类,接口或成员成为API的一部分
  • 除了作为常量的公共静态最终态的字段之外,公共类应该没有公共字段
  • 确保公共静态最终态字段引用的对象是不可变的

16. 公共类中,使用访问器方法,而不是公共字段

不建议对象的字段直接用public进行修饰,这样并没有用到封装的便利性。 应该是private的字段和public的访问器方法(geters)和对于可变类的存入器(setters)。

if a class accessible outside its package, provide accessor methods to preserve the flexibility to change the class's internal representation.

对于package-private或者private的类暴露数据字段就没什么问题。 因为对于使用它们的字段仅仅局限在包内,如果需要更改,可以在不触及包外的任何代码的情况下进行。 对于private nested的类,更改范围进一步限制在了类中

if a class is package-private or is a private nested class, there is nothing inherently wrong with exposing its data fields -- assuming they do an adequate job of describing the abstraction provided by the class. This approach generates less visual clutter than the accessor-method approach, both in the class definition and in the client code that use it.

有几个类在Java库中违背了公共类不应该直接暴露字段的建议。重要的例子包含了在java.awt包中的pointDimension类。这些类不应该被效仿,而应该被警示。

虽然公共类直接暴露字段不是一个好的主意,但是如果字段不可变的,危害会小一些。举个例子,下面的类保证了每个实例代表了有效的时间:

// Public class with exposed immutable fields - questionable
public final Class Time {
    private static final int HOURS_PER_DAY = 24;
    private static final int MINUTES_PER_HOUR = 60;
    
    public final int hour;
    public final int minute;
    
    public Time(int hour, int minute) {
        if (hour < 0 || hour >= HOURS_PER_DAY)
            throw new IllegalArgumentException("Hour: " + hour);
            
        if (minute < 0 || minute >= MINUTES_PER_HOUR)
            throw new IllegalArgumentException("Min: " + midute);
        
        this.hour = hour;
        this.minute = minute;
    }
    
    ...
}

SUMMARY

公共类不应该暴露可变的字段, 对于公共类暴露不可变的字段,危害相对小,但是仍是存在问题的。 然而,有时候package-private或者private nested类需要公开字段,无论这类是可变的还是不可变的。

17. 减少可变性

什么是 immutable class

An immutable class is simply a class whose instances cannot be modified. All of the information contained in each instance is fixed for the lifetime of the object, so no changes can ever be observed.

Java库中包含了许多不可变的类,包括String,基本数据类型的包装类,BigIntegerBigDecimal。 这样有很多好处:

  • 不可变类比可变类更容易设计,实现和使用
  • 不太容易出错,并且更安全

五个规则

使类不可变,请遵循下面五个规则:

  • Don't private methods that modify the object's state - 不要提供修改对象状态的方法(被称为赋值方法)
  • Ensure that the class can't be extended. - 确保类不可被扩展。这阻止了粗心的或者怀有恶意的子类通过表现得如对象状态发生了改变来破坏类的不可变行为。防止子类化通常通过使类成为final来实现,但有一种替代方法,后续会进行讨论。
  • Make all fields final. - This clearly expreses your intent in a manner that is enforced by the system.
  • Make all fields private. 这样能防止客户端通过字段活的修改对象引用的权限然后直接修改这些对象。然而从技术上来说,允许不可变对象有包含基本值或引用到不可变对象的公共的最终态字段。但这是不推荐的。因为这排除了在以后的版本中更改内部表示。
  • Ensure exclusive access to any mutable components. 如果你的类有任何引用可变对象的字段,确保类的客户端无法获得这些对象的引用。永远不要将这样的字段初始化为客户端提供的对象引用或从访问器返回的字段。在构造器,访问器和readObject方法中使用防御性拷贝。

示例类 Complex

// Immutable complex number class

示例代码展示了复数对象,包含了实数和虚数两个部分。除了标准的Object的方法,还提供了实数和虚数的获取方法,也提供了四个基础运算操作(addition, subtractionmultiplicationdivision)。 值得注意的是,这些基础运算方法是直接返回新的Conplex实例,而不是修改当前实例。

Tips:
这样的方法名使用介词(比如`plus`)而不是用动词(比如`add`)。这突出了方法不会改变对象的值的情况。
BigInteger和BigDecimal类没有遵循这个明明规则,导致许多误用的地方。

不变性的好处

函数式方法在你不熟悉时,会显得不自然,但它支持不变性。不变性有许多好处:

Immutable objects are simple.

不可变对象只有一种状态,即创建它的状态。

Immutable objects are inherently thread-safe; they require no synchronization.

Since no thread can ever observe any effect of another thread on an immutable object, immutable objects can be shared freely.

不可变类应该鼓励客户端尽可能地重复使用已经存在的实例。一个简单的方法是为常用值提供公共静态最终常量。例如

public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);

这个规则可以进一步扩展。一个不可变类可以提供静态工厂 - 可以缓存频繁请求的实例来避免创建已经存在的新实例。从而减少内存占用和垃圾回收成本。设计一个新的类时选择静态工厂而不是公共构造函数,这能让你之后灵活地添加缓存,而无需修改客户端。

All the boxed primitive classes and BigInteger do this.

A consequence of the fact that immutable objects can be shared freely is that you never have to make defensive copies of them.

实际上,不需要创建任何拷贝,因为拷贝的永远和原来的是相等的。因为,在不可变类中不需要也不应该提供一个clone方法或者拷贝构造器。 这在早期的Java平台上没理解好,所以String类有一个拷贝构造器,但是它很少被使用。

不可变类的内部可变字段的指向可共享

Not only can you share immutable objects, but they can share their internals.

比如BigInteger,在内部,它使用了int signumint[] mag两个字段分别表示符号和大小值。其中的negate方法会产生一个新的BigInteger,相同的大小,相反的符号。但它没有必要拷贝大小的数组即时它是可变的,完全可以新建的BigInteger指向和原来相同的内部数组。

Immutable objects make great building blocks for other objects, whether mutable or immutable.

如果你知道一个复杂对象的组件对象在后面不会改变,那么维护它的不变性就会容易很多。这个原则具体的例子是:

immutable objects make great map keys and set elements: you don't have to worry about their values changing once they're in the map or set, which would destroy the map or set's invariants.

Immutable objects provide failure atomicity for free.

Their state never changes, so there is no prossibility of a temporary inconsistency.

不变性的缺点

The major disadvantage of immutable classes is that they require a separate object for each distinct value. 主要的缺点就是需要创建独立的对象。然而创建对象的成本可能很高,尤其如果对象很大的话。 举个例子,假设你有一个百万位的BigInteger,同时你想要改变它低阶位。BigIntegerflipBit方法可以实现,但是它会创建一个新的一样有百万位长度的BigInteger的实例,只有一位上有差别。这个操作需要与BigInteger的长度成比例的时间和空间。这与java.util.BigSet不一样。如果BigIntegerBitSet可以表示任意长的位序列,但是和BigInteger不同的是BitSet是可变的。BitSet类提供了一种方法,可以让你在固定的时间内改变百万比特实例的单个位的状态。

BitSet moby = ...;
moby.flip(0);

这个性能问题会被无限放大,如果你在每一步执行了多个生成新对象的操作,最终只要一个结果,中间的对象都被抛弃。有两个方法来应对这种问题:

To guess which multistep operations will be commonly required and to provided as a primitive

估计哪些多步骤的操作通常是必须的,提供原始类型的数据结构。如果一个多步骤的操作使用了原始类型,那么不变类不会在每步创建分开的对象。在对象内部,不可变对象可以被灵活使用。 举个例子。BigInteger有一个package-private的可变的“伴随类”,可以用来加速类似模块化求幂的多步骤操作。如果你可以精确地预测哪些操作客户端会想要使用,可以使用private-package可变伴随类的方式。 如果不能,最好提供一个public可变的伴随类。String类是在Java库中的主要例子,它的伴随类是StringBuilder, 以及它的淘汰对象StringBuffer

Let's discuss a few design alternatives.

  1. To guarantee immutability, a class must not permit itself to be subclassed by making the class final.
  2. Make all of its constructors private or package-private and add public static factories in palce of th epublic constructors. 👍.
public class Complex {
    private final double re;
    private final double im;
    
    private Complex(double re, double im) {
        this.re = re;
        this.im = im;
    }
    public static Complex valueOf(double re, double im) {
        return new Complex(re, im);
    }
    
    ... // Remainer unchanged
}

这样的方式通常是最好的替代方案。它是最灵活的,因为它允许使用多个package-private实现类。 对于在包外调用它的地方,不可变类是最终态的,因为它不能继承一个来自其他包的类并且缺少一个publicprotected的构造器。

实现静态类的 a trick way

之前讨论了静态类的许多规则,不能有方法来更新对象和它所有的字段必须是最终态的。

Infact these rules are a bit stronger than necessary and can be relaxed to improve performance. Some immutable classes have one or more nonfinal fields in which they cache the results of expensive computations the first time they are needed. If the same value is requested again, the cached value is returned, saving the cost of recalculation.

这个妙计保证了如果重复,会产生相同的计算结果,所以可以实现不可变类的效果。

One Caveat

One caveat should be addeed concerning serializability.

如果选择将不可变类实现Serializable,并且它包含了一个或多个可变对象字段,必须提供一个明确的readObject或者readResolve方法,或者使用ObjectOutputStream.writeUbsharedObjectInputStream.readUnshared方法,即使它的默认实例化结果是可接受的。否则,攻击者能创建一个这个类的可变实例。(在Item 88中细说)

Summarize

总体来首,需要阻止给所有gettersetter的强烈欲望。

Clases should be immutable unless there's a very good reason to make them mutable.

不可变类有很多好处,并且它的劣势只有在特定状况下的潜在的性能问题。

应该经常考虑小的值对象是不可变的,例如PhoneNumberComplex,同时也应该严肃地考虑让更大的值对象也不可变,例如StringBigInteger。只有你确认了有必要实现令人满意的性能时,应该为不可变类提供一个公共的可变伴随类。

对于不可变属性是不现实的类

If a class cannot be made immutable, limit its mutability as much as possible. Reducing the number of states in which an object can exist makes it easier to reason about the object and reduces the likelihood of errors.

因此,让每个字段都是final,除非有必须的理由来让他nofinal

declare every field private final unless there's a good reason to do otherwise.

Constructors should create fully initialized objects with all of their invariants established.