位域表示还允许你使用按位算术有效地执行集合运算,如并集和交集。 但是位域具有 int 枚举常量等的所有缺点。 当打印为数字时,解释位域比简单的 int 枚举常量更难理解。 没有简单的方法遍历所有由位域表示的元素。 最后,必须预测在编写 API 时需要的最大位数,并相应地为位域(通常为 int 或 long)选择一种类型。 一旦你选择了一个类型,你就不能在不改变 API 的情况下超过它的宽度(32 或 64 位)。
当需要传递一组常量时,一些优先使用枚举而不是 int 常量的程序员仍然坚持使用位域。 没有理由这样做,因为存在更好的选择。 java.util 包提供了 EnumSet 类来有效地表示从单个枚举类型中提取的值集合。 这个类实现了 Set 接口,提供了所有其他 Set 实现的丰富性,类型安全性和互操作性。 但是在内部,每个 EnumSet 都表示为一个位向量(bit vector)。 如果底层的枚举类型有 64 个或更少的元素,并且大多数情况下,整个 EnumSet 用单个 long 表示,所以它的性能与位域的性能相当。 批量操作(如 removeAll 和 retainAll)是使用按位算术实现的,就像你手动操作位域一样。 但是完全避免了手动进行位操作导致的丑陋和潜在错误:EnumSet 为你完成这一困难工作。
下面是前一个使用枚举和枚举集合替代位域的示例。 它更短,更清晰,更安全:
1 2 3 4 5 6 7
// EnumSet - a modern replacement for bit fields publicclassText { publicenumStyle { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }
// Any Set could be passed in, but EnumSet is clearly best publicvoidapplyStyles(Set<Style> styles) { ... } }
如果方法抛出的异常与它所执行的任务没有明显的联系,这种情形将会使人不知所措。当方法传递由低层抽象抛出的异常时,往往会发生这种情况。除了使人感到困惑之外,这也“污染”了具有实现细节的更高层的 API 。如果高层的实现在后续的发行版本中发生了变化,它所抛出的异常也可能会跟着发生变化,从而潜在地破坏现有的客户端程序。
/* Exception Translation */ try { ... /* Use lower-level abstraction to do our bidding */ } catch (LowerLevelException e) { thrownewHigherLevelException(...); }
下面的异常转译例子取自于 AbstractSequentialList 类,该类是 List 接口的一个骨架实现(skeletal implementation),详见第 20 条。在这个例子中,按照List<E>接口中 get 方法的规范要求,异常转译是必需的:
1 2 3 4 5 6 7 8 9 10 11 12 13
/** * Returns the element at the specified position in this list. * @throws IndexOutOfBoundsException if the index is out of range * ({@code index < 0 || index >= size()}). */ public E get(int index) { ListIterator<E> i = listIterator(index); try { return(i.next() ); } catch (NoSuchElementException e) { thrownewIndexOutOfBoundsException("Index: " + index); } }
尽管依赖注入极大地提高了灵活性和可测试性,但它可能使大型项目变得混乱,这些项目通常包含数千个依赖项。使用依赖注入框架(如 Dagger [Dagger]、Guice [Guice] 或 Spring [Spring])可以消除这些混乱。这些框架的使用超出了本书的范围,但是请注意,为手动依赖注入而设计的 API 非常适合这些框架的使用。
// Default method added to the Collection interface in Java 8 defaultbooleanremoveIf(Predicate<? super E> filter) { Objects.requireNonNull(filter); booleanresult=false; for (Iterator<E> it = iterator(); it.hasNext(); ) { if (filter.test(it.next())) { it.remove(); result = true; } } return result; }
// The WRONG way to use varargs to pass one or more arguments! staticintmin(int... args) { if (args.length == 0) thrownewIllegalArgumentException("Too few arguments"); intmin= args[0]; for (inti=1; i < args.length; i++) if (args[i] < min) min = args[i]; return min; }
// The right way to use varargs to pass one or more arguments staticintmin(int firstArg, int... remainingArgs) { intmin= firstArg; for (int arg : remainingArgs) if (arg < min) min = arg; return min; }
实现 **Serializable 接口的一个主要代价是,一旦类的实现被发布,它就会降低更改该类实现的灵活性。** 当类实现 Serializable 时,其字节流编码(或序列化形式)成为其导出 API 的一部分。一旦广泛分发了一个类,通常就需要永远支持序列化的形式,就像需要支持导出 API 的所有其他部分一样。如果你不努力设计自定义序列化形式,而只是接受默认形式,则序列化形式将永远绑定在类的原始内部实现上。换句话说,如果你接受默认的序列化形式,类中私有的包以及私有实例字段将成为其导出 API 的一部分,此时最小化字段作用域(详见第 15 条)作为信息隐藏的工具,将失去其有效性。
有两种常见的方法来实现单例。两者都基于保持构造方法私有和导出公共静态成员以提供对唯一实例的访问。在第一种方法中,成员是 final 修饰的属性:
1 2 3 4 5 6
// Singleton with public final field publicclassElvis { publicstaticfinalElvisINSTANCE=newElvis(); privateElvis() { ... } publicvoidleaveTheBuilding() { ... } }
私有构造方法只调用一次,来初始化公共静态 final Elvis.INSTANCE 属性。缺少一个公共的或受保护的构造方法,保证了全局的唯一性:一旦 Elvis 类被初始化,一个 Elvis 的实例就会存在——不多也不少。客户端所做的任何事情都不能改变这一点,但需要注意的是:特权客户端可以使用 AccessibleObject.setAccessible 方法,以反射方式调用私有构造方法(详见第 65 条)。如果需要防御此攻击,请修改构造函数,使其在请求创建第二个实例时抛出异常。
在第二个实现单例的方法中,公共成员是一个静态的工厂方法:
1 2 3 4 5 6 7 8
// Singleton with static factory publicclassElvis { privatestaticfinalElvisINSTANCE=newElvis(); privateElvis() { ... } publicstatic Elvis getInstance() { return INSTANCE; }
publicvoidleaveTheBuilding() { ... } }
所有对 Elvis.getInstance 的调用都返回相同的对象引用,并且不会创建其他的 Elvis 实例(与前面提到的警告相同)。
公共属性方法的主要优点是 API 明确表示该类是一个单例:公共静态属性是 final 的,所以它总是包含相同的对象引用。 第二个好处是它更简单。
为了将上述方法中实现的单例类变成是可序列化的 (第 12 章),仅仅将 implements Serializable 添加到声明中是不够的。为了保证单例模式不被破坏,必须声明所有的实例字段为 transient,并提供一个 readResolve 方法(详见第 89 条)。否则,每当序列化的实例被反序列化时,就会创建一个新的实例,在我们的例子中,导致出现新的 Elvis 实例。为了防止这种情况发生,将如下的 readResolve 方法添加到 Elvis 类:
1 2 3 4 5 6
// readResolve method to preserve singleton property private Object readResolve() { // Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator. return INSTANCE; }
实现一个单例的第三种方法是声明单一元素的枚举类:
1 2 3 4 5 6
// Enum singleton - the preferred approach publicenumElvis { INSTANCE;