Effective Java 在工作中的应用总结

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

image.png

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

《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 类(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。如书中提到,它们的区别如下:

image.png

/** 推荐 */
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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512367.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

harfbuzz安装位置 linux_最新Ubuntu 20.04 LTS已发布,在Win10中该如何进行安装和使用?...

最近Ubuntu发布最新的20.04 LTS长期支持版本&#xff0c;官方提供为期5年的更新升级、安全修复等服务。国内的优麒麟团队也同步放出了优麒麟20.04 LTS&#xff0c;加入了全新的UKUI 3.0桌面环境。那么如何在微软的Windows系统中安装体验最新的Ubuntu系统呢&#xff1f;实际上相…

如何查看华为服务器配置信息,查看服务器网络配置信息

查看服务器网络配置信息 内容精选换一换ECS的网卡绑定虚拟IP地址后&#xff0c;该虚拟IP地址无法ping通。以下排查思路根据原因的出现概率进行排序&#xff0c;建议您从高频率原因往低频率原因排查&#xff0c;从而帮助您快速找到问题的原因。如果解决完某个可能原因仍未解决问…

通过Kubernetes监控探索应用架构,发现预期外的流量

简介&#xff1a; Kubernetes 监控立足于应用监控之下的 Kubernetes 容器界面和底层操作系统&#xff0c;是 Kubernetes 集群软件栈端到端可观测性的一体化解决方案&#xff0c;在 Kubernetes 监控中可以同时看到关联的所有层的观测数据。我们希望通过 Kubernetes 监控的一系列…

追踪 Kubernetes 中的网络流量

作者 | Addo Zhang来源 | 云原生指北译者注&#xff1a;这篇文章很全面的罗列出了 Kubernetes 中涉及的网络知识&#xff0c;从 Linux 内核的网络内容&#xff0c;到容器、Kubernetes&#xff0c;一一进行了详细的说明。文章篇幅有点长&#xff0c;不得不说&#xff0c;网络是很…

Go 语言网络库 getty 的那些事

简介&#xff1a; Getty 维护团队不追求无意义的 benchmark 数据&#xff0c;不做无意义的炫技式优化&#xff0c;只根据生产环境需求来进行自身改进。只要维护团队在&#xff0c;Getty 稳定性和性能定会越来越优秀。 个人从事互联网基础架构系统研发十年余&#xff0c;包括我…

std中稳定排序算法_源代码库已开放 | 哈工大硕士生用 Python 实现了 11 种经典数据降维算法...

转自&#xff1a;AI开发者网上关于各种降维算法的资料参差不齐&#xff0c;同时大部分不提供源代码。这里有个 GitHub 项目整理了使用 Python 实现了 11 种经典的数据抽取(数据降维)算法&#xff0c;包括&#xff1a;PCA、LDA、MDS、LLE、TSNE 等&#xff0c;并附有相关资料、展…

曲师大教务系统服务器,曲师大教务处信息门户入口地址

为了规范财务行为&#xff0c;加强财务管理&#xff0c;提高代管经费使用效益&#xff0c;提高项目建设质量&#xff0c;根据上级和学校有关财务规定&#xff0c;结合我校实际情况&#xff0c;特制定本办法。一、教务处代管的项目经费品牌特色专业建设经费、精品课程建设经费、…

云网管—云上构建网络自动化体系

简介&#xff1a; 云网管是基于阿里云网络多年技术和经验沉淀打造的云上智能网络管理运维平台&#xff0c;提供企业网络全生命周期管理运维的能力&#xff0c;让部署更快捷、运维更高效、网络更透明。 1.背景 云网管是基于阿里云网络多年技术和经验沉淀打造的云上智能网络管理…

【C++练级之路】【Lv.5】动态内存管理(都2023年了,不会有人还不知道new吧?)

目录 一、C/C内存分布二、new和delete的使用方式2.1 C语言内存管理2.2 C内存管理2.2.1 new和delete操作内置类型2.2.2 new和delete操作自定义类型 三、new和delete的底层原理3.1 operator new与operator delete函数3.2 原理总结3.2.1 内置类型3.2.2 自定义类型 四、定位new表达…

开工啦~Spring 完美导入 IDEA

作者 | 阿Q来源 | 阿Q说代码有小伙伴私信我说想要研究下Spring的源码&#xff0c;想让我出一期教程来实现IDEA导入Spring源码&#xff0c;今天它来了~版本 &#xff1a;IDEA 2020.2.3 &#xff1b;Spring 5.0.x &#xff1b;gradle 4.4.1 &#xff1b;先从github上面把 spring …

基于MaxCompute分布式Python能力的大规模数据科学分析

简介&#xff1a; 如何利用云上分布式 Python 加速数据科学。 如果你熟悉 numpy、pandas 或者 sklearn 这样的数据科学技术栈&#xff0c;同时又受限于平台的计算性能无法处理&#xff0c;本文介绍的 MaxCompute 可以让您利用并行和分布式技术来加速数据科学。也就是说只要会用…

5新建没有头文件_开垦绿茵版图迎来“真金白银”保障,新建足球场地可获财政补贴...

本周二&#xff0c;国家发改委、体育总局、国务院足球改革发展部际联席会议办公室共同制定了《全国社会足球场地设施建设专项行动实施方案(试行)》。《方案》指出&#xff0c;对新建11人制标准足球场&#xff0c;每个球场补助200万元&#xff1b;对新建5人制、7人制(8人制)足球…

网站免费空间和服务器的区别,网站空间和服务器的区别

网站空间和服务器的区别 内容精选换一换汇总对象存储服务OBS的各项功能&#xff0c;并对其进行简单的介绍&#xff0c;帮助您从整体上了解OBS的功能特性。CCE Turbo集群是基于云原生基础设施构建的云原生2.0容器引擎服务&#xff0c;具备软硬协同、网络无损、安全可靠、调度智能…

基于 MaxCompute + Hologres 的人群圈选和数据服务实践

简介&#xff1a; 本文主要介绍如何通过 MaxCompute 进行海量人群的标签加工&#xff0c;通过 Hologres 进行分析建模&#xff0c;从而支持大规模人群复杂圈选场景下的交互式体验&#xff0c;以及基于API的数据服务最佳实践。 本文作者 刘一鸣 阿里云智能 高级产品专家 人群圈…

一款强大的 Kubernetes API 流量查看神器

作者 | 小碗汤来源 | 我的小碗汤mizu 是为 Kubernetes 提供的一个简单而强大的 API 流量查看器&#xff0c;可以查看微服务之间的所有 API 通信&#xff0c;以帮助调试和排除故障。相当于 Kubernetes 的 TCPDump 和 Wireshark。简单而强大的 CLI丰富的过滤规则API 调用实时监控…

Redis 巧用数据类型实现亿级数据统计

作者 | 码哥字节来源 | 码哥字节在移动应用的业务场景中&#xff0c;我们需要保存这样的信息&#xff1a;一个 key 关联了一个数据集合&#xff0c;同时还要对集合中的数据进行统计排序。常见的场景如下&#xff1a;给一个 userId &#xff0c;判断用户登陆状态&#xff1b;两亿…

2021杭州·云栖大会来了!门票免费预约!

2021杭州云栖大会&#xff0c;定了&#xff01; 10月19日-22日&#xff0c;就在杭州云栖小镇 2场重磅主论坛上百场分论坛 超4万平米科技展 今年&#xff0c;云栖大会将首次免费开放 门票可在官网免费预约 入口现已开启 戳此预约&#xff0c;我们不见不散&#xff01; ​ …

js 可以做什么东西_Deno需要做什么才能取代Node.js?

全文共1843字&#xff0c;预计学习时长5分钟Deno是一个Javascript/TypeScript的运行时&#xff0c;旨在取代Node.js的地位。它拥有广泛功能&#xff0c;讨论度非常高&#xff0c;在Github上有将近68000个星星&#xff1a;既然这么受欢迎&#xff0c;那么有人要问了&#xff1a;…

37 手游基于 Flink CDC + Hudi 湖仓一体方案实践

简介&#xff1a; 介绍了 37 手游为何选择 Flink 作为计算引擎&#xff0c;并如何基于 Flink CDC Hudi 构建新的湖仓一体方案。 本文作者是 37 手游大数据开发徐润柏&#xff0c;介绍了 37 手游为何选择 Flink 作为计算引擎&#xff0c;并如何基于 Flink CDC Hudi 构建新的湖…

手把手搭建一个容器化+代理网关+可视化管理环境

作者 | togettoyou来源 | SuperGopher前言本文主要分享个人服务器的应用部署方案现状&#xff0c;容器化代理网关可视化管理。准备阶段我购买的是腾讯云服务器&#xff08;2 核 4GB 3Mbps&#xff09;域名也是在腾讯云备案过的&#xff0c;提前准备域名解析配置环境安装 Docker…