本文是软件构造系列的第二篇文章。其余文章在:
https://ruanx.net/tag/software-construction/

  最近做软件构造实验 Lab3,用了 Guava 库,大大提高了编程效率。写篇博客记录一下。

  首先是 Guava 库的 Github repo:

google/guava
Google core libraries for Java. Contribute to google/guava development by creating an account on GitHub.

引入 Guava & Travis CI

  本次试验是用 Maven 来管理依赖的。只需要在 pom.xml 里面写上:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>Lab3</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>29.0-jre</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>
    </dependencies>

    <build>
        <sourceDirectory>src</sourceDirectory>
        <testSourceDirectory>test</testSourceDirectory>

        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <encoding>utf-8</encoding>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>

    </build>

    <properties>
        <project.build.sourceEncoding>
            UTF-8
        </project.build.sourceEncoding>
    </properties>
</project>

  实验要求把所有的依赖库复制到 lab ,指令如下:

mvn dependency:copy-dependencies
▲ 复制依赖库

  于是 Maven 会把依赖库复制到 target/dependency  目录,手动把那些 jar 包移动到 lib 目录就行了。最后,我们给项目加上 Travis CI 来构建、测试项目:

language: java

jdk:
  - openjdk8

  于是就有自动构建、自动测试了:

▲ Travis CI 自动构建和测试

null 相关

  Google 在统计自己公司人员的 Java 代码之后发现,在数据结构中允许 null 值的存在,带来的坏处远多于好处。例如一个 Map,如果 get(key) 方法返回的是 null ,那么客户端无法判断是“找不到这个key”,还是“这个key对应的值是 null”。于是 Guava 的数据结构是不允许 null 的。官方文档说:

Using and avoiding null: null can be ambiguous, can cause confusing errors, and is sometimes just plain unpleasant. Many Guava utilities reject and fail fast on nulls, rather than accepting them blindly.

  为了应对必须要用 null 的情形,Guava 提供了 Optional 类。这个类相当于一个容器,可以存放一个值或者表明这个容器里面没有值(缺席)。来看两个例子:

Optional<Integer> possible = Optional.of(5);
possible.isPresent(); // returns true
possible.get();       // returns 5

Integer emp = null;
Optional<Integer> nothing = Optional.fromNullable(emp);
nothing.isPresent();	// returns false
nothing.get();          // throw IllegalStateException

  对于 Guave 数据结构,试图在键或值里面使用 null,都会立刻抛出 NullPointerException.

不可变数据结构

  Guava 提供了一套不可变的数据结构,方便我们防止表达泄漏。例如 ImmutableList 提供了一个不可变列表:

List<String> names = ImmutableList.of("hello", "world", "blue");

  另外,可以通过  ImmutableList.copyOf 获取另一个列表的拷贝。对于集合,可以采用 ImmutableSet ;对于键值对,可以采用 ImmutableMap. 工厂方法使用方式如下:

Map<String, Integer> score = 
    ImmutableMap.of("ruan", 100, "Alice", 200, "Bob", 300);
score.get("Alice");    // returns 200

特殊数据结构

  Guava 提供了几种特殊的数据结构:

  • Multiset 允许一个值出现多次
  • Multimap 允许一个键出现多次
  • BiMap 键值对双向绑定,键-值的对应关系是一个双射
  • Table 二维表,由行、列映射到值

  在这个实验中,能用 Immutable 数据结构的地方全都用了不可变型;控制台程序中,uuid 与各种实例的绑定采用了 BiMap. 

比较器

  Guava 极大地方便了我们自定义排序比较器。具体教程可以参考官方文档。配合 stream 以及 lambda 表达式使用,可以大幅度精简代码。例如以下代码可以把 plans 构成的 List 排序:

plans.stream()
     .sorted(Ordering.natural()
                     .onResultOf(plans::getStartTime()))
     .collect(Collectors.toList());
▲ 采用 Ordering 提供排序的比较器

总结

  Guava 不仅提供了大量易用、高效的 API,其设计模式更值得我们借鉴。我们在编程中也应当尽量避免使用 null,以免造成不必要的麻烦;对于各种方法的封装要适度,既要方便使用,又要清晰明了。写出高度可复用的代码是很困难的,Guava 在这方面提供了一个值得阅读的先例。