问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

EffectiveJava在工作中的应用总结

发布网友 发布时间:2024-10-01 06:29

我来回答

1个回答

热心网友 时间:2024-10-20 04:31

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

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

一创建和销毁对象篇

1若有多个构造器参数时,优先考虑构造器

当类构造包含多个参数时,同学们会选择JavaBeans模式。在这种模式下,可以调用一个无参构造器来创建对象,然后调用setter方法来设置必要和可选的参数。目前较受欢迎的方法之一如在类上加入Lombok提供的@Data注解,来自动生成getter/setter、equals等方法。但是JavaBeans模式无法将类做成不可变(immutable,详见“使可变形最小化”章节)。这就需要开发者自己掌控值的更新情况,确保线程安全等。

推荐:Builder模式

Builder模式通过builder对象上,调用类似setter的方法,设置相关的参数(类似ProtoBuffers)。最后,通过调用build方法来生成不可变的对象(immutableobject)。使用Builder模式的方法之一包括在类上加入Lombok提供的@Builder注解。

应用:APIRequest&Response

在微服务架构中,服务的请求(request)和响应(response)往往包含较多参数。在处理请求的过程中,笔者也常常会担心误操作修改了请求的内容。所以,笔者倾向使用Builder模式。

我们可使用Builder模式来构建该类型对象。在构建过程中,若需要引入额外逻辑(e.g.if-else),可先返回Builder对象,最后再调用build方法。

importlombok.Builder;/**请求类*/@BuilderpublicclassSampleRequest{privateStringparamOne;privateintparamTwo;privatebooleanparamThree;}/**响应类*/@BuilderpublicclassSampleResponse{privatebooleansuccess;}/**服务接口*/publicinterfaceSampleFacade{Result<SampleResponse>rpcOne(RequestParam<SampleRequest>);}/**调用*/publicvoidtestRpcOne(){SampleRequestrequest=SampleRequest.builder().paramOne("one").paramTwo(2).paramThree(true).build();Result<SampleResponse>response=sampleFacade.rpcOne(request);}

2通过私有构造器强化不可实例化的能力

有些类,例如工具类(utilityclass),只包含静态字段和静态方法。这些类应尽量确保不被实例化,防止用户误用。

推荐:私有化类构造器

为了防止误导用户,认为该类是专门为了继承而设计的,我们可以将构造器私有化。

publicclassSampleUtility{publicstaticStringgetXXX(){return"test";}/**私有化构造器*/privateSampleUtility(){}}/**直接调用方法*/publicstaticvoidmain(String[]args){System.out.println(SampleUtility.getXXX());}二类和接口篇

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

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

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

importcom.google.common.annotations.VisibleForTesting;@VisibleForTesting(otherwise=VisibleForTesting.PRIVATE)StringgetXXX(){return"test";}

此外,也有小伙伴推荐PowerMock单元测试框架。PowerMock是Mockito的加强版,可以实现完成对private/static/final方法的Mock(模拟)。通过加入@PrepareForTest注解来实现。

publicclassUtility{privatestaticbooleanisGreaterThan(inta,intb){returna>b;}privateUtility(){}}/**测试类*/importorg.junit.Test;importorg.junit.jupiter.api.Assertions;importorg.junit.runner.RunWith;importorg.powermock.core.classloader.annotations.PrepareForTest;importorg.powermock.moles.junit4.PowerMockRunner;importorg.powermock.reflect.Whitebox;@RunWith(PowerMockRunner.class)@PrepareForTest({Utility.class})publicclassUtilityTest{@Testpublicvoidtest_privateIsGreaterThan_success()throwsException{/**测试私有的isGreaterThan方法*/booleanresult=Whitebox.invokeMethod(Utility.class,"isGreaterThan",3,2);Assertions.assertTrue(result);}}

2使可变形最小化

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

不可变类一般采用函数(functional)模式,即对应的方法返回一个函数的结果,函数对操作数进行运算但并不修改它。与之相对应的更常见的是过程的(procere)或者命令式的(imperative)做法。使用这些方法时,将一个过程作用在它们的操作数上,会导致它的状态发生改变。

