乐趣区

关于java:Effective-Java-在工作中的应用总结

简介:《Effective Java》是一本经典的 Java 学习宝典,值得每位 Java 开发者浏览。笔者将书中和素日工作较亲密的知识点做了局部总结。

作者 | 宜秋
起源 | 阿里技术公众号

《Effective Java》是一本经典的 Java 学习宝典,值得每位 Java 开发者浏览。笔者将书中和素日工作较亲密的知识点做了局部总结。

一 创立和销毁对象篇

1 若有多个结构器参数时,优先思考结构器

当类结构蕴含多个参数时,同学们会抉择 JavaBeans 模式。在这种模式下,能够调用一个无参结构器来创建对象,而后调用 setter 办法来设置必要和可选的参数。目前较受欢迎的办法之一如在类上退出 Lombok 提供的 @Data 注解,来主动生成 getter/setter、equals 等办法。然而 JavaBeans 模式无奈将类做成不可变(immutable,详见“使可变形最小化”章节)。这就须要开发者本人掌控值的更新状况,确保线程平安等。

举荐:Builder 模式

Builder 模式通过 builder 对象上,调用相似 setter 的办法,设置相干的参数(相似 Proto Buffers)。最初,通过调用 build 办法来生成不可变的对象(immutable object)。应用 Builder 模式的办法之一包含在类上退出 Lombok 提供的 @Builder 注解。

利用:API Request & Response

在微服务架构中,服务的申请(request)和响应(response)往往蕴含较多参数。在解决申请的过程中,笔者也经常会放心误操作批改了申请的内容。所以,笔者偏向应用 Builder 模式。

咱们可应用 Builder 模式来构建该类型对象。在构建过程中,若须要引入额定逻辑(e.g. if-else),可先返回 Builder 对象,最初再调用 build 办法。

import lombok.Builder;

/** 申请类 */
@Builder
public class SampleRequest {
    private String paramOne;
    private int paramTwo;
    private boolean paramThree;
}

/** 响应类 */
@Builder
public class SampleResponse {private boolean success;}

/** 服务接口 */
public interface SampleFacade {Result< SampleResponse> rpcOne(RequestParam< SampleRequest>);
}

/** 调用 */
public void testRpcOne() {
    SampleRequest request =
          SampleRequest.builder().paramOne("one").paramTwo(2).paramThree(true).build();
    Result< SampleResponse> response = sampleFacade.rpcOne(request);
}

2 通过公有结构器强化不可实例化的能力

有些类,例如工具类(utility class),只蕴含动态字段和静态方法。这些类应尽量确保不被实例化,避免用户误用。

举荐:私有化类结构器

为了避免误导用户,认为该类是专门为了继承而设计的,咱们能够将结构器私有化。

public class SampleUtility {public static String getXXX() {return "test";}  

    /** 私有化结构器 */
    private SampleUtility() {}
}

/** 间接调用办法 */
public static void main(String[] args) {System.out.println(SampleUtility.getXXX());
}

二 类和接口篇

1 最小化类和成员的可拜访性

尽可能地使每个类或者成员不被外界拜访。

举荐:有的时候,为了测试,咱们不得不将某些公有的(private)类、接口或者成员变成包级公有的(package-private)。这里,笔者举荐大家应用 Guava 提供的 @VisiableForTesting 注解,来提醒这是为了测试而使可拜访级别变为包级公有,放宽了限度。

import com.google.common.annotations.VisibleForTesting;

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
String getXXX() {return "test";}

此外,也有小伙伴举荐 PowerMock 单元测试框架。PowerMock 是 Mockito 的加强版,能够实现实现对 private/static/final 办法的 Mock(模仿)。通过退出 @PrepareForTest 注解来实现。

public class Utility {private static boolean isGreaterThan(int a, int b) {return a > b;}

    private Utility() {}
}

/** 测试类 */
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@RunWith(PowerMockRunner.class)
@PrepareForTest({Utility.class})
public class UtilityTest {

    @Test
    public void test_privateIsGreaterThan_success() throws Exception {
        /** 测试公有的 isGreaterThan 办法 */
        boolean result = Whitebox.invokeMethod(Utility.class, "isGreaterThan", 3, 2);

        Assertions.assertTrue(result);
    }
}

2 使可变形最小化

不可变类(immutable class)是指类对应的实例被创立后,就无奈扭转其成员变量值。即实例中蕴含的所有信息都必须在创立该实例的时候提供,并在对象的生命周期内固定不变。

不可变类个别采纳函数(functional)模式,即对应的办法返回一个函数的后果,函数对操作数进行运算但并不批改它。与之绝对应的更常见的是过程的(procedure)或者命令式的(imperative)做法。应用这些办法时,将一个过程作用在它们的操作数上,会导致它的状态产生扭转。

如在“若有多个结构器参数时,优先思考结构器”一节中提到,不可变对象比较简单,线程平安,只有一种状态。应用该类的开发者无需再做额定的工作来保护束缚关系。另外,可变的对象能够有任意简单的状态。若 mutator 办法(e.g. update)无具体的形容,开发者须要自行浏览办法内容。笔者常常会破费较多工夫弄清楚在某办法内,可变对象的哪些字段被更改,办法完结后会不会影响后续的对象操作。笔者举荐传入不可变对象,基于此用更新的参数创立新的不可变对象返回。尽管会创立更多的对象,然而保障了不可变形,以及更可读性。

举荐:Guava Collection 之 Immutable 类

笔者在日常开发中偏向将 Immutable 类(ImmutableList,ImmutableSet,ImmuableMap)和上文提到的函数模式汇合,实现 mutator 类办法。

import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;

/** 举荐 */
private static final ImmutableMap< String, Integer> SAMPLE_MAP =
    ImmutableMap.of("One", 1, "Two", 2);

