Java 是在世界各地最流行的编程语言之一, 但是看起来没人喜欢使用它。而 Java 事实上还算是一门不错的语言,随着 Java 8 最近的问世,我决定编制一个库,实践和工具的清单,汇集 Java 的一些最佳实践。
本文被放到了 Github 上。你可以随意地提交贡献,并加入自己的有关 Java 方面的建议和最佳实践。
风格
Javadoc
构建器模式
结构
依赖注入
避免空值
默认不可变更
避免大量的工具类
格式化
流
发布
依赖收敛
框架
Maven
持续集成
Maven 资源库
配置管理
库
jUnit 4
jMock
AssertJ
Apache Commons
Guava
Gson
Java Tuples
Joda-Time
Lombok
Play framework
SLF4J
jOOQ
Missing Features
Testing
工具
Chronon
IntelliJ IDEA
JRebel
Checker 框架
Eclipse 内存分析器
资源
书籍
播客
风格
通常,我们会以一种非常详细繁杂的企业级 JavaBean 的风格进行 Java 代码的编写。新的风格则更加清晰,正确,且看上去也更加的简单。
结构
作为程序员的我们要做的最简单的事情之一,就是传递数据。一般的方式就是定义一个 JavaBean:
public class DataHolder {
private String data;
public DataHolder() {
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return this.data;
}}
这有点麻烦,并且也有点浪费。尽管你的 IDE 也能自动的生成这样的代码,但那也是种浪费。所以,别这么做。
相反,我更愿意选择编写类 C 的结构体风格的类,类里面只容纳数据:
public class DataHolder {
public final String data;
public DataHolder(String data) {
this.data = data;
}}
这样就在代码行数上减少了一半。此外,这个类是不能被修改的,除非你对它进行了扩展,因此我们可以更加容易的理解它,因为我们明白它不可以被修改。
如果你要保存像 Map 或者 List 这样容易被修改的对象,就应该使用 ImmutableMap 和 ImmutableList,这一点会在不可变性质的那一节被讲到。
Builder 模式
如果你有一个相当复杂的对象想要去为其构建一个结构,可以考虑使用 Builder 模式。
你可以在对象中创建一个能帮助你构建出这个对象的子类。它使用了可变语句,但是一旦你调用了build,它就会提供给你一个不可变的对象。
想象一下我们要有一个更加复杂的 DataHolder。针对它的构建器看起来可能像是下面这样:
public class ComplicatedDataHolder {
public final String data;
public final int num;
// lots more fields and a constructor
public static class Builder {
private String data;
private int num;
public Builder data(String data) {
this.data = data;
return this;
}
public Builder num(int num) {
this.num = num;
return this;
}
public ComplicatedDataHolder build() {
return new ComplicatedDataHolder(data, num); // etc
}
}}
然后这样去使用它:
final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder()
.data("set this")
.num(523)
.build();
还有其它关于构建器的更好的例子 ,而这里提供给你浅尝辄止。这样做最终会得到许多的我们努力去避免的样板式代码,不过这也让你得到了不可变的对象和一个非常流畅的接口。
依赖注入
这是更偏向软件工程而不是 Java 的一节。编写可测试软件的最佳方式之一就是使用依赖注入(DI)。因为 Java 非常鼓励 OO 设计,为了创造出可测试的软件,你需要使用DI。
在 Java 中,一般使用 Spring 框架 的 DI 实现。它同时支持基于代码的装配和基于 XML 配置的装配。 如果你使用的是 XML 配置,因为其基于 XML 的配置, 不去过分使用 Spring 这一点很重要。XML 中绝对不能有任何逻辑或者控制结构,只能用来注入依赖。
使用 Spring 的好的选择就是 Google 和 Square 的 Dagger 库以及Google 的 Guice。他们不使用 Spring 的 XML 配置文件格式,而是将依赖逻辑放到注解和代码中。
避免空值
尽你所能避免空值。如果你可以返回一个空的集合,就不要返回一个空值。如果你要使用空值,就考虑使用 @Nullable 注解。IntelliJ IDEA 内置有对于 @Nullable 注解的支持。
如果你使用的是 Java 8,就可以利用其优秀的新的 Optional 类型。如果一个可能存在也可能不存在,那就像下面这样把它封装到一个 Optional 类中:
public class FooWidget {
private final String data;
private final Optional<Bar> bar;
public FooWidget(String data) {
this(data, Optional.empty());
}
public FooWidget(String data, Optional<Bar> bar) {
this.data = data;
this.bar = bar;
}
public Optional<Bar> getBar() {
return bar;
}}
这样现在就能很确定数据永远都不会是空值了, 不过 bar 可能存在也可能不存在。Optional 有一些诸如 isPresent 这样的方法,这使得其感觉跟只检查空值的做法小同大异。但是它能让你写出像下面这样的语句:
final Optional<FooWidget> fooWidget = maybeGetFooWidget();
final Baz baz = fooWidget.flatMap(FooWidget::getBar)
.flatMap(BarWidget::getBaz)
.orElse(defaultBaz);
这样就比链条时的 if 空值检查看起来好多了。使用 Optional 的唯一缺陷就是标准库并没有对 Optional 有很好的支持,因此针对空值的处理还是需要的。
默认不可被改变
除非你有一个好的理由要这样做,那么变量、类和集合都是不应该被修改的。
变量的引用可以用 final 来变成不可被修改的:
final FooWidget fooWidget;if (condition()) {
fooWidget = getWidget();} else {
try {
fooWidget = cachedFooWidget.get();
} catch (CachingException e) {
log.error("Couldn't get cached value", e);
throw e;
}}// fooWidget is guaranteed to be set here
现在你就可以确信 fooWidget 不会突然被重新赋值了。final 关键字一般同 if/else 块和 try/catch 块一起使用。当然,如果 fooWidget 不是不可被修改的,那你就可以很轻易了修改它了。
集合就应该无论何时都尽量使用 Guava 的 ImmutableMap,ImmutableList,或者 ImmutableSet 类。这些都拥有构建器,因此你可以动态地构建它们,并通过调用 build 方法来将它们标记为不可变。
类应该(通过 final)声明其属性域不可变和使用不可变的集合而变成不可变的。你也可以选择使得类自身为 final,那样它就不能被扩展和被改变了。
避免许多的工具类
在你发现自己添加了太多的方法到一个工具类中时要小心。
public class MiscUtil {
public static String frobnicateString(String base, int times) {
// ... etc
}
public static void throwIfCondition(boolean condition, String msg) {
// ... etc
}}
这些类一开始看起来很吸引人,因为它们里面包含的方法并不真的属于任何一块。所以你就以代码重用的名义将它们扔到了一块儿。
治病比生病更糟糕。将这些类放到原本属于它们的地方,要不如果你必须要有像这么一些方法的话,就考虑使用 Java 8 的接口上的默认方法。然后你就可以将公共方法统统扔到接口中去。而因为他们是接口,你就可以多次实现它们。
public interface Thrower {
default void throwIfCondition(boolean condition, String msg) {
// ...
}
default void throwAorB(Throwable a, Throwable b, boolean throwA) {
// ...
}}
然后每个有需要的类都可以简单的实现这个接口。
格式化
格式化比起大多数程序员所认为的更加不被重视。那么它是不是同你对于自己技术水平的在意目标一致,还有是不是能有助于其他人的对于代码的解读呢?当然是。但我们也不要浪费一整天加空格来使得 if 的括号能“匹配”。
如果你绝对需要一个代码格式手册,我强烈推荐 Google 的 Java 代码风格 指南。该指南的最佳部分就是编程实践这一节。绝对值得一读.
Javadoc
为你的用户所要面对的代码加注文档是很重要的。而这就意味着要使用示例和对于变量、方法和类的极值描述。
这样做的必然结果就是对于不需要加注文档的就不要去加注文档. 如果就一个参数代表的是什么你不想多费口舌,因为答案很明显,就不要为其加注文档。样板一样的文档比没有文档更糟糕,因为这对于会思考此处为何要加注的文档的用户而言这会是一种戏弄。
流
Java 8 有了一个不错的流和 lambda 语法。你可以像下面这样编写代码:
final List<String> filtered = list.stream()
.filter(s -> s.startsWith("s"))
.map(s -> s.toUpperCase());
而不是再像以前这样写:
final List<String> filtered = Lists.newArrayList();for (String str : list) {
if (str.startsWith("s") {
filtered.add(str.toUpperCase());
}}
这就让你能写出更加流畅的代码,更具可读性。
发布
发布 Java 通常有点棘手。如今有两种主要的 Java 发布方式 : 使用一套框架,或者根据灵活性的本地增量方案。
框架
因为发布 Java 并不容易,现有的框架可能会有所帮助。最好的两个就是 Dropwizard 和 Spring Boot。Play 框架 也可以被考虑也作为这些部署框架的其中之一。
它们全都试图降低让你的代码发布出去的门槛. 它们在你是名Java新手或者希望能快速运行起来时特别有帮助. 单个的JAR部署比复杂的WAR和EAR部署更简单.
不过,它们可能不怎么灵活,而且详单笨拙,因此如果你的项目不适合框架开发者为你的框架所做出选择,你就得自己集成一个更加手动的配置了。
文章转载自 开源中国社区[https://www.oschina.net]