Advanced Search
Search Results
155 total results found
33. 优先考虑类型安全的异构容器
泛型的常见用法包括集合,如 Set<E> 和 Map<K,V> 和单个元素容器,如 ThreadLocal<T> 和 AtomicReference<T>。 在所有这些用途中,它都是参数化的容器。 这限制了每个容器只能有固定数量的类型参数。 通常这正是你想要的。 一个 Set 有单一的类型参数,表示它的元素类型; 一个 Map 有两个,代表它的键和值的类型;等等。 然而有时候,你需要更多的灵活性。 例如,数据库一行记录可以具有任意多列,并且能够以类型安全的方式访问它们是很好的。 幸运的是,有一个简单的方法可...
44. 优先使用标准的函数式接口
现在 Java 已经有 lambda 表达式,编写 API 的最佳实践已经发生了很大的变化。 例如,模板方法模式[Gamma95],其中一个子类重写原始方法以专门化其父类的行为,变得没有那么吸引人。 现代替代的选择是提供一个静态工厂或构造方法来接受函数对象以达到相同的效果。 通常地说,可以编写更多以函数对象为参数的构造方法和方法。 选择正确的函数式参数类型需要注意。 考虑 LinkedHashMap。 可以通过重写其受保护的 removeEldestEntry 方法将此类用作缓存,每次将新的 key 值加入...
51. 仔细设计方法签名
这一条目是 API 设计提示的大杂烩,但它们本身并足以设立一个单独的条目。综合起来,这些设计提示将帮助你更容易地学习和使用 API,并且更不容易出错。 仔细选择方法名名称。名称应始终遵守标准命名约定(详见第 68 条)。你的主要目标应该是选择与同一包中的其他名称一致且易于理解的名称。其次是应该是选择与更广泛的共识一致的名称。避免使用较长的方法名。如果有疑问,可以从 Java 类库 API 中寻求指导。尽管类库中也存在许多不一致之处(考虑到这些类库的规模和范围,这是不可避免的),也提供了相当客观的认可和共识。...
50. 必要时进行防御性拷贝
愉快使用 Java 的原因,它是一种安全的语言(safe language)。 这意味着在缺少本地方法(native methods)的情况下,它不受缓冲区溢出,数组溢出,野指针以及其他困扰 C 和 C++ 等不安全语言的内存损坏错误的影响。 在一种安全的语言中,无论系统的任何其他部分发生什么,都可以编写类并确切地知道它们的不变量会保持不变。 在将所有内存视为一个巨大数组的语言中,这是不可能的。 即使在一种安全的语言中,如果不付出一些努力,也不会与其他类隔离。必须防御性地编写程序,假定类的客户端尽力摧毁类其...
49. 检查参数有效性
本章(第 8 章)讨论了方法设计的几个方面:如何处理参数和返回值,如何设计方法签名以及如何记载方法文档。 本章中的大部分内容适用于构造方法和其他普通方法。 与第 4 章一样,本章重点关注可用性,健壮性和灵活性上。 大多数方法和构造方法对可以将哪些值传递到其对应参数中有一些限制。 例如,索引值必须是非负数,对象引用必须为非 null。 你应该清楚地在文档中记载所有这些限制,并在方法主体的开头用检查来强制执行。 应该尝试在错误发生后尽快检测到错误,这是一般原则的特殊情况。 如果不这样做,则不太可能检测到错误,并...
48. 谨慎使用流并行
在主流语言中,Java 一直处于提供简化并发编程任务的工具的最前沿。 当 Java 于 1996 年发布时,它内置了对线程的支持,包括同步和 wait / notify 机制。 Java 5 引入了 java.util.concurrent 类库,带有并发集合和执行器框架。 Java 7 引入了 fork-join 包,这是一个用于并行分解的高性能框架。 Java 8 引入了流,可以通过对 parallel 方法的单个调用来并行化。 用 Java 编写并发程序变得越来越容易,但编写正确快速的并发程序还像以前一样...
47. 优先使用 Collection 而不是 Stream 来作为方法的返回类型
许多方法返回元素序列(sequence)。在 Java 8 之前,通常方法的返回类型是 Collection,Set 和 List 这些接口;还包括 Iterable 和数组类型。通常,很容易决定返回哪一种类型。规范(norm)是返回 Collection 接口。如果该方法仅用于启用 for-each 循环,或者返回的序列不能实现某些 Collection 方法 (通常是 contains(Object)),则使用迭代(Iterable)接口。如果返回的元素是基本类型或有严格的性能要求,则使用数组。在 Java...
46. 优先考虑流中无副作用的函数
如果你是一个刚开始使用流的新手,那么很难掌握它们。仅仅将计算表示为流管道是很困难的。当你成功时,你的程序将运行,但对你来说可能没有意识到任何好处。流不仅仅是一个 API,它是基于函数式编程的范式(paradigm)。为了获得流提供的可表达性、速度和某些情况下的并行性,你必须采用范式和 API。 流范式中最重要的部分是将计算结构化为一系列转换,其中每个阶段的结果尽可能接近前一阶段结果的纯函数(pure function)。 纯函数的结果仅取决于其输入:它不依赖于任何可变状态,也不更新任何状态。 为了实现这一点...
45. 明智审慎地使用 Stream
在 Java 8 中添加了 Stream API,以简化串行或并行执行批量操作的任务。 该 API 提供了两个关键的抽象:流 (Stream),表示有限或无限的数据元素序列,以及流管道 (stream pipeline),表示对这些元素的多级计算。 Stream 中的元素可以来自任何地方。 常见的源包括集合、数组、文件、正则表达式模式匹配器、伪随机数生成器和其他流。 流中的数据元素可以是对象引用或基本类型。 支持三种基本类型:int,long 和 double。 Stream pipeline 由 Sour...
43. 方法引用优于 lambda 表达式
lambda 优于匿名类的主要优点是它更简洁。Java 提供了一种生成函数对象的方法,比 lambda 还要简洁,那就是:方法引用(method references)。下面是一段程序代码片段,它维护一个从任意键到整数值的映射。如果将该值解释为键的实例个数,则该程序是一个多重集合的实现。该代码的功能是,根据键找到整数值,然后在此基础上加 1: map.merge(key, 1, (count, incr) -> count + incr); 请注意,此代码使用 merge 方法,该方法已添加到 Java ...
34. 使用枚举类型替代整型常量
枚举是其合法值由一组固定的常量组成的一种类型,例如一年中的季节,太阳系中的行星或一副扑克牌中的花色。 在将枚举类型添加到该语言之前,表示枚举类型的常见模式是声明一组名为 int 的常量,每个类型的成员都有一个常量: // The int enum pattern - severely deficient! public static final int APPLE_FUJI = 0; public static final int APPLE_PIPPIN = 1; public s...
42. lambda 表达式优于匿名类
在 Java 8 中,添加了函数式接口,lambda 表达式和方法引用,以便更容易地创建函数对象。 Stream API 随着其他语言的修改一同被添加进来,为处理数据元素序列提供类库支持。 在本章中,我们将讨论如何充分利用这些功能。 以往,使用单一抽象方法的接口(或者很少使用的抽象类)被用作函数类型。 它们的实例(称为函数对象)表示函数(functions)或行动(actions)。 自从 JDK 1.1 于 1997 年发布以来,创建函数对象的主要手段就是匿名类(详见第 24 条)。 下面是一段代码片段,...
41. 使用标记接口定义类型
标记接口(marker interface),是不包含方法声明的接口,只是指定(或「标记」)一个类实现了具有某些属性的接口。 例如,考虑 Serializable 接口(第 12 章)。通过实现这个接口,一个类表明它的实例可以写入 ObjectOutputStream(或被「序列化」)。 你可能会听说过标记注解(详见第 39 条)使得标记接口过时了。 这个断言是不正确的。 标记接口与标记注解相比具有两个优点。 首先,也是最重要的一点,标记接口定义了一个由标记类实例实现的类型;标记注解则没有定义这样的类型。 ...
40. 始终使用 Override 注解
Java 类库包含几个注解类型。对于典型的程序员来说,最重要的是 @Override。此注解只能在方法声明上使用,它表明带此注解的方法声明重写了父类的声明。如果始终使用这个注解,它将避免产生大量的恶意 bug。考虑这个程序,在这个程序中,类 Bigram 表示双字母组合,或者是有序的一对字母: // Can you spot the bug? public class Bigram { private final char first; private final char second; ...
39. 注解优于命名模式
过去,通常使用命名模式(naming patterns)来指示某些程序元素需要通过工具或框架进行特殊处理。 例如,在第 4 版之前,JUnit 测试框架要求其用户通过以 test[Beck04] 开始名称来指定测试方法。 这种技术是有效的,但它有几个很大的缺点。 首先,拼写错误导致失败,但不会提示。 例如,假设意外地命名了测试方法 tsetSafetyOverride 而不是 testSafetyOverride。 JUnit 3 不会报错,但它也不会执行测试,导致错误的安全感。 命名模式的第二个缺点是无法...
38. 使用接口模拟可扩展的枚举
在几乎所有方面,枚举类型都优于本书第一版中描述的类型安全模式[Bloch01]。 从表面上看,一个例外涉及可扩展性,这在原始模式下是可能的,但不受语言结构支持。 换句话说,使用该模式,有可能使一个枚举类型扩展为另一个;使用语言功能特性,它不能这样做。 这不是偶然的。 大多数情况下,枚举的可扩展性是一个糟糕的主意。 令人困惑的是,扩展类型的元素是基类型的实例,反之亦然。 枚举基本类型及其扩展的所有元素没有好的方法。 最后,可扩展性会使设计和实现的很多方面复杂化。 也就是说,对于可扩展枚举类型至少有一个有说服力...
37. 使用 EnumMap 替代序数索引
有时可能会看到使用 ordinal 方法(详见第 35 条)来索引到数组或列表的代码。 例如,考虑一下这个简单的类来代表一种植物: class Plant { enum LifeCycle { ANNUAL, PERENNIAL, BIENNIAL } final String name; final LifeCycle lifeCycle; Plant(String name, LifeCycle lifeCycle) { this.name = name; ...
36. 使用 EnumSet 替代位属性
如果枚举类型的元素主要用于集合中,则传统上使用 int 枚举模式(详见第 34 条),将 2 的不同次幂赋值给每个常量: // Bit field enumeration constants - OBSOLETE! public class Text { public static final int STYLE_BOLD = 1 << 0; // 1 public static final int STYLE_ITALIC = 1 << 1; // 2 ...
35. 使用实例属性替代序数
许多枚举通常与单个 int 值关联。所有枚举都有一个 ordinal 方法,它返回每个枚举常量类型的数值位置。你可能想从序数中派生一个关联的 int 值: // Abuse of ordinal to derive an associated value - DON'T DO THIS public enum Ensemble { SOLO, DUET, TRIO, QUARTET, QUINTET, SEXTET, SEPTET, OCTET, NONET, DECTET; ...
52. 明智审慎地使用重载
下面的程序是一个善意的尝试,根据 Set、List 或其他类型的集合对它进行分类: // Broken! - What does this program print? public class CollectionClassifier { public static String classify(Set<?> s) { return "Set"; } public static String classify(List<?> lst) { retu...