Advanced Search
Search Results
127 total results found
13. 谨慎地重写 clone 方法
Cloneable 接口的目的是作为一个 mixin 接口 (详见第 20 条),公布这样的类允许克隆。不幸的是,它没有达到这个目的。它的主要缺点是缺少 clone 方法,而 Object 的 clone 方法是受保护的。你不能,不借助反射 (详见第 65 条),仅仅因为它实现了 Cloneable 接口,就调用对象上的 clone 方法。即使是反射调用也可能失败,因为不能保证对象具有可访问的 clone 方法。尽管存在许多缺陷,该机制在合理的范围内使用,所以理解它是值得的。这个条目告诉你如何实现一个行为良好的...
24. 支持使用静态成员类而不是非静态类
嵌套类(nested class)是在另一个类中定义的类。 嵌套类应该只存在于其宿主类(enclosing class)中。 如果一个嵌套类在其他一些情况下是有用的,那么它应该是一个顶级类。 有四种嵌套类:静态成员类,非静态成员类,匿名类和局部类。 除了第一种以外,剩下的三种都被称为内部类(inner class)。 这个条目告诉你什么时候使用哪种类型的嵌套类以及为什么使用。 静态成员类是最简单的嵌套类。 最好把它看作是一个普通的类,恰好在另一个类中声明,并且可以访问所有宿主类的成员,甚至是那些被声明为私有...
31. 使用限定通配符来增加 API 的灵活性
如条目 28 所述,参数化类型是不变的。换句话说,对于任何两个不同类型的 Type1 和 Type2,List<Type1> 既不是 List<Type2> 的子类型也不是其父类型。尽管 List<String> 不是 List<Object> 的子类型是违反直觉的,但它确实是有道理的。 可以将任何对象放入 List<Object> 中,但是只能将字符串放入 List<String> 中。 由于 List<String> 不能做 List<Object> 所能做的所有事情,所以它不是一个子类型(条目 10 中的...
30. 优先使用泛型方法
正如类可以是泛型的,方法也可以是泛型的。 对参数化类型进行操作的静态工具方法通常都是泛型的。 集合中的所有“算法”方法(如 binarySearch 和 sort)都是泛型的。 编写泛型方法类似于编写泛型类型。 考虑这个方法,它返回两个集合的并集: // Uses raw types - unacceptable! [Item 26] public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result....
29. 优先考虑泛型
参数化声明并使用 JDK 提供的泛型类型和方法通常不会太困难。 但编写自己的泛型类型有点困难,但值得努力学习。 考虑条目 7 中的简单堆栈实现: // Object-based collection - a prime candidate for generics public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_...
28. 列表优于数组
数组在两个重要方面与泛型不同。 首先,数组是协变的(covariant)。 这个吓人的单词意味着如果 Sub 是 Super 的子类型,则数组类型 Sub[] 是数组类型 Super[] 的子类型。 相比之下,泛型是不变的(invariant):对于任何两种不同的类型 Type1 和 Type2,List<Type1> 既不是 List<Type2> 的子类型也不是父类型。[JLS,4.10; Naftalin07, 2.5]。 你可能认为这意味着泛型是不足的,但可以说是数组缺陷。 这段代码是合法的: // F...
27. 消除非检查警告
使用泛型编程时,会看到许多编译器警告:未经检查的强制转换警告,未经检查的方法调用警告,未经检查的参数化可变长度类型警告以及未经检查的转换警告。 你使用泛型获得的经验越多,获得的警告越少,但不要期望新编写的代码能够干净地编译。 许多未经检查的警告很容易消除。 例如,假设你不小心写了以下声明: Set<Lark> exaltation = new HashSet(); 编译器会提醒你你做错了什么: Venery.java:4: warning: [unchecked] unchecked conversi...
26. 不要使用原始类型
首先,有几个术语。一个类或接口,它的声明有一个或多个类型参数(type parameters ),被称之为泛型类或泛型接口[JLS,8.1.2,9.1.2]。 例如,List 接口具有单个类型参数 E,表示其元素类型。 接口的全名是 List<E>(读作「E」的列表),但是人们经常称它为 List。 泛型类和接口统称为泛型类型(generic types)。 每个泛型定义了一组参数化类型(parameterized types),它们由类或接口名称组成,后跟一个与泛型类型的形式类型参数[JLS,4.4,4....
25. 将源文件限制为单个顶级类
虽然 Java 编译器允许在单个源文件中定义多个顶级类,但这样做没有任何好处,并且存在重大风险。 风险源于在源文件中定义多个顶级类使得为类提供多个定义成为可能。 使用哪个定义会受到源文件传递给编译器的顺序的影响。 为了具体说明,请考虑下面源文件,其中只包含一个引用其他两个顶级类(Utensil 和 Dessert 类)的成员的 Main 类: public class Main { public static void main(String[] args) { System.out...
23. 类层次结构优于标签类
有时你可能会碰到一个类,它的实例有两个或更多的风格,并且包含一个标签字段(tag field),表示实例的风格。 例如,考虑这个类,它可以表示一个圆形或矩形: // Tagged class - vastly inferior to a class hierarchy! class Figure { enum Shape { RECTANGLE, CIRCLE }; // Tag field - the shape of this figure final Shape shape; ...
14. 考虑实现 Comparable 接口
与本章讨论的其他方法不同,compareTo 方法并没有在 Object 类中声明。 相反,它是 Comparable 接口中的唯一方法。 它与 Object 类的 equals 方法在性质上是相似的,除了它允许在简单的相等比较之外的顺序比较,它是泛型的。 通过实现 Comparable 接口,一个类表明它的实例有一个自然顺序(natural ordering)。 对实现 Comparable 接口的对象数组排序非常简单,如下所示: Arrays.sort(a); 它很容易查找,计算极端数值,以及维护 C...
22. 接口仅用来定义类型
当类实现接口时,该接口作为一种类型(type),可以用来引用类的实例。因此,一个类实现了一个接口,因此表明客户端可以如何处理类的实例。为其他目的定义接口是不合适的。 一种失败的接口就是所谓的常量接口(constant interface)。 这样的接口不包含任何方法; 它只包含静态 final 属性,每个输出一个常量。 使用这些常量的类实现接口,以避免需要用类名限定常量名。 这里是一个例子: // Constant interface antipattern - do not use! public int...
21. 为后代设计接口
在 Java 8 之前,不可能在不破坏现有实现的情况下为接口添加方法。 如果向接口添加了一个新方法,现有的实现通常会缺少该方法,从而导致编译时错误。 在 Java 8 中,添加了默认方法(default method)构造[JLS 9.4],目的是允许将方法添加到现有的接口。 但是增加新的方法到现有的接口是充满风险的。 默认方法的声明包含一个默认实现,该方法允许实现接口的类直接使用,而不必实现默认方法。 虽然在 Java 中添加默认方法可以将方法添加到现有接口,但不能保证这些方法可以在所有已有的实现中使用。...
20. 接口优于抽象类
Java 有两种机制来定义允许多个实现的类型:接口和抽象类。 由于在 Java 8 [JLS 9.4.3] 中引入了接口的默认方法(default methods ),因此这两种机制都允许为某些实例方法提供实现。 一个主要的区别是要实现由抽象类定义的类型,类必须是抽象类的子类。 因为 Java 只允许单一继承,所以对抽象类的这种限制严格限制了它们作为类型定义的使用。 任何定义所有必需方法并服从通用约定的类都可以实现一个接口,而不管类在类层次结构中的位置。 现有的类可以很容易地进行改进来实现一个新的接口。 你...
19. 要么设计继承并提供文档说明,要么禁用继承
条目 18 中提醒你注意继承没有设计和文档说明的「外来」类的子类化的危险。 那么对于专门为了继承而设计并且具有良好文档说明的类而言,这又意味着什么呢? 首先,这个类必须准确地描述重写每个方法带来的影响。 换句话说,该类必须文档说明可重写方法的自用性(self-use)。 对于每个 public 或者 protected 的方法,文档必须指明方法调用哪些可重写方法,以何种顺序调用的,以及每次调用的结果又是如何影响后续处理。 (重写方法,这里是指非 final 修饰的方法,无论是公开还是保护的。)更一般地说,一...
18. 组合优于继承
继承是实现代码重用的有效方式,但并不总是最好的工具。使用不当,会导致脆弱的软件。 在包中使用继承是安全的,其中子类和父类的实现都在同一个程序员的控制之下。对应专门为了继承而设计的,并且有文档说明的类来说(详见第 19 条),使用继承也是安全的。 然而,从普通的具体类跨越包级边界继承,是危险的。 提醒一下,本书使用「继承」一词,其含义是实现继承(当一个类扩展另一个类时)。 在本条目中讨论的问题不适用于接口继承(当类实现接口,或者当接口继承另一个接口时)。 与方法调用不同,继承打破了封装[Snyder86]。 ...
17. 最小化可变性
不可变类简单来说是其实例不能被修改的类。 包含在每个实例中的所有信息在对象的生命周期中是固定的,因此不会观察到任何变化。 Java 平台类库包含许多不可变的类,包括 String 类、基本类型包装类以及 BigInteger 类和 BigDecimal 类。 有很多很好的理由:不可变类比可变类更易于设计,实现和使用。 他们不容易出错,并且更安全。 要使一个类成为不可变类,请遵循以下五条规则: 不要提供修改对象状态的方法(也称为 mutators,设值方法)。 确保这个类不能被继承。 这可以防止粗心或...
16. 在公共类中使用访问方法而不是公共属性
有时候,你可能会试图写一些退化的类(degenerate classes),除了集中实例属性之外别无用处: // Degenerate classes like this should not be public! class Point { public double x; public double y; } 由于这些类的数据属性可以直接被访问,因此这些类不提供封装的好处(详见第 15 条)。 如果不更改 API,则无法更改其表示形式,无法强制执行不变量,并且在访问属性时无法执行辅助操...
15. 使类和成员的可访问性最小化
将设计良好的组件与设计不佳的组件区分开来的最重要的因素是,隐藏内部数据和其他实现细节的程度。一个设计良好的组件隐藏了它的所有实现细节,干净地将它的 API 与它的实现分离开来。然后,组件只通过它们的 API 进行通信,并且对彼此的内部工作一无所知。这一概念,被称为信息隐藏或封装,是软件设计的基本原则[Parnas72]。 信息隐藏很重要有很多原因,其中大部分来源于它将组成系统的组件分离开来,允许它们被独立地开发,测试,优化,使用,理解和修改。这加速了系统开发,因为组件可以并行开发。它减轻了维护的负担,因为可...
32. 合理地结合泛型和可变参数
在 Java 5 中,可变参数方法(详见第 53 条)和泛型都被添加到平台中,所以你可能希望它们能够正常交互; 可悲的是,他们并没有。 可变参数的目的是允许客户端将一个可变数量的参数传递给一个方法,但这是一个脆弱的抽象(leaky abstraction):当你调用一个可变参数方法时,会创建一个数组来保存可变参数;那个应该是实现细节的数组是可见的。 因此,当可变参数具有泛型或参数化类型时,会导致编译器警告混淆。 回顾条目 28,非具体化(non-reifiable)的类型是其运行时表示比其编译时表示具有更少...