第59项:了解和使用类库

  假设你希望产生位于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比较大,这个影响就会相当的明显。这可以通过下面的程序直观地体现出来,它会产生一百万个细心指定范围内的随机数,并打印出有多少个数字落在随机数取值范围的前半部分:

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方法工作正常的话,这个程序打印出来的数将接近一百万的一半,但是如果真正运行这个程序,就会发现它打印出来的数接近于666666.由random方法产生的数字有2/3落在随机数取值范围的前半部分。

  random方法的第三个缺点是,在极少数情况下,它的失败是灾难性的,返回一个落在指定范围之外的数。之所以如此,是因为这个方法试图通过调用Math.abs,将rnd.nextInt()返回的值映射为一个非负整数int。假如nextInt()返回Integer.MIN_VALUE,那么Math.abs也会返回Integer.MIN_VALUE,假设n不是2的乘方,那么取模操作符(%)将返回一个负数。这几乎肯定会使程序失败,而且这种失败很难重现。

  为了编写能修正这三个缺点的random方法,有必要了解关于伪随机数生成器、数论和2的求补算法的相关只是。幸运的是,你并不需要自己来做这些工作————已经有现成的成果可以为你所用。它被称为Random.nextInt(int),你无需关心它如何完成其工作的细节(如果你有强烈的好奇心,可以研究它的文档或者源代码)。具有算法背景的高级工程师已经花了大量的实践来设计、实现和测试这个方法,然后经过这个领域中的专家的审查,以确保它的正确性。然后,标准类库经过Beta测试、发行和将近二十年的成千上万程序猿的广泛使用。在这个方法中还没有发现过缺陷,但是,如果将来发现有缺陷,在下一个发行版本中就会修正这些缺陷。通过使用标准类库,可以充分利用这些编写标准类库的专家的只是,以及在你之前的其他人的使用经验

  从Java 7开始,你不应再使用Random。对于大多数的用途,选择的随机数生成器现在【首选】是ThreadLocalRandom 。它产生更高质量的随机数,而且速度非常快。在我的机器上,它比Random快3.6倍。对于fork连接池和并行流,请使用SplittableRandom。

  使用这些类库的第二个好处是,不必浪费时间为那些与工作不太相关的问题提供特别的解决方案。就像大多数程序猿一样,应该把实践花在应用程序上,而不是底层的细节上。

  使用标准类库的第三个好处是,它们的性能往往会随着时间的推移而不断提高,无需你做任何努力。因为许多人在使用它们,被当作工业标准在使用,所以,提供这些标准类库的组织有强烈的动机要使它们运行得更快。这些年来,许多Java平台类库已经被重新编写了,有时候是重复编写,从而导致性能上有了显著的提高。

  使用库的第四个优点是,它们往往会随着时间的推移而获得功能。如果某个类库缺少了某些东西,开发人员社区就会把这些缺点告示出来(the developer community will make it known),并且可能会在后续版本中添加缺少的功能。

  使用标准类库的最后一个好处是,可以使自己的代码融入主流,这样的代码更易容阅读、更容易维护、更容易被大多数的开发人员重用。

  既然有那么多的有点,使用标准类库机制而不选择专门的实现,这显然是符合逻辑的,然而还是有相当一部分的程序猿没有这样做。为什么呢?可能它们并不知道有这些类库机制的存在。每个主要版本的库中都添加了许多功能,并且可以随时了解这些新增内容 。每次Java平台有重要的发行版本时,会有一个网页说明新的特性。这些网页值得好好读一读[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.lang、java.util和java.io,以及它们的子包 。可以根据需要获取其他类库的知识。总结类库的技巧超出了本项的范围,这些技巧多年来一直在增长。

  有几个类库特别值得一提。集合框架和流类库(第45项-第58项)应该是没个程序猿的基本工具包的一部分,也包括java.util.concurrent中的并发实用工具。这个包即包含高级别的并发工具来简化多线程的编程任务,还包含低级别的并发类型,允许专家们自己编写更高级的并发抽象。第80和81项讨论了java.util.current的高级部分。

  有些情况下,一个类库工具并不能满足你的需要。你的需求越是特殊,这种情形就越有可能发生。虽然你的第一个念头应该是使用标准类库,但是,如果你在观察了它们在某些领域所提供的功能之后,确定它不能满足需要,那么【就】使用替代实现。任何有限的类库集提供的功能总是存在遗漏。如果你无法在Java平台库中找到所需要的内容,那么你的下一个选择应该是查看高质量的第三方库,例如Google优秀的开源Guava库[Guava]。如果你在任何适当的类库中都找不到所需要的功能,你可能只能自己实现,别无选择。

  总而言之,不要重新发明轮子。如果你需要做的事情看起来是十分常见的,有可能类库中已经有某个类完成了这样的工作。如果确实是这样,就使用它【前面的那个某个类】;如果你不知道是否存在这样的类,就去查一查。一般而言,类库的代码可能比你自己编写的代码更好一些,并且会随着时间的推移不断改进。这并不是在影射你作为一只程序猿的能力。从经济角度的分析表明:类库代码受到的关注远远超过大多数普通程序猿在同样的功能上所能给予的投入。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值