当前位置: 首页 > 后端技术 > Java

EffectiveJava在工作中的应用总结

时间:2023-04-01 22:07:51 Java

简介:《Effective Java》是一本经典的Java学习书籍,值得每一位Java开发者阅读。作者对书中与日常工作关系较密切的知识点进行了部分总结。作者|易秋源|阿里技术公众号《Effective Java》是一本经典的Java学习书籍,值得每一位Java开发者阅读。作者对书中与日常工作关系较密切的知识点进行了部分总结。1创建和销毁对象1如果有多个构造函数参数,优先考虑构造函数。当类构造包含多个参数时,学生会选择JavaBeans模式。在这种模式下,你可以调用一个无参数的构造函数来创建对象,然后调用setter方法来设置必需参数和可选参数。目前比较流行的一种方法是在类中添加Lombok提供的@Data注解,自动生成getter/setter、equals等方法。但是,JavaBeans模式不能使类不可变(不可变,详见“MinimizingDeformability”部分)。这就需要开发者自己控制值的更新,保证线程安全等等。建议:Builder模式Builder模式在构建器对象上调用类似setter的方法来设置相关参数(类似于ProtoBuffers)。最后,通过调用build方法生成一个不可变对象。使用Builder模式的一种方法是在类上添加Lombok提供的@Builder注释。应用:APIRequest&Response在微服务架构中,服务请求(request)和响应(response)往往包含很多参数。在处理请求的过程中,笔者经常担心请求的内容被误操作修改了。因此笔者倾向于使用Builder模式。我们可以使用Builder模式来构建这种类型的对象。在构建过程中,如果需要引入额外的逻辑(如if-else),可以先返回Builder对象,再调用build方法。importlombok.Builder;/**Requestclass*/@BuilderpublicclassSampleRequest{privateStringparamOne;私人诠释参数二;privatebooleanparamThree;}/**Responseclass*/@BuilderpublicclassSampleResponse{privatebooleansuccess;}/**Serviceinterface*/publicinterfaceSampleFacade{ResultrpcOne(RequestParam);}/**call*/publicvoidtestRpcOne(){SampleRequestrequest=SampleRequest.builder().paramOne("one").paramTwo(2).paramThree(true).build();Resultresponse=sampleFacade.rpcOne(request);}2通过私有构造函数加强不可实例化的能力有些类,比如实用类,只包含静态字段和静态方法。这些类应该尽量确保它们不被实例化,以防止被用户误用。建议:私有类构造函数为了防止误导用户,认为类是专门为继承而设计的,我们可以将构造函数私有化。公共类SampleUtility{publicstaticStringgetXXX(){返回“测试”;}/**私有构造函数*/privateSampleUtility(){}}/**直接方法调用*/publicstaticvoidmain(String[]args){System.out.println(SampleUtility.getXXX());}SecondClassandInterfacePart1最小化类和成员的可访问性尽可能使每个类或成员无法被外界访问。建议:有时,出于测试目的,我们不得不将一些私有类、接口或成员包私有化。这里笔者推荐大家使用Guava提供的@VisiableForTesting注解,提醒大家这是为了测试,并且在包级别将无障碍级别设为private,放宽了限制。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;导入org.junit.runner.RunWith;导入org.powermock.core.classloader.annotations.PrepareForTest;导入org.powermock.modules.junit4.PowerMockRunner;导入org.powermock.reflect.Whitebox;@RunWith(PowerMockRunner.class)@PrepareForTest({Utility.class})publicclassUtilityTest{@Testpublicvoidtest_privateIsGreaterThan_success()throwsException{/**测试私有isGreaterThan方法*/booleanresult=Whitebox.invokeMethod(Utility.class,"isGreaterThan",3,2);断言.assertTrue(结果);}}2最小化可变形性不可变类(immutableclass)是指类的对应实例创建后,其成员变量的值不能改变。即实例中包含的所有信息必须在创建实例时提供,并且在对象的生命周期内是固定的。不可变类一般采用函数式模式,即对应的方法返回一个函数的结果,函数对操作数进行操作但不修改。更常见的对应物是程序或命令。使用这些方法时,将过程应用于它们的操作数会导致其状态发生变化。正如“如果有多个构造函数参数,优先考虑构造函数”一节中提到的,不可变对象比较简单,线程安全,只有一个状态。使用这个类的开发者不需要做额外的工作来维护约束关系。此外,可变对象可以具有任意复杂的状态。如果mutator方法(如update)没有详细说明,开发者需要自行阅读方法内容。作者往往花很多时间去搞清楚某个方法中改变了可变对象的哪些字段,以及该方法结束后是否会影响后续的对象操作。作者建议传入一个不可变对象,并以此为基础,用更新后的参数创建一个新的不可变对象并返回。虽然创建的对象更多,但保证不可变形,可读性更强。推荐:GuavaCollection之Immutable类在日常开发中,作者倾向于使用Immutable类(ImmutableList、ImmutableSet、ImmuableMap)和上面提到的函数模式集合来实现mutator类方法。importstaticcom.google.common.collect.ImmutableList.toImmutableList;importcom.google.common.collect.ImmutableList;importcom.google.common.collect.ImmutableMap;/**推荐*/privatestaticfinalImmutableMapSAMPLE_MAP=ImmutableMap.of("One",1,"Two",2);/**建议:确保原始输入列表不会改变*/publicImmutableListupdateXXX(ImmutableListinput){returninput.stream().map(obj->obj.setXXX(true)).collect(toImmutableList());}/**不推荐:更改输入信息*/publicvoidfilterXXX(Listinput){input.forEach(obj->obj.setXXX(true));}三个泛型1Listpreferredoverarrays数组是协变的(covariant),即Sub是Super的子类型,那么数组类型Sub[]就是Super[];数组被具体化了,它们的元素类型约束在运行时是未知的和检查的。而泛型是不可变和可擦除的(即它们的类型信息在编译时强制执行并在运行时丢弃)。警惕公共静态最终数组的出现。很可能是一个安全漏洞!四种方法1参数的校验如果一个方法传递了一个无效的参数值,这个方法在执行复杂耗时的逻辑之前先对参数进行校验,失败后会迅速明确地正确抛出相应的异常。如果不验证它的参数,后面可能会出现各种奇怪的异常,有时很难排查和定位原因。笔者认为微服务提供的API请求也应该遵循这个思路。即在API请求被服务处理之前,先进行参数校验。每个请求都应该绑定到相应的请求验证器。如果参数值无效,则会抛出特定的ClientException(例如IllegalArgumentException)。2精心设计方法签名谨慎选择方法名称:执行动作的方法通常以动词或动词短语命名:createXXX、updateXXX、removeXXX、convertXXX、generateXXX对于返回布尔值的方法,一般以is:isValid、isValid、isLive,isEnabled以避免参数列表过长:目标是四个参数或更少。当参数过多时,作者会使用Pair、Triple或辅助类(如静态成员类)...}privatestaticSampleResultgenerateResult(Stringinput){...}/**辅助类*/privatestaticclassSampleResult{privatebooleansuccess;私有列表xxxList;私人整数计数;该方法返回null而不是零长度数组或集合。开发人员需要添加!=null检查。有时候很容易忘记出错,报NullpointerException。话虽如此,我想提一下Optional。网上有很多关于Optional和null的使用的讨论。Optional允许调用者继续流畅的方法调用序列(例如stream.getFirst().orElseThrow(()->newMyFancyException()))。以下是作者的观点。/**推荐:提示返回值可能为空。*/publicOptionalfindFoo(Stringid);/***中性:稍微麻烦*考虑doSomething("bar",null);*或者重载doSomething("bar");和doSomething("bar","baz");**/publicFoodoSomething(Stringid,OptionalbarOptional);/***不推荐:违背了Optional设计的目的。*当Optional值为default时,一般有3种处理方式:1)提供替换值;2)调用方法提供替换值;3)抛出异常*这些处理方法都可以在字段初始化或赋值时处理。**/publicclassBook{privateList页面;privateOptionalindex;}/***弃用:违背了Optional的设计目的。*如果是默认值,则不能直接放入列表中。**/List>VGeneralProgrammingPart1如果您需要精确的答案,请避免使用float和double。float和double类型主要用于科学和工程计算。它们执行二进制浮点运算,以提供相对准确的数值范围快速近似值。但是,它们不能提供完全准确的结果,尤其不适合货币计算。float或double不能精确表示0.1。如果需要系统记录小数点,可以使用BigDecimal。2基元优先于盒装基元,例如int、double、long和boolean。每一种原始类型都有一个对应的引用类型,称为装箱原始类型,对应于Integer、Double、Long和Boolean。书中提到,它们的区别如下:/**推荐*/publicintsum(inta,intb){returna+b;}/**不推荐:不必要的装箱*/publicIntegersum(Integera,Integerb){returna+b;}如果没有特殊的使用场景,建议始终使用原始类型。如果您必须使用盒装原语,请注意==操作和NullPointerException异常。盒装原始类型的使用场景:方法调用反映为集合中的元素(例如Set)参数化类型(例如ThreadLocal)六种异常1方法抛出的每个异常都必须记录在案始终单独声明检查异常,并且使用Javadoc@throws标记准确记录每个异常被抛出的条件。在日常工作中,我在调用其他组的API时,有时会发现一些意想不到的异常。好的文档可以帮助API调用者更好地处理相关的异常。文档可以包括:异常的类型、异常的错误代码和描述。2其他一些公司将API产生的异常分为ClientException和ServerException。一般来说,ClientException(例如无效的服务请求)是调用者非常规的API调用导致的异常处理,可能不在服务端的主要异常监控范围内。而ServerException(如数据库查询超时)是服务器本身的问题,需要经常监控。7引用布洛赫,约书亚。2018.EffectiveJava,3rdEdition原文链接本文为阿里云原创内容,未经允许不得转载。