ITEM 59: KNOW AND USE THE LIBRARIES
假设你想在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);
}
如果随机方法工作正常,程序将打印一个接近50万的数字,但是如果运行它,您将发现它打印一个接近 666666 的数字。随机方法生成的数字中有三分之二落在其范围的下半部分!
随机方法的第三个缺陷是,在极少数情况下,它可能会灾难性地失败,返回一个超出指定范围的数字。这是因为该方法试图通过调用 Math.abs 将 rnd.nextInt() 返回的值映射到非负整数。如果 nextInt() 返回 Integer.MIN_VALUE 。Math.abs 将返回 Integer.MIN_VALUE。假设 n 不是 2 的幂,那么 MIN_VALUE 和 余数运算符(%)将返回一个负数。这几乎肯定会导致您的程序失败,而且这种失败可能很难重现。
要编写一个版本的随机方法来纠正这些缺陷,您必须了解相当多的伪随机数生成器、数论和 2 的补数算法。幸运的是,你不需要做这个 —— 已经有人为你做了。它被称为 Random.nextInt (int)。您不必关心它如何工作的细节(如果您感兴趣,可以研究文档或源代码)。一位具有算法背景的高级工程师花了大量时间设计、实现和测试这个方法,然后将它展示给该领域的几位专家,以确保它是正确的。然后,这个库进行了beta测试、发布,并被数百万程序员广泛使用了近20年。该方法还没有发现任何缺陷,但是如果发现了缺陷,将在下一个版本中进行修复。通过使用标准库,您可以利用编写它的专家的知识和以前使用它的人的经验。
从Java 7开始,您应该不再使用 Random。对于大多数情况,现在选择的随机数生成器是 ThreadLocalRandom。它产生更高质量的随机数,而且非常快。在我的机器上,它比 Random 快3.6倍。对于 fork 连接池和并行流,使用 SplittableRandom。
使用这些库的第二个好处是,您不必浪费时间为那些与您的工作无关的问题编写专门的解决方案。如果您和大多数程序员一样,您宁愿把时间花在应用程序上,而不是花在底层管道上。
使用标准库的第三个好处是,随着时间的推移,它们的性能会不断提高,而您无需为此付出任何努力。因为许多人使用它们,而且它们用于行业标准的基准测试,所以提供这些库的组织有强烈的动机使它们运行得更快。多年来,许多 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);
}
}
然而库太大了,不能学习所有的文档[Java9-api],但是每个程序员都应该熟悉java的基础知识。比如 java.lang, java.util 和 java.io 和它们的子包。可以根据需要获取其他库的知识。概述库的设施超出了本项目的范围,这些设施多年来已发展得非常庞大。
有几个库得一提。集合框架和 streams 库(Item 45-48)应该是每个程序员的基本工具包的一部分,就像 java.util.concurrent 中的并发实用程序的一部分一样。这个包包含简化多线程编程任务的高级实用程序和允许专家编写自己的高级并发抽象的低级原语。java.util 的高级部分 java.util.concurrent 在 item 80和81中讨论。
有时,库可能不能满足您的需求。你的需求越专业化,这种情况就越有可能发生。虽然您的第一个冲动应该是使用这些库,但是如果您已经了解了它们在某些领域提供的功能,而这些功能不能满足您的需要,那么就使用另一种实现。任何有限的库集所提供的功能中总会有漏洞。如果你在 Java 平台库中找不到你需要的东西,你的下一个选择应该是寻找高质量的第三方库,比如谷歌的优秀的开源 Guava 库[Guava]。如果您无法在任何适当的库中找到所需的功能,那么您可能别无选择,只能自己实现它。
总之,不要重新发明轮子。如果您需要做一些看起来比较常见的事情,那么在库中可能已经有一个工具可以做您想做的事情。如果有,就用它;如果你不知道,检查一下。一般来说,库代码可能比您自己编写的代码更好,并且随着时间的推移可能会得到改进。这并不反映你作为一个程序员的能力。规模经济决定了库代码得到的关注远远超过了大多数开发人员所能承担的相同功能。
网友评论