/** 举荐:确保原 input 列表不会变动 */
public ImmutableList< TestObj> updateXXX(ImmutableList< TestObj> input) {return input.stream()
            .map(obj -> obj.setXXX(true))
            .collect(toImmutableList());
}

/** 不举荐:扭转 input 的信息 */
public void filterXXX(List< TestObj> input) {input.forEach(obj -> obj.setXXX(true));
}

三 泛型篇

1 列表优先于数组

数组是协变的(covariant),即 Sub 为 Super 的子类型,那么数组类型 Sub[] 就是 Super[] 的子类型;数组是具体化的,在运行时才晓得并查看它们的元素类型束缚。而泛型是不可变的和可擦除的(即编译时强化它们的类型信息,并在运行时抛弃)。
须要警觉 public static final 数组的呈现。很有可能是个安全漏洞!

四 办法篇

1 校验参数的有效性

若传递有效的参数值给办法,这个办法在执行简单、耗时逻辑之前先对参数进行了校验(validation),便很快就会失败,并且可分明地抛出适当的异样。若没有校验它的参数,就可能会在后续产生各种奇怪的异样,有时难以排查定位起因。

笔者认为,微服务提供的 API request 也应沿用这一思维。即在 API 申请被服务解决之前,先进行参数校验。每个 request 应与对应的 request validator 绑定。若参数值有效,则抛出特定的 ClientException(e.g. IllegalArgumentException)。

2 审慎设计办法签名

  • 审慎地抉择办法的名称:

执行某个动作的办法通常用动词或者动词短语命名:createXXX,updateXXX,removeXXX,convertXXX,generateXXX
对于返回 boolean 值的办法,个别以 is 结尾:isValid,isLive,isEnabled

  • 防止过长的参数列表:指标是四个参数,或者更少。

当参数过多时,笔者会应用 Pair,Triple 或辅助类(e.g. 动态成员类)

public class SampleListener {public ConsumeConcurrentlyStatus consumeMessage(String input) {SampleResult result = generateResult(input);
        ...
    } 

    private static SampleResult generateResult(String input) {...}

    /** 辅助类 */
    private static class SampleResult {
        private boolean success;
        private List< String> xxxList;
        private int count;
    }
}

3 返回零长度的数组或者汇合,而不是 null

若一个办法返回 null 而不是零长度的数组或者汇合,开发者须要退出 != null 的查看,有时容易遗记出错,报 NullpointerException。

说到此,笔者想额定提一下 Optional。网络上有很多对于 Optional 和 null 的应用探讨。Optional 容许调用者持续一系列晦涩的办法调用(e.g. stream.getFirst().orElseThrow(() -> new MyFancyException()))。以下为笔者整顿的观点。

/** 举荐:提醒返回值可能为空。*/
public Optional< Foo> findFoo(String id);

/**
  * 中立:稍显轻便
  * 可思考 doSomething("bar", null);
  * 或者重载 doSomething("bar"); 和 doSomething("bar", "baz");
  **/
public Foo doSomething(String id, Optional< Bar> barOptional);

/** 
  * 不举荐:违反 Optional 设计的目标。* 当 Optional 值缺省时,个别有 3 种解决办法:1)提供代替的值;2)调用办法提供代替的值;3)抛出异样
  * 这些解决办法能够在字段初始或赋值的时候解决。**/
public class Book {
    private List< Pages> pages;
    private Optional< Index> index;
}

/** 
  * 不举荐:违反 Optional 设计的目标。* 若为缺省值,可间接不放入列表中。**/
List< Optional< Foo>>

五 通用程序设计篇

1 如果须要准确的答案,请防止应用 float 和 double

float 和 double 类型次要用于迷信工程计算。它们执行二进制浮点运算,为了在数值范畴上提供较为精准的疾速近似计算。然而,它们并不能提供齐全准确的后果,尤其不适宜用于货币计算。float 或者 double 准确地示意 0.1 是不可行的。

若需零碎来记录十进制小数点,可应用 BigDecimal。

2 根本类型优先于装箱根本类型

根本类型(primitive)例如 int、double、long 和 boolean。每个根本类型都有一个对应的援用类型,称作装箱根本类型(boxed primitive),对应为 Integer、Double、Long 和 Boolean。如书中提到,它们的区别如下:

/** 举荐 */
public int sum(int a, int b) {return a + b;}

/** 不举荐:不必要的装箱 */
public Integer sum(Integer a, Integer b) {return a + b;}

若无非凡的应用场景,举荐总是应用根本类型。若不得不应用装箱根本类型,留神 == 操作和 NullPointerException 异样。装箱根本类型的应用场景:

  • 作为汇合中的元素(e.g. Set< Long>)
  • 参数化类型(e.g. ThreadLocal< Long>)
  • 反射的办法调用

六 异样

1 每个办法抛出的异样都要有文档

始终要独自地申明受检的异样,并且利用 Javadoc 的 @throws 标记,精确地记录下抛出每个异样的条件。
在日常工作中,笔者调用其余组的 API 时,有时会发现一些意料之外的异样。良好的文档记录,能够帮忙 API 调用者更好得解决相干的异样。文档记录可包含:异样的类型,异样的 error code,和形容。

2 其余

一些公司将 API 产生的异样分成 ClientException 和 ServerException。个别 ClientException (e.g. 有效的服务 request) 是由调用方非常规调用 API 导致的异样解决,可不在服务端次要的异样监测范畴中。而 ServerException(e.g. 数据库查问超时)是由服务端本身起因导致的问题,平时须要着重监测。

七 援用

Bloch, Joshua. 2018. Effective Java, 3rd Edition

原文链接
本文为阿里云原创内容,未经容许不得转载。

退出移动版