发布网友 发布时间: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。如书中提到,它们的区别如下:
若无特殊的使用场景,推荐总是使用基本类型。若不得不使用装箱基本类型,注意==操作和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
原文链接
作者|宜秋
来源|阿里技术公众号