如在“若有多个构造器参数时,优先考虑构造器”一节中提到,不可变对象比较简单,线程安全,只有一种状态。使用该类的开发者无需再做额外的工作来维护约束关系。另外,可变的对象可以有任意复杂的状态。若mutator方法(e.g.update)无详细的描述,开发者需要自行阅读方法内容。笔者经常会花费较多时间弄清楚在某方法内,可变对象的哪些字段被更改,方法结束后会不会影响后续的对象操作。笔者推荐传入不可变对象,基于此用更新的参数创建新的不可变对象返回。虽然会创建更多的对象,但是保证了不可变形,以及更可读性。

推荐:GuavaCollection之Immutable类

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

importstaticcom.google.common.collect.ImmutableList.toImmutableList;importcom.google.common.collect.ImmutableList;importcom.google.common.collect.ImmutableMap;/**推荐*/privatestaticfinalImmutableMap<String,Integer>SAMPLE_MAP=ImmutableMap.of("One",1,"Two",2);/**推荐:确保原input列表不会变化*/publicImmutableList<TestObj>updateXXX(ImmutableList<TestObj>input){returninput.stream().map(obj->obj.setXXX(true)).collect(toImmutableList());}/**不推荐:改变input的信息*/publicvoidfilterXXX(List<TestObj>input){input.forEach(obj->obj.setXXX(true));}三泛型篇

1列表优先于数组

数组是协变的(covariant),即Sub为Super的子类型,那么数组类型Sub[]就是Super[]的子类型;数组是具体化的,在运行时才知道并检查它们的元素类型约束。而泛型是不可变的和可擦除的(即编译时强化它们的类型信息,并在运行时丢弃)。

需要警惕publicstaticfinal数组的出现。很有可能是个安全漏洞!

四方法篇

1校验参数的有效性

若传递无效的参数值给方法,这个方法在执行复杂、耗时逻辑之前先对参数进行了校验(validation),便很快就会失败,并且可清楚地抛出适当的异常。若没有校验它的参数,就可能会在后续发生各种奇怪的异常,有时难以排查定位原因。

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

2谨慎设计方法签名

谨慎地选择方法的名称:执行某个动作的方法通常用动词或者动词短语命名:createXXX,updateXXX,removeXXX,convertXXX,generateXXX对于返回boolean值的方法,一般以is开头:isValid,isLive,isEnabled

避免过长的参数列表:目标是四个参数,或者更少。当参数过多时,笔者会使用Pair,Triple或辅助类(e.g.静态成员类)

publicclassSampleListener{

publicConsumeConcurrentlyStatusconsumeMessage(Stringinput){SampleResultresult=generateResult(input);...}privatestaticSampleResultgenerateResult(Stringinput){...}/**辅助类*/privatestaticclassSampleResult{privatebooleansuccess;privateList<String>xxxList;privateintcount;}}

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

若一个方法返回null而不是零长度的数组或者集合,开发者需要加入!=null的检查,有时容易忘记出错,报NullpointerException。

说到此,笔者想额外提一下Optional。网络上有很多关于Optional和null的使用讨论。Optional允许调用者继续一系列流畅的方法调用(e.g.stream.getFirst().orElseThrow(()->newMyFancyException()))。以下为笔者整理的观点。

/**推荐:提示返回值可能为空。*/publicOptional<Foo>findFoo(Stringid);/***中立:稍显笨重*可考虑doSomething("bar",null);*或者重载doSomething("bar");和doSomething("bar","baz");**/publicFoodoSomething(Stringid,Optional<Bar>barOptional);/***不推荐:违背Optional设计的目的。*当Optional值缺省时,一般有3种处理方法:1)提供代替的值;2)调用方法提供代替的值;3)抛出异常*这些处理方法可以在字段初始或赋值的时候处理。**/publicclassBook{privateList<Pages>pages;privateOptional<Index>index;}/***不推荐:违背Optional设计的目的。*若为缺省值,可直接不放入列表中。**/List<Optional<Foo>>五通用程序设计篇

1如果需要精确的答案,请避免使用float和double

float和double类型主要用于科学工程计算。它们执行二进制浮点运算,为了在数值范围上提供较为精准的快速近似计算。但是,它们并不能提供完全精确的结果,尤其不适合用于货币计算。float或者double精确地表示0.1是不可行的。

