那些年,老同志们踩过的 Java 坑
中国有句老话叫"事不过三",指一个人犯了同样的错误,一次两次三次还可以原谅,超过三次就不可原谅了。有人指出这个“三”是虚数,用来泛指多次,所以"事不过三"不包括“三”。至于"事不过三"包不包括“三”,可能跟每个人的底线有关系,属于哲学范畴,不在本文的讨论范围之内。
写代码也是如此,同一个代码“坑”,踩第一次叫"长了经验",踩第二次叫"加深印象",踩第三次叫"不长心眼",踩三次以上就叫"不可救药"。在本文中,笔者总结了一些代码坑,描述了问题现象,进行了问题分析,给出了避坑方法。希望大家在日常编码中,遇到了这类代码坑,能够提前避让开来。
1.对象比较方法提供的方法,非常方便地实现了对象的比较,有效地避免了繁琐的空指针检查。
1.1.问题现象在之前,在判断一个短整型、整型、长整型包装数据类型与常量是否相等时,我们一般这样写:
复制代码
ShortshortValue=(short)12345;(shortValue==12345);//(12345==shortValue);//trueIntegerintValue=12345;(intValue==12345);//(12345==intValue);//trueLonglongValue=12345L;(longValue==12345);//(12345==longValue);//true
从之后,提供了方法,并推荐使用函数式编程,更改代码如下:
复制代码
ShortshortValue=(short)12345;((shortValue,12345));//((12345,shortValue));//falseIntegerintValue=12345;((intValue,12345));//((12345,intValue));//trueLonglongValue=12345L;((longValue,12345));//((12345,longValue));//false
为什么直接把==替换为方法会导致输出结果不一样?
1.2.问题分析通过反编译第一段代码,我们得到语句"(shortValue==12345);"的字节码指令如下:
复制代码
7:[22]10aload_1[shortValue]11():short[28]14sipush1234517if_icmpne2420iconst_121goto2524iconst_025(boolean):void[32]
原来,编译器会判断包装数据类型对应的基本数据类型,并采用这个基本数据类型的指令进行比较(比如上面字节码指令中的sipush和if_icmpne等),相当于编译器自动对常量进行了数据类型的强制转化。
为什么采用方法后,编译器不自动对常量进行数据类型的强制转化?通过反编译第二段代码,我们得到语句“((shortValue,12345));”的字节码指令如下:
复制代码
7:[22]10aload_1[shortValue]11(int):[28]17(,):boolean[33]20(boolean):void[39]
原来,编译器根据字面意思,认为常量12345默认基本数据类型是int,所以会自动转化为包装数据类型Integer。
下面来分析一下方法的代码实现:
复制代码
publicstaticbooleanequals(Objecta,Objectb){return(a==b)||(a!=(b));}其中,语句“(b)”将会使用到方法。
方法的代码实现为:
复制代码
publicbooleanequals(Objectobj){if(objinstanceofShort){returnvalue==((Short)obj).shortValue();}returnfalse;}通过代码实现分析:对应语句"((shortValue,12345));",因为的两个参数对象类型不一致,一个是包装数据类型Short,另一个是包装数据类型Integer,所以最终的比较结果必然是false。同样,语句“((intValue,12345));”,因为的两个参数对象类型一致,都是包装数据类型Integer且取值一样,所以最终的比较结果必然是true。
1.3.避坑方法1、保持良好的编码习惯,避免数据类型的自动转化
为了避免数据类型自动转化,更科学的写法是直接声明常量为对应的基本数据类型。
第一段代码可以这样写:
复制代码
ShortshortValue=(short)12345;(shortValue==(short)12345);//((short)12345==shortValue);//trueIntegerintValue=12345;(intValue==12345);//(12345==intValue);//trueLonglongValue=12345L;(longValue==12345L);//(12345L==longValue);//true
第二段代码可以这样写:
复制代码
ShortshortValue=(short)12345;((shortValue,(short)12345));//(((short)12345,shortValue));//trueIntegerintValue=12345;((intValue,12345));//((12345,intValue));//trueLonglongValue=12345L;((longValue,12345L));//((12345L,longValue));//true
2、借助开发工具或插件,及早地发现数据类型不匹配问题
在Eclipse的问题窗口中,我们会看到这样的提示:
复制代码
Unlikelyargumenttypeforequals():intseemstobeunrelatedtoShortUnlikelyargumenttypeforequals():ShortseemstobeunrelatedtointUnlikelyargumenttypeforequals():intseemstobeunrelatedtoLongUnlikelyargumenttypeforequals():Longseemstobeunrelatedtoint
通过FindBugs插件扫描,我们会看到这样的警告:
复制代码
(Integer)(String[])[Scariest(1),Highconfidence](Short)(String[])[Scariest(1),Highconfidence](Integer)(String[])[Scariest(1),Highconfidence](Long)(String[])[Scariest(1),Highconfidence]
3、进行常规性单元测试,尽量把问题发现在研发阶段
“勿以善小而不为”,不要因为改动很小就不需要进行单元测试了,往往Bug都出现在自己过度自信的代码中。像这种问题,只要进行一次单元测试,是完全可以发现问题的。
2.三元表达式拆包三元表达式是Java编码中的一个固定语法格式:“条件表达式?表达式1:表达式2”。三元表达式的逻辑为:“如果条件表达式成立,则执行表达式1,否则执行表达式2”。
2.1.问题现象复制代码
booleancondition=false;Doublevalue1=1.0D;Doublevalue2=2.0D;Doublevalue3=null;Doubleresult=condition?value1*value2:value3;//抛出空指针异常
当条件表达式condition等于false时,直接把Double对象value3赋值给Double对象result,按道理没有问题呀,为什么会抛出空指针异常(NullPointerException)?
2.2.问题分析通过反编译代码,我们得到语句"Doubleresult=condition?value1*value2:value3;"的字节码指令如下:
复制代码
17iload_1[condition]18ifeq3321aload_2[value1]22():double[24]25aload_3[value2]26():double[24]29dmul30goto3833aload4[value3]35():double[24]38(double):[16]41astore5[result]43:[28]46aload5[result]
在第33行,加载Double对象value3到操作数栈中;在第35行,调用Double对象value3的doubleValue方法。这个时候,由于value3是空对象null,调用doubleValue方法必然抛出抛出空指针异常。但是,为什么要把空对象value3转化为基础数据类型double?
查阅相关资料,得到三元表达式的类型转化规则:
根据规则分析,表达式1(value1*value2)计算后返回基础数据类型double,表达式2(value3)返回包装数据类型Double,根据三元表达式的类型转化规则判断,最终的返回类型为基础数据类型double。所以,当条件表达式condition等于false时,需要把空对象value3转化为基础数据类型double,于是就调用了value3的doubleValue方法抛出了空指针异常。
可以用以下案例验证三元表达式的类型转化规则:
复制代码
booleancondition=false;Doublevalue1=1.0D;Doublevalue2=2.0D;Doublevalue3=null;Integervalue4=null;//返回类型为Double,不抛出空指针异常Doubleresult1=condition?value1:value3;//返回类型为double,会抛出空指针异常Doubleresult2=condition?value1:value4;//返回类型为double,不抛出空指针异常Doubleresult3=!condition?value1*value2:value3;//返回类型为double,会抛出空指针异常Doubleresult4=condition?value1*value2:value3;2.3.避坑方法
1、尽量避免使用三元表达式,可以采用if-else语句代替
如果三元表达式中有算术计算和包装数据类型,可以考虑利用if-else语句代替。改写代码如下:
复制代码
booleancondition=false;Doublevalue1=1.0D;Doublevalue2=2.0D;Doublevalue3=null;Doubleresult;if(condition){result=value1*value2;}else{result=value3;}2、尽量使用基本数据类型,避免数据类型的自动转化
如果三元表达式中有算术计算和包装数据类型,可以考虑利用if-else语句代替。改写代码如下:
复制代码
booleancondition=false;doublevalue1=1.0D;doublevalue2=2.0D;doublevalue3=3.0D;doubleresult=condition?value1*value2:value3;
3、进行覆盖性单元测试,尽量把问题发现在研发阶段
像这种问题,只要编写一些单元测试用例,进行一些覆盖性测试,是完全可以提前发现的。
3.泛型对象赋值Java泛型是中引入的一个新特性,其本质是参数化类型,即把数据类型做为一个参数使用。
3.1.问题现象在做用户数据分页查询时,因为笔误编写了如下代码:
1、:
复制代码
/**分页数据VO类*/@Getter@Setter@ToString@NoArgsConstructor@AllArgsConstructorpublicclassPageDataVOT{/**总共数量*/privateLongtotalCount;/**数据列表*/privateListTdataList;}2、:
复制代码
/**用户DAO接口*/@MapperpublicinterfaceUserDAO{/**统计用户数量*/publicLongcountUser(@Param("query")UserQueryVOquery);/**查询用户信息*/publicListUserDOqueryUser(@Param("query")UserQueryVOquery);}3、:
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**用户DAO*/@AutowiredprivateUserDAOuserDAO;/**查询用户信息*/publicPageDataVOUserVOqueryUser(UserQueryVOquery){ListUserDOdataList=null;LongtotalCount=(query);if((totalCount)(0L)0){dataList=(query);}returnnewPageDataVO(totalCount,dataList);}}4、:
复制代码
/**用户控制器类*/@Controller@RequestMapping("/user")publicclassUserController{/**用户服务*/@AutowiredprivateUserServiceuserService;/**查询用户*/@ResponseBody@RequestMapping(value="/query",method=)publicResultPageDataVOUserVOqueryUser(@RequestBodyUserQueryVOquery){PageDataVOUserVOpageData=(query);(pageData);}}以上代码没有任何编译问题,但是却把UserDO中一些涉密字段返回给前端。细心的读者可能已经发现了,在UserService类的queryUser方法的语句"returnnewPageDataVO(totalCount,dataList);"中,我们把ListUserDO对象dataList赋值给了PageDataVOUserVO的ListUserVO字段dataList。
问题是:为什么开发工具不报编译错误啦?
3.2.问题分析由于历史原因,参数化类型和原始类型需要兼容。我们以ArrayList举例子,来看看如何兼容的。
以前的写法:
复制代码
ArrayListlist=newArrayList();
现在的写法:
复制代码
ArrayListStringlist=newArrayListString();
考虑到与以前的代码兼容,各种对象引用之间传值,必然会出现以下的情况:
复制代码
//第一种情况ArrayListlist1=newArrayListString();//第二种情况ArrayListStringlist2=newArrayList();
所以,Java编译器对以上两种类型进行了兼容,不会出现编译错误,但会出现编译告警。但是,我的开发工具在编译时真没出现过告警。
再来分析我们遇到的问题,实际上同时命中了两种情况:
1、把ListUserDO对象赋值给List,命中了第一种情况;
2、把PageDataVO对象赋值给PageDataVOUserVO,命中了第二种情况。
最终的效果就是:我们神奇地把ListUserDO对象赋值给了ListUserVO。
问题的根源就是:我们在初始化PageDataVO对象时,没有要求强制进行类型检查。
3.3.避坑方法1、在初始化泛型对象时,推荐使用diamond语法
在《阿里巴巴Java开发手册》中,有这么一条推荐规则:
其实,初始化泛型对象时,全省略是不推荐的。这样会避免类型检查,从而造成上面的问题。
在初始化泛型对象时,推荐使用diamond语法,代码如下:
复制代码
returnnewPageDataVO(totalCount,dataList);
现在,在Eclipse的问题窗口中,我们会看到这样的错误:
复制代码
CannotinfertypeargumentsforPageDataVO
于是,我们就知道忘了把ListUserDO对象转化为ListUserVO对象了。
2、在进行单元测试时,需要对比数据内容
在进行单元测试时,运行正常是一个指标,但数据正确才是更重要的指标。
4.泛型属性拷贝Spring的方法,是一个很好用的属性拷贝工具方法。
4.1.问题现象根据数据库开发规范,数据库表格必须包含id,gmt_create,gmt_modified三个字段。其中,id这个字段,可能根据数据量不同,采用int或long类型(注意:阿里规范要求必须是long类型,这里为了举例说明,允许为int或long类型)。
所以,把这三个字段抽出来,定义了一个BaseDO基类:
复制代码
/**基础DO类*/@Getter@Setter@ToStringpublicclassBaseDOT{privateTid;privateDategmtCreate;privateDategmtModified;}针对user表,定义了一个UserDO类:
复制代码
/**用户DO类*/@Getter@Setter@ToStringpublicclassUserDOextsBaseDOLong{privateStringname;privateStringdescription;}对于查询接口,定义了一个UserVO类:
复制代码
/**用户VO类*/@Getter@Setter@ToStringpublicstaticclassUserVO{privateLongid;privateStringname;privateStringdescription;}实现查询用户服务接口,实现代码如下:
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**用户DAO*/@AutowiredprivateUserDAOuserDAO;/**查询用户*/publicListUserVOqueryUser(UserQueryVOquery){//查询用户信息ListUserDOuserDOList=(query);if(()){();}//转化用户列表ListUserVOuserVOList=newArrayList(());for(UserDOuserDO:userDOList){UserVOuserVO=newUserVO();(userDO,userVO);(userVO);}//返回用户列表returnuserVOList;}}通过测试,我们会发现一个问题——调用查询用户服务接口,用户ID的值并没有返回。
复制代码
[{"description":"Thisisatester.","name":"tester"},]4.2.问题分析**
**按道理,UserDO类和UserVO类的id字段,类型都是Long类型,不存在类型不可转化,应该能够正常赋值。尝试手工赋值,代码如下:
复制代码
for(UserDOuserDO:userDOList){UserVOuserVO=newUserVO();(());(());(());(userVO);}经过测试,上面代码返回结果正常,用户ID的值成功返回。
那么,就是工具方法的问题了。用Debug模式运行,进入到工具方法内部,得到以下数据:
原来,UserDO类的getId方法返回类型不是Long类型,而是被泛型还原成了Object类型。而下面的工具方法,判断是否能够把Object类型赋值给Long类型,当然会返回false导致不能进行属性拷贝。
为什么作者不考虑"先获取属性值,再判断能否赋值”?建议代码如下:
复制代码
Objectvalue=(source);if((value)(()[0],())){//赋值相关代码}4.3.避坑方法1、不要盲目地相信第三方工具包,任何工具包都有可能存在问题
在Java中,存在很多第三方工具包,比如:Apache的commons-lang3、commons-collections,Google的guava……都是很好用的第三方工具包。但是,不要盲目地相信第三方工具包,任何工具包都有可能存在问题。
2、如果需要拷贝的属性较少,可以手动编码进行属性拷贝
用反射拷贝属性,主要优点是节省了代码量,主要缺点是导致程序性能下降。所以,如果需要拷贝的属性较少,可以手动编码进行属性拷贝。
3、一定要进行单元测试,一定要对比数据内容
在编写完代码后,一定要进行单元测试,一定要对比数据内容。切莫想当然地认为:工具包很成熟、代码也很简单,不可能出现问题。
5.Set对象排重在Java语言中,Set数据结构可以用于对象排重,常见的Set类有HashSet、LinkedHashSet等。
5.1.问题现象编写了一个城市辅助类,从CSV文件中读取城市数据:
复制代码
/**城市辅助类*/@Slf4jpublicclassCityHelper{/**测试主方法*/publicstaticvoidmain(String[]args){CollectionCitycityCollection=readCities2("");((cityCollection));}/**读取城市*/publicstaticCollectionCityreadCities(StringfileName){try(FileInputStreamstream=newFileInputStream(fileName);InputStreamReaderreader=newInputStreamReader(stream,"GBK");CSVParserparser=newCSVParser(reader,())){SetCitycitySet=newHashSet(1024);IteratorCSVRecorditerator=();while(()){(parseCity(()));}returncitySet;}catch(IOExceptione){("读取所有城市异常",e);}();}/**解析城市*/privatestaticCityparseCity(CSVRecordrecord){Citycity=newCity();((0));((1));returncity;}/**城市类*/@Getter@Setter@ToStringprivatestaticclassCity{/**城市编码*/privateStringcode;/**城市名称*/privateStringname;}}代码中使用HashSet数据结构,目的是为了避免城市数据重复,对读取的城市数据进行强制排重。
当输入文件内容如下时:
复制代码
编码,名称010,北京020,广州010,北京
解析后的JSON结果如下:
复制代码
[{"code":"010","name":"北京"},{"code":"020","name":"广州"},{"code":"010","name":"北京"}]但是,并没有对城市“北京”进行排重。
5.2.问题分析当向集合Set中增加对象时,首先集合计算要增加对象的hashCode,根据该值来得到一个位置用来存放当前对象。如在该位置没有一个对象存在的话,那么集合Set认为该对象在集合中不存在,直接增加进去。如果在该位置有一个对象存在的话,接着将准备增加到集合中的对象与该位置上的对象进行equals方法比较:如果该equals方法返回false,那么集合认为集合中不存在该对象,就把该对象放在这个对象之后;如果equals方法返回true,那么就认为集合中已经存在该对象了,就不会再将该对象增加到集合中了。所以,在哈希表中判断两个元素是否重复要使用到hashCode方法和equals方法。hashCode方法决定数据在表中的存储位置,而equals方法判断表中是否存在相同的数据。
分析上面的问题,由于没有重写City类的hashCode方法和equals方法,就会采用Object类的hashCode方法和equals方法。其实现如下:
复制代码
publicnativeinthashCode();publicbooleanequals(Objectobj){return(this==obj);}可以看出:Object类的hashCode方法是一个本地方法,返回的是对象地址;Object类的equals方法只比较对象是否相等。所以,对于两条完全一样的北京数据,由于在解析时初始化了不同的City对象,导致hashCode方法和equals方法值都不一样,必然被Set认为是不同的对象,所以没有进行排重。
那么,我们就重写把City类的hashCode方法和equals方法,代码如下:
复制代码
/**城市类*/@Getter@Setter@ToStringprivatestaticclassCity{/**城市编码*/privateStringcode;/**城市名称*/privateStringname;/**判断相等*/@Overridepublicbooleanequals(Objectobj){if(obj==this){returntrue;}if((obj)){returnfalse;}if(()!=()){returnfalse;}(,((City)obj).code);}/**哈希编码*/@OverridepublicinthashCode(){();}}重新支持测试程序,解析后的JSON结果如下:
复制代码
[{"code":"010","name":"北京"},{"code":"020","name":"广州"}]结果正确,已经对城市“北京”进行排重。
5.3.避坑方法1、当确定数据唯一时,可以使用List代替Set
当确定解析的城市数据唯一时,就没有必要进行排重操作,可以直接使用List来存储。
复制代码
ListCitycitySet=newArrayList(1024);IteratorCSVRecorditerator=();while(()){(parseCity(()));}returncitySet;2、当确定数据不唯一时,可以使用Map代替Set
当确定解析的城市数据不唯一时,需要安装城市名称进行排重操作,可以直接使用Map进行存储。为什么不建议实现City类的hashCode方法,再采用HashSet来实现排重呢?首先,不希望把业务逻辑放在模型DO类中;其次,把排重字段放在代码中,便于代码的阅读、理解和维护。
复制代码
MapString,CitycityMap=newHashMap(1024);IteratorCSVRecorditerator=();while(()){Citycity=parseCity(());((),city);}();3、遵循Java语言规范,重写hashCode方法和equals方法不重写hashCode方法和equals方法的自定义类不应该在Set中使用。
6.公有方法代理SpringCGLIB代理生成的代理类是一个继承被代理类,通过重写被代理类中的非final的方法实现代理。所以,SpringCGLIB代理的类不能是final类,代理的方法也不能是final方法,这是由继承机制限制的。
6.1.问题现象这里举例一个简单的例子,只有超级用户才有删除公司的权限,并且所有服务函数被AOP拦截处理异常。例子代码如下:
1、:
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**超级用户*/privateUsersuperUser;/**设置超级用户*/publicvoidsetSuperUser(UsersuperUser){=superUser;}/**获取超级用户*/publicfinalUsergetSuperUser(){;}}2、:
复制代码
/**公司服务类*/@ServicepublicclassCompanyService{/**公司DAO*/@AutowiredprivateCompanyDAOcompanyDAO;/**用户服务*/@AutowiredprivateUserServiceuserService;/**删除公司*/publicvoiddeleteCompany(LongcompanyId,LongoperatorId){//设置超级用户(newUser(0L,"admin","超级用户"));//验证超级用户if(!(operatorId,().getId())){thrownewExampleException("只有超级用户才能删除公司");}//删除公司信息(companyId,operatorId);}}3、:
复制代码
/**AOP配置类*/@Slf4j@Aspect@ConfigurationpublicclassAopConfiguration{/**环绕方法*/@Around("execution(*.*.*(..))")publicObjectaround(ProceedingJoinPointjoinPoint){try{("开始调用服务方法");();}catch(Throwablee){((),e);thrownewExampleException((),e);}}}当我们调用CompanyService的deleteCompany方法时,居然也抛出空指针异常(NullPointerException),因为调用UserService类的getSuperUser方法获取的超级用户为null。但是,我们在CompanyService类的deleteCompany方法中,每次都通过UserService类的setSuperUser方法强制指定了超级用户,按道理通过UserService类的getSuperUser方法获取到的超级用户不应该为null。其实,这个问题也是由AOP代理导致的。
6.2.问题分析使用SpringCGLIB代理类时,Spring会创建一个名为UserService$EnhancerBySpringCGLIB$???的代理类。反编译这个代理类,得到以下主要代码:
复制代码
publicclassUserService$EnhancerBySpringCGLIB$a2c3b345extsUserServiceimplementsSpringProxy,Advised,Factory{publicfinalvoidsetSuperUser(Uservar1){MethodInterceptorvar10000=$CALLBACK_0;if(var10000==null){CGLIB$BIND_CALLBACKS(this);var10000=$CALLBACK_0;}if(var10000!=null){(this,CGLIB$setSuperUser$0$Method,newObject[]{var1},CGLIB$setSuperUser$0$Proxy);}else{(var1);}}}可以看出,这个代理类继承了UserService类,代理了setSuperUser方法,但是没有代理getSuperUser方法。所以,当我们调用setSuperUser方法时,设置的是原始对象实例的superUser字段值;而当我们调用getSuperUser方法时,获取的是代理对象实例的superUser字段值。如果把这两个方法的final修饰符互换,同样存在获取超级用户为null的问题。
6.3.避坑方法1、严格遵循CGLIB代理规范,被代理的类和方法不要加final修饰符
严格遵循CGLIB代理规范,被代理的类和方法不要加final修饰符,避免动态代理操作对象实例不同(原始对象实例和代理对象实例),从而导致数据不一致或空指针问题。
2、缩小CGLIB代理类的范围,能不用被代理的类就不要被代理
缩小CGLIB代理类的范围,能不用被代理的类就不要被代理,即可以节省内存开销,又可以提高函数调用效率。
7.公有字段代理在fastjson强制升级到1.2.60时踩过一个坑,作者为了开发快速,在ParseConfig中定义了:
复制代码
publicclassParseConfig{publicfinalSymbolTablesymbolTable=newSymbolTable(4096);}在我们的项目中继承了该类,同时又被AOP动态代理了,于是一行代码引起了一场“血案”。
7.1.问题现象仍然使用上章的例子,但是把获取、设置方法删除,定义了一个公有字段。例子代码如下:
1、:
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**超级用户*/publicfinalUsersuperUser=newUser(0L,"admin","超级用户");}2、:
复制代码
/**公司服务类*/@ServicepublicclassCompanyService{/**公司DAO*/@AutowiredprivateCompanyDAOcompanyDAO;/**用户服务*/@AutowiredprivateUserServiceuserService;/**删除公司*/publicvoiddeleteCompany(LongcompanyId,LongoperatorId){//验证超级用户if(!(operatorId,())){thrownewExampleException("只有超级用户才能删除公司");}//删除公司信息(companyId,operatorId);}}3、:
同上一章。
当我们调用CompanyService的deleteCompany方法时,居然抛出空指针异常(NullPointerException)。经过调试打印,发现是UserService的superUser变量为null。如果把AopConfiguration删除,就不会出现空指针异常,说明这个问题是由AOP代理导致的。
7.2.问题分析使用SpringCGLIB代理类时,Spring会创建一个名为UserService$EnhancerBySpringCGLIB$???的代理类。这个代理类继承了UserService类,并覆盖了UserService类中的所有非final的public的方法。但是,这个代理类并不调用super基类的方法;相反,它会创建的一个成员userService并指向原始的UserService类对象实例。现在,内存中存在两个对象实例:一个是原始的UserService对象实例,另一个指向UserService的代理对象实例。这个代理类只是一个虚拟代理,它继承了UserService类,并且具有与UserService相同的字段,但是它从来不会去初始化和使用它们。所以,一但通过这个代理类对象实例获取公有成员变量时,将返回一个默认值null。
7.3.避坑方法1、当确定字段不可变时,可以定义为公有静态常量
当确定字段不可变时,可以定义为公有静态常量,并用类名称+字段名称访问。类名称+字段名称访问公有静态常量,与类实例的动态代理无关。
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**超级用户*/publicstaticfinalUserSUPER_USER=newUser(0L,"admin","超级用户");}/**使用代码*/if(!(operatorId,_())){thrownewExampleException("只有超级用户才能删除公司");}2、当确定字段不可变时,可以定义为私有成员变量
当确定字段不可变时,可以定义为私有成员变量,提供一个公有方法获取该变量值。当该类实例被动态代理时,代理方法会调用被代理方法,从而返回被代理类的成员变量值。
复制代码
/**用户服务类*/@ServicepublicclassUserService{/**超级用户*/privateUsersuperUser=newUser(0L,"admin","超级用户");/**获取超级用户*/publicUsergetSuperUser(){;}}/**使用代码*/if(!(operatorId,().getId())){thrownewExampleException("只有超级用户才能删除公司");}3、遵循JavaBean编码规范,不要定义公有成员变量
遵循JavaBean编码规范,不要定义公有成员变量。JavaBean规范如下:
后记人类受益于“类比”思维,举一反三就是人类的智慧,每当遇到新生事物时,人们往往用类似的已知事物作为参考,能够加速对新生事物的认知。而人类又受制于“定势”思维,因为已知事物并不能代表新生事物,而人们又容易形成先入为主的概念,最终导致对新生事物产生误判。
陈昌毅,花名常意,高德地图技术专家,2018年加入阿里巴巴,一直从事地图数据采集的相关工作。
本文转载自公众号阿里巴巴中间件(ID:Aliware_2018)。
;mid=2247489196idx=1sn=1055ad532d6da3408b97aecd50fd637dchksm=fdeb24ccca9caddaadb4c7ed68a54b6789badc2b349f40e775f422cc2403a13249d56ea53e27scene=27#wechat_redirect
推荐阅读
-
虚现科技股东王博减持5.17万股 权益变动后合计拥有权益为69.94%
挖贝网8月1日,虚现科技(873646)股东王博于2022年8月1日在股转系统通过大宗交易方式减持5.17万股,权益变动后合计拥有权益为69.9394%。据挖贝网了解,2022年8月1日股东王博在全国中小企业股份转让系统通过大宗交易方式完成5.17万股的减持,权益变动前王博合计拥有权益70.6892...
-
广州飞跑信息科技跨境出口云申报平台推动跨境电商高质量发展
广州飞跑信息科技有限公司是一家专业从事软件开发、软件定制、软件实施的高新技术企业。公司坚持以技术为本,服务至上。拥有一批长期专业从事软件开发、软件定制的专业人才,具有雄厚的技术开发实力,全方位满足企业信息化需求。同时公司非常重视企业的内部管理工作,市场销售、软件研发、技术支持是公司的三大核心部门,现...
-
达发(络达)AB1562A蓝牙音频芯片,TOZO NC2无线降噪耳机拆解报告
TOZONC2是TOZO旗下的一款TWS降噪耳机产品,在外观上为柄状的入耳式设计,耳机柄背板采用了光滑的亮面材质,内部彩色散点分布,非常的时尚。功能上,支持蓝牙5.2、光学入耳检测,支持混合主动降噪功能,通透模式以及三麦通话降噪,拥有32小时整体续航,并支持无线充电功能。下面就让我们来看看吧~近期我...
-
黑科技提升用户体验 从技术角度看斗鱼如何应对观赛高峰?
近年来,国内电竞氛围高涨,不仅许多小说、影视剧出现电竞元素,部分电竞项目还进入了亚运会。数据显示,2021年,电竞、体育赛事网络观看人数超20%,随着国内电竞爱好者群体的不断扩大,观赛需求也随之增大。对于直播平台来说,既要持续提升画面质量、也要保障高清晰模式下的播放流畅度,是一件非常考验平台负载能力...