Tips
书中的源代码地址:https://github.com/jbloch/effective-java-3e-source-code
注意,书中的有些代码里方法是基于Java 9 API中的,所以JDK 最好下载 JDK 9以上的版本。
假设想要生成0到某个上界之间的随机整数。对于这个常见的任务,许多程序员会编写一个类似这样的方法:
// Common but deeply flawed! static Random rnd = new Random(); static int random(int n) { return Math.abs(rnd.nextInt()) % n; }这个方法可能看起来不错,但它有三个缺陷。 首先,如果n是一个比较小的2的乘方,则随机数的序列将在相当短的时间段后开始重复。 第二个缺陷是,如果n不是2的乘方,平均而言,某些数字会比其他数字出现得更加频繁。 如果n很大,这种效果可能非常明显。 以下程序有力地证明了这一点,该程序在精心选择的范围内生成了100万个随机数,然后打印出有多少个数字落在范围的上半部分:
public static void main(String[] args) { int n = 2 * (Integer.MAX_VALUE / 3); int low = 0; for (int i = 0; i < 1000000; i++) if (random(n) < n/2) low++; System.out.println(low); }如果random方法正常工作,程序将打印接近50万的数字,但如果运行它,你会发现它打印的数字接近666,666。random方法生成的三分之二数字落在其范围的上半部分!
random方法的第三个缺陷是,在极少数情况下,它可能会灾难性地失败,返回超出指定范围之外的数字。 这是因为该方法尝试通过调用Math.abs将rnd.nextInt()返回的值映射到非负整数。 如果nextInt()返回Integer.MIN_VALUE,则Math.abs也会返回Integer.MIN_VALUE,假设n不是2的乘方,取模运算符(%)将返回负数。 这几乎肯定会导致程序失败,并且可能难以重现。
要编写一个纠正这些缺陷的random方法的版本,你必须知道关于伪随机数生成器,数论和二进制补码算法的知识。幸运的是,你不必这样做 —— 它已经为你完成了,就是Random.nextInt(int)方法。你不必关心它如何完成其工作的细节(,如果您很好奇,可以研究文档或源代码)。一位具有算法背景的高级工程师花费了大量时间来设计,实现和测试这种方法,然后向该领域的几位专家展示,以确保其正确性。然后,这个类库经过了beta测试,发布,并被数百万程序员广泛使用了近二十年。该方法尚未发现任何缺陷,但如果发现了缺陷,将在下一个版本中修复。通过使用标准类库,可以利用编写类库专家的知识以及在前人使用它的经验。
从Java 7开始,就不应再使用Random了。 对于大多数用途,选择的随机数生成器现在是ThreadLocalRandom。 它产生更高质量的随机数,而且速度非常快。 在我的机器上,它比Random快3.6倍。 对于fork-join池和并行流的应用,请使用SplittableRandoms。
使用这些类库的第二个好处是,不必浪费时间为那些与你的工作关联不大的问题上,而去编写专门的解决方案。如果像大多数程序员一样,那么宁愿将时间花在应用程序上,而不是底层内容上。
使用标准类库的第三个优点是,它们的性能会随着时间的推移而不断提高,而你无需付出任何努力。 因为许多人使用它们并且因为它们被用于行业标准基准测试,所以提供这些类库的组织有强烈的动力使它们运行得更快。 多年来,许多Java平台类库都经过重写,有时甚至是重复编写,从而显着提升性能。
使用类库的第四个优点是它们倾向于随着时间的推移不断增加功能。 如果某个类库遗失了某些东西,开发人员社区就会知道它,并且可能会在后续版本中添加缺少的功能。
使用标准库的最后一个好处是,可以将代码放在主流中。这样的代码更容易被开发人员阅读、维护和重用。
鉴于所有这些优点,使用类库设施优先于专门实现似乎是合乎逻辑的,但许多程序员并不这样做。为什么不呢? 也许他们不知道类库工具设施的存在。 在每个主要版本中,都会向类库中添加许多特性,了解这些新增特性是非常值得的。每次有Java平台的主要版本发布时,都会发布一个web页面来描述它的新特性。这些页面非常值得一读[Java8-feat, Java9-feat]。为了强调这一点,假设你想编写一个程序来打印命令行中指定的URL的内容(这大致与Linux系统下curl命令相同)。 在Java 9之前,这段代码有点乏味,但在Java 9中,transferTo方法被添加到InputStream中。 以下是使用此新方法执行此任务的完整程序:
// Printing the contents of a URL with transferTo, added in Java 9 public static void main(String[] args) throws IOException { try (InputStream in = new URL(args[0]).openStream()) { in.transferTo(System.out); } }