Libraries
对 Java 来说,拥有大量的扩展库也许是最大的特点了。下面这些一小部分的扩展库对大部分人来说很适用的。
Missing Features
Java 标准库曾经作出过惊人的改进,但是现在来看,它仍然缺少一些关键的特性。
Apache Commons
[Apache Commons 项目][apachecommons] 拥有大量的有用的扩展库。
Commons Codec 对 Base64 和 16 进制字符串来说有很多有用的编/解码方法。不要再浪费时间重写这些东西了。
Commons Lang 有许多关于字符串的操作和创建,字符集和许多各种各样的实用的方法。
Commons IO 拥有所有你能想到的关于文件操作的方法。有
[FileUtils.copyDirectory][copydir],[FileUtils.writeStringToFile][writestring],[IOUtils.readLines][readlines] 和更多实用的方法。
Guava
[Guava][guava] 是谷歌优秀的对 Java 标准库缺少的特性进行补充的扩展库。虽然这很难提炼总结出我有多喜欢这个库,但是我会尽力的。
Cache 让你可以用很简单的方法,实现把网络访问,磁盘访问,缓存函数或者其他任何你想要缓存的内容,缓存到内存当中。你仅仅只需要实现 [CacheBuilder][cachebuilder] 类并且告诉 Guava 怎么样构建你的缓存,一切就搞定了!
Immutable 集合。它有许多如:[ImmutableMap][immutablemap],[ImmutableList][immutablelist],或者甚至 [ImmutableSortedMultiSet][immutablesorted] 等不可变集合可以使用,如果你喜欢用这种风格的话。
我也喜欢用 Guava 的方式来写一些可变的集合:
// Instead of
final Map<String, Widget> map = new HashMap<>();
// You can use
final Map<String, Widget> map = Maps.newHashMap();
它还有一些静态类如 [Lists][lists],[Maps][maps]和[Sets][sets] 等。使用起来它们显得更整洁,并且可读性更强。
如果你坚持使用 Java 6 或者 7 的话,你可以使用 [Collections2][collections2] 这个类,它有一些像 filter 和 transform 这样的方法。能够让你没有 Java 8 的 Stream 的支持也能写出流畅的代码。
Guava 也可以做一些很简单的事情,比如 Joiner 类可以用来用分隔符把字符串拼接起来,并且可以用忽略的方式[来处理打断程序][uninterrupt]的数据。
Gson
谷歌的 [Gson][gson] 库是一个简单快速的 JSON 解析库。可以这样用:
final Gson gson = new Gson();
final String json = gson.toJson(fooWidget);
final FooWidget newFooWidget = gson.fromJson(json, FooWidget.class);
这用起来真的很简单,很愉悦。[Gson 用户手册][gsonguide] 有很多的使用示例。
Java Tuples
Java 令我比较烦恼的问题之一 Java 标准库中没有内置对元组的支持。幸运的是,[Java tuples][javatuples] 项目解决了这个问题。
它使用用起来很简单,很棒:
Pair<String, Integer> func(String input) {
// something...
return Pair.with(stringResult, intResult);
}
Javaslang
[Javaslang][javaslang] 是一个函数式编程库,它被设计用来弥补本应该出现在 Java 8 中但缺失的一些特性。它有这样的一些特点:
- 一个全新函数式集合库
- 紧密集成的元组功能
- 模式匹配
- 通过不可变性保证线程安全
- 饥汉式和懒汉式的数据类型
- 通过 Option 实现了 null 的安全性
- 通过 Try 更好的实现异常处理
有一些 Java 库依赖于原始的 Java 集合类。它们通过以面向对象和被设计为可变的方式来保证和其他的类的兼容性。而 Javaslang 的集合的设计灵感来源于 Haskell, Clojure 和 Scala,是一个全新的飞跃。它们被设计为函数式风格并且遵循不可变性的设计风格。
像下面这样的代码就可以自动实现线程安全,并且不用 try-catch 语句处理异常:
// Success/Failure containing the result/exception
public static Try<User> getUser(int userId) {
return Try.of(() -> DB.findUser(userId))
.recover(x -> Match.of(x)
.whenType(RemoteException.class).then(e -> ...)
.whenType(SQLException.class).then(e -> ...));
}
// Thread-safe, reusable collections
public static List<String> sayByeBye() {
return List.of("bye, "bye", "collect", "mania")
.map(String::toUpperCase)
.intersperse(" ");
}
Joda-Time
[Joda-Time][joda] 是我用过的最简单的时间处理库。简单,直接,并且很容易测试。夫复何求?
因为 Java 8 已经有了自己的新的 [时间处理][java8datetime]库, 所以如果你还没有用 Java 8,你需要这一个库足矣。
Lombok
[Lombok][lombok] 是一个很有意思的库。它可以让你以注解的方式减少 Java 中糟糕的样板代码。
想为你的类的变量添加 setter 和 getter 方法吗?像这样:
public class Foo {
@Getter @Setter private int var;
}
现在你就可以这么用了:
final Foo foo = new Foo();
foo.setVar(5);
这还有[很多][lombokguide]例子。我在之前的产品中还没有用过 Lombok,但是现在我等不急了。
Play framework
好的替代品: [Jersey][jersey] 或者 [Spark][spark]
在 Java 实现 RESTful web services 有两大主要阵营:[JAX-RS][jaxrs] 和其他。
JAX-RS 是传统的实现方式。你可以用像 [Jersey][jersey] 这样的框架,以注解的方式来实现接口及其实现的结合。这样你就可以很容易的根据接口类来开发客户端。
[Play 框架][play] 基于 JVM 的 web services 实现和其他根本框架不同:它有一个路由文件,你写的类要和路由文件中的路由信息关联起来。Play 框架其实是一个[完整的 MVC 框架][playdoc],但是你可以很简单地仅仅使用它的 REST web services 部分的功能。
它同时支持 Java 和 Scala。虽然对重点支持的 Scala 稍有不足,但是对 Java 的支持还是很好用的。
如果你在 Python 中用过像 Flask 这样的微框架,你对 [Spark][spark] 肯定会很熟悉。它对 Java 8 的支持尤其的好。
SLF4J
有很多 Java 日志解决方案。我最喜欢的是 [SLF4J][slf4j],因为它拥有非常棒的可插拔性,同时能够和很多的日志框架想结合。有没有做过同时使用 java.util.logging,JCL,和 log4j 的奇葩项目?SLF4J 就是为你而生。
这[两页手册][slf4jmanual]足够你可以开始入门使用 SLF4J 了。
jOOQ
我不喜欢重量级的 ORM 框架,因为我喜欢 SQL。所以我写了很多 [JDBC 模板][jdbc],但是很难去维护它。[jOOQ][jooq] 是一个更好的解决方案。
它让你在 Java 中用类型安全的方式编写 SQL:
// Typesafely execute the SQL statement directly with jOOQ
Result<Record3<String, String, String>> result =
create.select(BOOK.TITLE, AUTHOR.FIRST_NAME, AUTHOR.LAST_NAME)
.from(BOOK)
.join(AUTHOR)
.on(BOOK.AUTHOR_ID.equal(AUTHOR.ID))
.where(BOOK.PUBLISHED_IN.equal(1948))
.fetch();
使用 jOOQ 和 [DAO][dao] 的模式让你的数据库访问变得轻而易举。
Testing
测试是软件的关键环节。下面这些软件包能够让你更容易地测试。
jUnit 4
好的替代品:[TestNG][testng].
[jUnit][junit] 就无需多言了。它是 Java 单元测试中的标准工具。
但是很可能你使用的 jUnit 并没有发挥它的全部潜力。jUnit 支持[参数化测试][junitparam],[规则化][junitrules]测试,[theories][junittheories] 可以随机测试特定代码,还有 [assumptions][junitassume],可以让你少写很多样板代码。
jMock
如果你完成了依赖注入,这是它的回报:可以 mock 出有副作用(比如和 REST 服务器交互)的代码,并且可以断言调用这段代码的行为。
[jMock][jmock] 是标准的 Java mock 工具。像这样使用:
public class FooWidgetTest {
private Mockery context = new Mockery();
@Test
public void basicTest() {
final FooWidgetDependency dep = context.mock(FooWidgetDependency.class);
context.checking(new Expectations() {{
oneOf(dep).call(with(any(String.class)));
atLeast(0).of(dep).optionalCall();
}});
final FooWidget foo = new FooWidget(dep);
Assert.assertTrue(foo.doThing());
context.assertIsSatisfied();
}
}
这段代码通过 jMock 建立了一个 FooWidgetDependency,然后添加你所期望结果的条件。我们期望 dep 的 call 方法会被以一个字符串为参数的形式调用,并且会被调用 0 次或者多次。
如果你想一遍又一遍地设置相同的依赖,你应该把它放到 [test fixture][junitfixture] 中,并且把assertIsSatisfied 放在以 @After 注解的 fixture 中。
AssertJ
你曾经用 jUnit 干过这个吗?
final List<String> result = some.testMethod();
assertEquals(4, result.size());
assertTrue(result.contains("some result"));
assertTrue(result.contains("some other result"));
assertFalse(result.contains("shouldn't be here"));
这是很恶心的样板代码。[AssertJ][assertj] 可以解决这个问题。你可以把相同的代码转换成这个样子:
assertThat(some.testMethod()).hasSize(4)
.contains("some result", "some other result")
.doesNotContain("shouldn't be here");
这样的流畅接口让你的测试更具有可读性。你还想咋地?