成都网站优化师,超市网站源码,湖南优化电商服务有限公司,著名的网站有哪些简介#xff1a; 《Effective Java》是一本经典的 Java 学习宝典#xff0c;值得每位 Java 开发者阅读。笔者将书中和平日工作较密切的知识点做了部分总结。 作者 | 宜秋 来源 | 阿里技术公众号
《Effective Java》是一本经典的 Java 学习宝典#xff0c;值得每位 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 {Testpublic 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 类ImmutableListImmutableSetImmuableMap和上文提到的函数模式集合实现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 绑定。若参数值无效则抛出特定的ClientExceptione.g. IllegalArgumentException。
2 谨慎设计方法签名 谨慎地选择方法的名称 执行某个动作的方法通常用动词或者动词短语命名createXXXupdateXXXremoveXXXconvertXXXgenerateXXX对于返回boolean值的方法一般以 is 开头isValidisLiveisEnabled 避免过长的参数列表目标是四个参数或者更少。 当参数过多时笔者会使用PairTriple或辅助类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 导致的异常处理可不在服务端主要的异常监测范围中。而 ServerExceptione.g. 数据库查询超时是由服务端自身原因导致的问题平时需要着重监测。
七 引用
Bloch, Joshua. 2018. Effective Java, 3rd Edition
原文链接 本文为阿里云原创内容未经允许不得转载。