在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
开源软件名称:cxxr/better-java开源软件地址:https://github.com/cxxr/better-java开源编程语言:开源软件介绍:Note: I'm working on version 2 of this guide and I need your help! Please use this form to give me feedback on what you think should go in the next version. Thanks! Better JavaJava is one of the most popular programming languages around, but no one seems to enjoy using it. Well, Java is actually an alright programming language, and since Java 8 came out recently, I decided to compile a list of libraries, practices, and tools to make using Java better. "Better" is subjective, so I would recommend taking the parts that speak to you and use them, rather than trying to use all of them at once. Feel free to submit pull requests suggesting additions. This article was originally posted on my blog. Read this in other languages: English, 简体中文 Table Of ContentsStyleTraditionally, Java was programmed in a very verbose enterprise JavaBean style. The new style is much cleaner, more correct, and easier on the eyes. StructsOne of the simplest things we as programmers do is pass around data. The traditional way to do this is to define a JavaBean: public class DataHolder {
private String data;
public DataHolder() {
}
public void setData(String data) {
this.data = data;
}
public String getData() {
return this.data;
}
} This is verbose and wasteful. Even if your IDE automatically generated this code, it's a waste. So, don't do this. Instead, I prefer the C struct style of writing classes that merely hold data: public class DataHolder {
public final String data;
public DataHolder(String data) {
this.data = data;
}
} This is a reduction in number of lines of code by a half. Further, this class is immutable unless you extend it, so we can reason about it easier as we know that it can't be changed. If you're storing objects like Map or List that can be modified easily, you should instead use ImmutableMap or ImmutableList, which is discussed in the section about immutability. The Builder PatternIf you have a rather complicated object that you want to build a struct for, consider the Builder pattern. You make a static inner class which will construct your object. It uses mutable state, but as soon as you call build, it will emit an immutable object. Imagine we had a more complicated DataHolder. The builder for it might look like: 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
}
}
} Then to use it: final ComplicatedDataHolder cdh = new ComplicatedDataHolder.Builder()
.data("set this")
.num(523)
.build(); There are better examples of Builders elsewhere but this should give you a taste for what it's like. This ends up with a lot of the boilerplate we were trying to avoid, but it gets you immutable objects and a very fluent interface. Instead of creating builder objects by hand, consider using one of the many libraries which can help you generate builders. Immutable Object GenerationIf you create many immutable objects by hand, consider using the annotation processor to generate them from interfaces automatically. This minimizes boilerplate code, reduces probability of bugs and promotes immutability. See this presentation for an interesting discussion of some of the problems with normal Java coding patterns. Some great code generation libraries are immutables, Google's auto-value and Lombok. ExceptionsChecked exceptions should be used with caution, if at all. They force your users to add many try/catch blocks and wrap your exceptions in their own. Better is to make your exceptions extend RuntimeException instead. This allows your users to handle your exceptions in the way they would like, rather than forcing them to handle/declare that it throws every time, which pollutes the code. One nifty trick is to put RuntimeExceptions in your method's throws declaration. This has no effect on the compiler, but will inform your users via documentation that these exceptions can be thrown. Dependency injectionThis is more of a software engineering section than a Java section, but one of the best ways to write testable software is to use dependency injection (DI). Because Java strongly encourages OO design, to make testable software, you need to use DI. In Java, this is typically done with the Spring Framework. It has a either code-based wiring or XML configuration-based wiring. If you use the XML configuration, it's important that you don't overuse Spring because of its XML-based configuration format. There should be absolutely no logic or control structures in XML. It should only inject dependencies. Good alternatives to using Spring is Google and Square's Dagger library or Google's Guice. They don't use Spring's XML configuration file format, and instead they put the injection logic in annotations and in code. Avoid NullsTry to avoid using nulls when you can. Do not return null collections when you should have instead returned an empty collection. If you're going to use null, consider the @Nullable annotation. IntelliJ IDEA has built-in support for the @Nullable annotation. Read more about why not to use nulls in The worst mistake of computer science. If you're using Java 8, you can use the excellent new Optional type. If a value may or may not be present, wrap it in an Optional class like this: 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;
}
} So now it's clear that data will never be null, but bar may or may not be present. Optional has methods like isPresent, which may make it feel like not a lot is different from just checking null. But it allows you to write statements like: final Optional<FooWidget> fooWidget = maybeGetFooWidget();
final Baz baz = fooWidget.flatMap(FooWidget::getBar)
.flatMap(BarWidget::getBaz)
.orElse(defaultBaz); Which is much better than chained if null checks. The only downside of using Optional is that the standard library doesn't have good Optional support, so dealing with nulls is still required there. Immutable-by-defaultUnless you have a good reason to make them otherwise, variables, classes, and collections should be immutable. Variables can be made immutable with 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 Now you can be sure that fooWidget won't be accidentally reassigned. The final keyword works with if/else blocks and with try/catch blocks. Of course, if the fooWidget itself isn't immutable you could easily mutate it. Collections should, whenever possible, use the Guava ImmutableMap, ImmutableList, or ImmutableSet classes. These have builders so that you can build them up dynamically and then mark them immutable by calling the build method. Classes should be made immutable by declaring fields immutable (via final) and by using immutable collections. Optionally, you can make the class itself final so that it can't be extended and made mutable. Avoid lots of Util classesBe careful if you find yourself adding a lot of methods to a Util class. public class MiscUtil {
public static String frobnicateString(String base, int times) {
// ... etc
}
public static void throwIfCondition(boolean condition, String msg) {
// ... etc
}
} These classes, at first, seem attractive because the methods that go in them don't really belong in any one place. So you throw them all in here in the name of code reuse. The cure is worse than the disease. Put these classes where they belong and refactor aggressively. Don't name classes, packages, or libraries anything too generic, such as "MiscUtils" or "ExtrasLibrary". This encourages dumping unrelated code there. FormattingFormatting is so much less important than most programmers make it out to be. Does consistency show that you care about your craft and does it help others read? Absolutely. But let's not waste a day adding spaces to if blocks so that it "matches". If you absolutely need a code formatting guide, I highly recommend Google's Java Style guide. The best part of that guide is the Programming Practices section. Definitely worth a read. JavadocDocumenting your user facing code is important. And this means using examples and using sensible descriptions of variables, methods, and classes. The corollary of this is to not document what doesn't need documenting. If you don't have anything to say about what an argument is, or if it's obvious, don't document it. Boilerplate documentation is worse than no documentation at all, as it tricks your users into thinking that there is documentation. StreamsJava 8 has a nice stream and lambda syntax. You could write code like this: final List<String> filtered = list.stream()
.filter(s -> s.startsWith("s"))
.map(s -> s.toUpperCase())
.collect(Collectors.toList()); Instead of this: final List<String> filtered = new ArrayList<>();
for (String str : list) {
if (str.startsWith("s") {
filtered.add(str.toUpperCase());
}
} This allows you to write more fluent code, which is more readable. DeployingDeploying Java properly can be a bit tricky. There are two main ways to deploy Java nowadays: use a framework or use a home grown solution that is more flexible. FrameworksBecause deploying Java isn't easy, frameworks have been made which can help. Two of the best are Dropwizard and Spring Boot. The Play framework can also be considered one of these deployment frameworks as well. All of them try to lower the barrier to getting your code out the door. They're especially helpful if you're new to Java or if you need to get things done fast. Single JAR deployments are just easier than complicated WAR or EAR deployments. However, they can be somewhat inflexible and are rather opinionated, so if your project doesn't fit with the choices the developers of your framework made, you'll have to migrate to a more hand-rolled configuration. MavenGood alternative: Gradle. Maven is still the standard tool to build, package, and run your tests. There are alternatives, like Gradle, but they don't have the same adoption that Maven has. If you're new to Maven, you should start with Maven by Example. I like to have a root POM with all of the external dependencies you want to use. It will look something like this. This root POM has only one external dependency, but if your product is big enough, you'll have dozens. Your root POM should be a project on its own: in version control and released like any other Java project. If you think that tagging your root POM for every external dependency change is too much, you haven't wasted a week tracking down cross project dependency errors. All of your Maven projects will include your root POM and all of its version information. This way, you get your company's selected version of each external dependency, and all of the correct Maven plugins. If you need to pull in external dependencies, it works just like this: <dependencies>
<dependency>
<groupId>org.third.party</groupId>
<artifactId>some-artifact</artifactId>
</dependency>
</dependencies> If you want internal dependencies, that should be managed by each individual project's section. Otherwise it would be difficult to keep the root POM version number sane. Dependency ConvergenceOne of the best parts about Java is the massive amount of third party libraries which do everything. Essentially every API or toolkit has a Java SDK and it's easy to pull it in with Maven. And those Java libraries themselves depend on specific versions of other libraries. If you pull in enough libraries, you'll get version conflicts, that is, something like this:
Which version will get pulled into your project? With the Maven dependency convergence plugin, the build will error if your dependencies don't use the same version. Then, you have two options for solving the conflict:
The choice of which to choose depends on your situation: if you want to track one project's version, then exclude makes sense. On the other hand, if you want to be explicit about it, you can pick a version, although you'll need to update it when you update the other dependencies. Continuous IntegrationObviously you need some kind of continuous integration server which is going to continuously build your SNAPSHOT versions and tag builds based on git tags. Jenkins and Travis-CI are natural choices. Code coverage is useful, and Cobertura has a good Maven plugin and CI support. There are other code coverage tools for Java, but I've used Cobertura. Maven repositoryYou need a place to put your JARs, WARs, and EARs that you make, so you'll need a repository. Common choices are Artifactory and Nexus. Both work, and have their own pros and cons. You should have your own Artifactory/Nexus installation and mirror your dependencies onto it. This will stop your build from breaking because some upstream Maven repository went down. Configuration managementSo now you've got your code compiled, your repository set up, and you need to get your code out in your development environment and eventually push it to production. Don't skimp here, because automating this will pay dividends for a long time. Chef, Puppet, and Ansible are typical choices. I've written an alternative called Squadron, which I, of course, think you should check out because it's easier to get right than the alternatives. Regardless of what tool you choose, don't forget to automate your deployments. LibrariesProbably the best feature about Java is the extensive amount of libraries it has. This is a small collection of libraries that are likely to be applicable to the largest group of people. Missing FeaturesJava's standard library, once an amazing step forward, now looks like it's missing several key features. Apache CommonsThe Apache Commons project has a bunch of useful libraries. Commons Codec has many useful encoding/decoding methods for Base64 and hex strings. Don't waste your time rewriting those. Commons Lang is the go-to library for String manipulation and creation, character sets, and a bunch of miscellaneous utility methods. Commons IO has all the File related methods you could ever want. It has FileUtils.copyDirectory, FileUtils.writeStringToFile, IOUtils.readLines and much more. GuavaGuava is Google's excellent here's-what-Java-is-missing library. It's almost hard to distill everything that I like about this library, but I'm going to try. Cache is a simple way to get an in-memory cache that can be used to cache network access, disk access, memoize functions, or anything really. Just implement a CacheBuilder which tells Guava how to build your cache and you're all set! Immutable collections. There's a bunch of these: ImmutableMap, ImmutableList, or even ImmutableSortedMultiSet if that's your style. I also like writing mutable collections the Guava way: // Instead of
final Map<String, Widget> map = new HashMap<>();
// You can use
final Map<String, Widget> map = Maps.newHashMap(); There are static classes for Lists, Maps, Sets and more. They're cleaner and easier to read. If you're stuck with Java 6 or 7, you can use the Collections2 class, which has methods like filter and transform. They allow you to write fluent code without Java 8's stream support. Guava has simple things too, like a Joiner that joins strings on separators and a class to handle interrupts by ignoring them. GsonGoogle's Gson library is a simple and fast JSON parsing library. It works like this: final Gson gson = new Gson();
final String json = gson.toJson(fooWidget);
final FooWidget newFooWidget = gson.fromJson(json, FooWidget.class); It's really easy and a pleasure to work with. The Gson user guide has many more examples. Java TuplesOne of my on going annoyances with Java is that it doesn't have tuples built into the standard library. Luckily, the Java tuples project fixes that. It's simple to use and works great: Pair<String, Integer> func(String input) {
// something...
return Pair.with(stringResult, intResult);
} JavaslangJavaslang is a functional library, designed to add missing features that should have been part of Java 8. Some of these features are
There are several Java libraries which depend on the original Java collections. These are restricted to stay compatible to classes which were created with an object-oriented focus and designed to be mutable. The Javaslang collections for Java are a completely new take, inspired by Haskell, Clojure and Scala. They are created with a functional focus and follow an immutable design. Code like this is automatically thread safe and try-catch free: // 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-TimeJoda-Time is easily the best time library I've ever used. Simple, straightforward, easy to test. What else can you ask for? You only need this if you're not yet on Java 8, as that has its own new time library that doesn't suck. LombokLombok is an interesting library. Through annotations, it allows you to reduce the boilerplate that Java suffers from so badly. Want setters and getters for your class variables? Simple: public class Foo {
@Getter @Setter private int var;
} Now you can do this: final Foo foo = new Foo();
foo.setVar(5); And there's so much more. I haven't used Lombok in production yet, but I can't wait to. |