如何更好的玩转Java

2023-01-10 10:07:51

原文地址:Better Java

Better Java

Java是目前最流行的编程语言之一,但并不是所有的人都能享受地使用它。其实,Java是一种非常棒的编程语言,由于最近Java 8的发布,我决定整理一个包括库、实践、和工具的列表来帮助大家更好的使用Java。

这篇文章也放在Github上,你可以随时贡献并添加你自己的Java秘籍与最佳实践。

风格

在传统风格中,Java代码总是被编写成冗长的JavaBean。然而新的风格看起来更加简洁、准确、而且容易理解。

结构体

对码农来说,编程中最常见的事情是传递包装数据。下面是传统的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;
    }
}

这种方式少了近一半的代码量。而且这个类是不可变的,除非你继承它进行扩展。由于具有不变性,因此在某些情况下可以放心的使用它。

如果你想保存像MapList一样容易修改的对象,你应该使用ImmutableMap或者ImmutableList,这些将在不变性的那一部分讨论。

Builder模式

如果你想构造一个比较复杂的对象,可以考虑使用Builder模式。

使用一个静态内部类来创建你的对象,内部类对象的状态是可变的,一旦你调用它的build方法,它将构造出一个你所需要的不可变对象。

想象一下我们有一个很复杂的DataHolder,里面定义了很多数据字段,那么Builder模式大概是这样的:

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();

更多的例子,通过这个小例子你应该知道Builder模式大概是什么样子了。它没有使用许多模板代码,而且它提供了不可变对象和非常良好的接口。

依赖注入

在软件工程领域,而不仅是在Java领域,使用依赖注入是编写可测试软件最好的方式之一。因为Java强烈鼓励使用面向对象的设计,为了开发可测试软件,你不得不使用依赖注入。

在Java中,通常的方式是使用Spring框架来完成依赖注入。它能够通过基于代码方式和基于XML配置文件方式来完成依赖注入。如果你使用基于XML配置文件的方式,记住不要过度使用Spring,正式因为它使用的基于XML配置文件的格式。在XML配置文件中绝对不应该有逻辑或者控制结构,它应该仅仅用来做依赖注入。

比较好的能够替代Spring的有Google和Square的Dagger库,或者Google的Guice库。它们不使用像Spring那样的XML配置文件,相反的它们把注入逻辑放在注解和代码中。

避免null值

尽量避免使用null值,不要返回一个null集合,而应该返回一个empty集合。如果你确实要使用null值,可以考虑加上@Nullable注解,IntelliJ IDEA已经内建了对@Nullable注解的支持。

如果你正在使用Java 8,你可以使用新提供的类Optional。如果一个值可能是null,可以将它包裹在一个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;
    }
}

现在清楚的知道data永远不会为null,但是bar或许是null。Optional有像isPresent这样的方法,可以用来检查是否为为null,或许感觉和原来的方式没什么不同。但是你可以这样写代码:

final Optional<FooWidget> fooWidget = maybeGetFooWidget();
final Baz baz = fooWidget.flatMap(FooWidget::getBar)
                         .flatMap(BarWidget::getBaz)
                         .orElse(defaultBaz);

这样比写一连串的判空处理代码更好,唯一的缺点就是标准库对Optional的支持并不是很好,因此对null值的处理任然是必要的。

不可变模式

除非你有一个好的方式来构造变量、类、和集合,否则他们应该是不可变的。

变量可以使用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提供的ImmutableMapImmutableList或者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) {
        // ...
    }
}

然后每一个需要它们的类只需要简单的实现这个接口就行了。

格式

大部分程序员认为格式没那么重要,代码的一致性能够帮助其他人更好的阅读吗?当然啦,但是别为匹配你的代码块而浪费一天时间去加空格。

如果你需要一份代码规范教程,我强烈推荐Google's Java Style Guide,这份指导中写的最好的部分是Programming Practices,绝对值得一读。

Javadoc

文档对你代码的阅读者来说是很重要的,这意味着你要给出使用示例,并且给出变量、方法以及类的清晰描述。

这样做的必然结果是不要给不需要的部分写文档。如果你对一个参数的含义没什么可说的,或者它本身是什么意思已经是显而易见的,那就不要写文档了。样版文档比没有文档更糟糕,这会让读你代码的人误以为那就是文档。

Streams

Java 8提供了很棒的stream和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程序并不太容易,所以使用框架来完成能帮不少忙。最好的2个框架是DropwizardSpring BootPlay framework也是众多框架里可以考虑的。

所有的这些框架降低了你部署代码的难度。如果你刚接触Java或者需要快速完成这些工作,那么它们是格外有帮助的。单个JAR包的部署比复杂的WAR包或者EAR包部署更加容易。

然而,这些框架有些地方还是不太灵活,如果你的项目不能符合框架开发者的选择,你必须手动做一些配置。

Maven

不错的替代品:Gradle

Maven是一个构建、打包、测试的标准工具。有一些不错的替代品,比如说Gradle,但是它们没有Maven那样的适应性。如果你是Maven新手,你应该从Maven示例开始。

我喜欢使用一个根POM来管理所有用到的外部依赖,就像这样。这个根POM仅仅有一个外部依赖,如果你的项目足够大,你将会需要更多,依据你的项目而定。你的根POM应该像其他的Java项目一样使用版本控制和发布的方式,有一个自己的项目。

如果你认为为每一个外部依赖的改变标记你的根POM过于繁琐,你可能会花费更多的时间来跟踪错误。

你的所有Maven项目都包含你的根POM和一些版本信息,按照这种方式,你能轻易获得你需要的相应版本的外部依赖和所有Maven插件。如果你需要外部依赖,你可以这样配置:

<dependencies>
    <dependency>
        <groupId>org.third.party</groupId>
        <artifactId>some-artifact</artifactId>
    </dependency>
</dependencies>

如果你需要内部依赖,应该由每个项目自己来管理,否则难以保持根POM版本号是正常的。

Dependency Convergence

Java最好的一面就是拥有大量的第三方库能够使用。基本上每一个API或者工具包都有一个Java SDK,可以很容易使用Maven引入。

有些Java包依赖于另一个包的特定版本,如果你加入太多jar包,你也许会发现一些版本冲突的问题,就像这样:

Foo library depends on Bar library v1.0
Widget library depends on Bar library v0.9

那么你应该获取哪一个版本呢

  • 作者:夕辰
  • 原文链接:https://blog.csdn.net/houjian914/article/details/46315877
    更新时间:2023-01-10 10:07:51