若需系统来记录十进制小数点,可使用BigDecimal。

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

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

/**推荐*/publicintsum(inta,intb){returna+b;}/**不推荐:不必要的装箱*/publicIntegersum(Integera,Integerb){returna+b;}

若无特殊的使用场景,推荐总是使用基本类型。若不得不使用装箱基本类型,注意==操作和NullPointerException异常。装箱基本类型的使用场景:

作为集合中的元素(e.g.Set<Long>)

参数化类型(e.g.ThreadLocal<Long>)

反射的方法调用

六异常

1每个方法抛出的异常都要有文档

始终要单独地声明受检的异常,并且利用Javadoc的@throws标记,准确地记录下抛出每个异常的条件。

在日常工作中,笔者调用其他组的API时,有时会发现一些意料之外的异常。良好的文档记录,可以帮助API调用者更好得处理相关的异常。文档记录可包括:异常的类型,异常的errorcode,和描述。

2其他

一些公司将API产生的异常分成ClientException和ServerException。一般ClientException(e.g.无效的服务request)是由调用方非常规调用API导致的异常处理,可不在服务端主要的异常监测范围中。而ServerException(e.g.数据库查询超时)是由服务端自身原因导致的问题,平时需要着重监测。

七引用

Bloch,Joshua.2018.EffectiveJava,3rdEdition

原文链接

作者|宜秋

来源|阿里技术公众号
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
做青红椒炒毛肚有哪些好吃的诀窍? 如何自制好吃的香辣毛肚? 穿越火线警告码SX(2,509,0)如何解决 CF sx警告码(2,770,0)什么原因 ...要求重新启动电脑 警告码是2 xxxx 0 有时重启4 5次才能玩 win7系统... 穿越火线 sx 警告码 2,994,0 怎么解决 CF出现非法模块SX警告码(2,990,0) 我为什么上CF体验服没开G有非法模块???怎么办啊 警告码(2,502,0) 穿越火线进游戏后 2分钟左右 就出来个 SX 警告码2,990,0 看到刘老师那慈祥的面庞,我感慨万分,我想到了这样几个赞美刘老师的成语... 怎样用语言描述下雨的声音和场面? 手机银行 可不可以 用在淘宝上付款 ...手机银行卡,但是在不知道怎样用它在淘宝上买东西付款。求具体步骤... 贵州工程应用技术学院成人高考计算机科学与技术专业怎么样 考研日语难度如何 考研外语选英语好还是日语好 原子桌面是什么意思? 叕怎么拼? 人工养殖海参和野生的区别 歪歪卫士多开器常见问答 伊秉绶书法成就 60岁大寿生日快乐祝福语大全 闲雅胜闻琴的下一句闲雅胜闻琴的下一句是什么 写给自己的生63岁'生日诗? 歪歪卫士多开器软件信息 姚合《寄国子杨巨源祭酒》原文及翻译赏析 如何让胶水反应 如何让胶失去粘性 贵州广播有哪些频道 oppofindx8参数 《信息检索导论》第一章 布尔检索——学习笔记及要点整理 去就朝天终有路,千外祈求千处现是什么生肖 千处祈求千处应答一生肖 国外有哪些素材网站推荐呢? 全网最好用的20个出色的国外素材网站 冷车启动抖动热车正常原因? 冷车剧烈抖动热车正常是为什么? 冷车怠速抖动厉害热车后正常 发动机冷车抖动热车正常 温州一个160G的移动硬盘大概价位是多少? 宁都县国家税务局机构概况 蓝田县国家税务局机构设置 心肺复苏的适应症和禁忌症 细数新生儿窒息容易窒息的常见原因 头抬不起来是什么原因 win10电脑频繁出现“选择一个选项”界面怎么办? 颈椎痛得厉害怎么办 站立着,头不能抬起来看天,躺着,要起身后背很痛。是颈椎病吗?该怎么治... Win10蓝屏提示“PAGE_FAULT_IN_NONPAGED_AREA”的解决方法 酒股票为什么涨 绣球养殖护理方法 绣球怎么养护和管理