lombok相信大家都用过,没用过肯定也不会点进来,一直用着都很舒服。今但是天在码代码时,碰到了一个问题,卡了半天。问题是这样的:Lombok链式调用父类中的set属性时,返回的居然是一个父类对象,紧靠文字描述,可能比较抽象,代码如下:
子类实体
父类实体
其实就是实体间的继承,把公共的字段抽取出来,具体的实体继承。我这里的JcZyyhzh继承了BaseCompanyIdEntity,BaseCompanyIdEntity其实还继承了BaseIdEntity,因为和我们今天主题扯不上关系,就没贴出来。
业务代码:
业务很简单,就是一个级联的一对多批量增加,将reqVo里的bankRespVos拿出来,用stream转换成对应的实体JcZyyhzh类型的集合,然后在使用service批量增加,QlBeanUtil是我二次封装的BeanUtil工具类,可以把源中的属性按名字复制到一个新的对象,这个新的对象是根据传入的字节码参数newInstance的,这样用起来更舒服,不用多new一次。大概就是这样,因为我在实体是使用@Accessors(chain = true)注解,所以可以链式的set(具体细节后面再说),但是我发现了一个问题,我set子类的属性zyid没问题,一旦我set从父类中继承下来的属性dwid就会报错,
掺杂业务,代码看起来很繁杂,想表达的问题也不突出,我把问题从业务中剥离出来,大概是这样子的;
实体Entity02继承了Entity01,两个实体上都有@Accessors(chain = true)注解,所以都能链式set。
但是由上面的代码可以看到,在第18行,我链式set子类中的 field02和field03就正确,但是我链式从父类中继承下来的field01时,就会报错,这就是今天我的问题。报的什么错呢?19行说的是需要一个Entity01类型的变量,但是返回的却是一个Entity02类型的变量。
这时有人可能马上会说,这问题我会解决,直接把19行的entity02b变量类型声明为Entity01,像这样:
Entity01 entity02b =new Entity02().setField02("111").setField01("111");
这样是不会报错,但是你看这代码,明明new了一个Entity02子类对象,我也想要一个Entity02对象,但是却硬生生的返回了一个一个Entity01类型的对象。变成Entity01类型,那我set的field02不是白set了。那我还不如直接new一个Entity01
原因我第一时间就知道,这是为啥呢?这是因为@Accessors(chain = true),这么顺手的链式调用,只不过是在修改了编译后的class文件,把set方法的返回值由void变成了当前对象,点开Entity02的class文件看看
原本的set方法返回值是void,所以set完了就没有后续,只能再加个分号。但是Lombok这一波操作之后,就有无限可能,set方法把值赋上了之后,还把当前对象扔了出来,我又可以拿着这个对象,干别的事情了。但是问题就来了,因为扔出的是当前的对象,那么Eentity01中的set方法:
Entity01中的field01字段的set方法返回的始终都是Entity01对象,而作为子类,继承了父类的所有属性和方法(关于私有的,可以看我这篇文章:java继承关系中,父类private修饰的内容真的不能被继承吗?NO!!),子类去set从父类中继承下来的属性,其实也是调用的父类中的set方法(这里很明显没有重写),所以才有了上面那一幕,我想要的是一个Entity02类型对象,但是却给我返回一个Entity01类型。我就只是简单的相想用链式调用构造一个Entity02对象,set一些属性而已
经过思考,有两种解决办法,分别是向下转型和重写父类中的set方法。
调用了两个set方法,其实在调用完setField02之后,这个对象还是Entity02类型的,不信你看
拆成两行,只是第二setField01行报错,第一行setField02并不报错,其实想想也是,因为setField02调用的是这个方法(java文件看不到,class文件中)
返回的类型就是Entity02类型。那setField01呢?class文件中如下
setField01方法返账的是Entity01类型,所以最终的类型就是Entity01,用Entity02类型的变量去接收会报错。 但是问题来了,我明明new的是一个Entity02对象,搞过去搞过来,最后变成了Entity01类型,这……??不太合理吧。其实,在setField01时,返回的这个this,本质上是一个Eentity02对象,只是用了一个父类对象Entity01去接收,所以最后返回了Entity01,听起来很绕,我跟你细细的分析一波,setField01方法
其实这里就是多态,方法定义的返回值和方法实际返回值之间的多态。
既然是这样,那我们直接强转就OK了,因为这里的对象本来就是Eentity02,只是在setField01时使用多态,子类对象指向父类变量,那我们强转回本来的Entity02运行时也不会有问题
这样虽然能解决问题,但我始终觉得不舒服,明明我new的是一个Entity02类型的对象,你setField01偏偏要搞成一个Entity02类型的变量来接收,还要让我自己来强转回来,不是多此一举吗?
通过上面的分析得知,是因为我们在使用Lombok自动生成的setField01方法时,返回了一个Entity01类型的对象,所以后面才需要我们再转一次。那我不用你自动生成的setField01方法行不,我自己实现一个setField01方法:
自己重写的setField01方法中,先调用父类的setField01方法,把值设置到field01中,然后再返回this,一个Entity02类型的对象,那么这样最开始报错的地方就不会报错了
出现这个问题,是因为@Accessors(chain = true)注解默认生成的的set方法,返回值是一个本类类型,所以Entity01中的属性field01,返回值就是Entity01,但其实我就是想用一个子类的对象链式set一个从父类中继承下来的属性而已。
问题的解决办法有两种,一种是强制转型,另一种是在子类中重写从父类继承下来的set方法。我觉得第二种才是解决问题的最终办法,这是一个Lombok的缺陷,Lombok应该在检测到父类实体和子类同时存在@Accessors(chain = true)注解时,在子类中就重写父类中的set方法,像我那样子重写。而不是无动于衷,让使用者手动来转或是手动重写set方法。这样会使Lombok更加智能一点,使用起来更加顺手。
本人水平有限,有问题还请指出。其实我还想,看看Lombok是怎么实现的动态修改了class文件的,如果可以找到逻辑,那我就修改一下Lombok,二次开发,补上刚刚那儿。但是我点开Lombok的源码,发现除了定义了一些注解外,其他都是一些看不懂的文件。
不过我了解到了一些动态修改字节码的技术,例如:javassist,有空研究一些