谁说Java不能多继承!
从今以后,只要谁说Java不能多继承
我都会说,是的没错(秒怂)
要不你再看看标题写了啥?
没毛病啊,你说Java不能多继承,我也说Java不能多继承
这不是巧了么,没想到我们对一件事物的看法竟如此一致,看来这就是猿粪啊此继承非彼继承
那你这又是唱哪出?
直接上图!
可以看到当我们在B类上添加注解@InheritClass并指定A1.class和A2.class之后,我们的B实例就有了A1和A2的属性和方法
就好像B同时继承了A1和A2
这…难道是黑魔法?(为什么脑子里会突然冒出来巴啦啦能量?)
来人,把.class文件带上来
其实就是把A1和A2的属性和方法都复制到了B上,和继承没有半毛钱关系!
这玩意儿有啥用
说起来现在实现的功能和当初的目的还是有点出入的
众所周知,Lombok中提供了@Builder的注解来生成一个类对应的Builder
但是我想在build之前校验某些字段就不太好实现
于是我就考虑,能不能实现一个注解,只是生成对应的字段和方法(毕竟最麻烦的就是要复制一堆的属性),而build方法由我们自己来实现,类似下面的代码public class A { private String a; public A(String a) { this.a = a; } @BuilderWith(A.class) public static class Builder { //注解自动生成 a 属性和 a(String a) 方法 public A build() { if (a == null) { throw new IllegalArgumentException("a is null"); } return new A(a); } } } 复制代码
这样的话,我们不仅不用手动处理大量的属性,还可以在build之前加入额外的逻辑,不至于像Lombok的@Builder那么不灵活
然后在后面实现的过程中就发现:
可以把一个类的属性复制过来,那也可以把一个类的方法复制过来!
可以复制一个类,那也可以复制多个类!
于是就发展成了现在这样,给人一种多继承的错觉
所以说这种方式也会存在很多限制和冲突,比如相同名称但不同类型的字段,相同名称相同入参但不同返回值的方法,或是调用了super的方法等等,毕竟只是一个缝合怪
这也许就是Java不支持多继承的主要原因,不然要校验要注意的地方就太多了,一不小心就会有歧义,出问题
目前我主要能想到两种使用场景Builder
Builder本来就是我最初的目的,所以肯定要想着法儿的实现public class A { private String a; public A(String a) { this.a = a; } @InheritField(sources = A.class, flags = InheritFlag.BUILDER) public static class Builder { //注解自动生成 a 属性和 a(String a) 方法 public A build() { if (a == null) { throw new IllegalArgumentException("a is null"); } return new A(a); } } } 复制代码
这个用法和之前设想的没有太大区别,就是对应的注解有点不太一样
@InheritField可以用来复制属性,然后flags = InheritFlag.BUILDER表示同时生成属性对应的方法参数组合
另一种场景就是用来组合参数
比如我们现在有两个实体A和B@Data public class A { private String a1; private String a2; ... private String a20; } @Data public class B { private String b1; private String b2; ... private String b30; } 复制代码
之前遇到过一些类似的场景,有一些比较老的项目,要加参数但是不能改参数的结构
一般情况下,如果要一个入参接收所有的参数我们会这样写@Data public class Params extends B { private String a1; private String a2; ... private String a20; } 复制代码
新写一个类继承属性多的B,然后把A的属性复制过去
但是如果修改了A就要同时修改这个新的类
如果用我们的这个就是这样的@InheritField(sources = {A.class, B.class}, flags = {InheritFlag.GETTER, InheritFlag.SETTER}) public class Params { } 复制代码
不需要手动复制了,A和B如果有修改也会自动同步
当然这个功能也是很只因肋了,因为我想不出还有其他的用法了,哭手把手教你实现
要实现这个功能需要分别实现对应的注解处理器和IDEA插件
注解处理器用于在编译的时候根据注解生成对应的代码
IDEA插件用于在标记注解后能够有对应的提示Annotation Processor
我们先来实现注解处理器public class InheritProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { //自定义的处理流程 } @Override public Set getSupportedAnnotationTypes() { //需要扫描的注解的全限定名 return new HashSet<>(); } } 复制代码
首先我们要继承javax.annotation.processing.AbstractProcessor这个类
其中getSupportedAnnotationTypes方法中返回需要扫描的注解的全限定名
然后就可以在process方法中添加自己的逻辑了,第一个参数Set<? extends TypeElement> annotations就是扫描到的注解
我们先拿到这些注解标注的类public class InheritProcessor extends AbstractProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { for (TypeElement annotation : annotations) { //获得标注了注解的类 Set<? extends Element> targetClassElements = roundEnv.getElementsAnnotatedWith(annotation); } } } 复制代码
通过第二个参数RoundEnvironment的方法getElementsAnnotatedWith就能获得标注了注解的类
接着我们来获得这些类的语法树,获得这些类的语法树之后,我们就可以通过语法树来修改这个类了JavacElements elementUtils = (JavacElements) processingEnv.getElementUtils(); JCTree.JCClassDecl targetClassDef = (JCTree.JCClassDecl) elementUtils.getTree(targetClassElement); 复制代码
processingEnv是AbstractProcessor中自带的,直接用就行了,通过processingEnv可以获得JavacElements对象
再通过JavacElements就可以获得类的语法树JCTree.JCClassDecl
为了后面更好区分,我们把这些标注了注解的类叫做【目标类】,把注解上标记的类叫做【来源类】,我们要将【来源类】中的字段和方法复制到【目标类】中
我们只要拿到【来源类】的语法树,就可以获得对应的字段和方法然后添加到【目标类】的语法树中
先通过【目标类】获得类上的注解然后筛选出我们需要的注解,这里我的注解因为支持了@Repeatable,所以还要多解析一步//获得类上所有的注解 List<? extends AnnotationMirror> annotations = targetClassElement.getAnnotationMirrors(); //解析@Repeatable获得实际的注解 List children = (List)annotation.getElementValues().values(); 复制代码
拿到注解之后,就可以获得注解上的属性private Map getAttributes(AnnotationMirror annotation) { Map attributes = new LinkedHashMap<>(); for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : annotation.getElementValues().entrySet()) { Symbol.MethodSymbol key = (Symbol.MethodSymbol) entry.getKey(); attributes.put(key.getQualifiedName().toString(), entry.getValue().getValue()); } return attributes; } 复制代码
通过方法getElementValues就可以获得注解方法和返回值的键值对,其中键为方法,所以直接强转Symbol.MethodSymbol就行了
而对应的值是特定了类型
值的类型
值的类
类
Attribute.Class
字符串
Attribute.Constant
枚举
Attribute.Enum
还有一些我没有用到所以这里就没有列出来了
所以我们拿到的值有的时候不能直接用,比如我们现在要获得【来源类】的语法树Attribute.Class attributeClass = ... Type.ClassType sourceClass = (Type.ClassType)attribute.getValue(); JCTree.JCClassDecl sourceClassDef = (JCTree.JCClassDecl) elementUtils.getTree(sourceClass.asElement()); 复制代码
通过上述的方式我们就可以拿到注解上的【来源类】的语法树
接着我们就可以把【来源类】上的字段和方法复制到【目标类】了for (JCTree sourceDef : sourceClassDef.defs) { //如果是非静态的字段 if (InheritUtils.isNonStaticVariable(sourceDef)) { JCTree.JCVariableDecl sourceVarDef = (JCTree.JCVariableDecl) sourceDef; //Class 中未定义 if (!InheritUtils.isFieldDefined(targetClassDef, sourceVarDef)) { //添加字段 targetClassDef.defs = targetClassDef.defs.append(sourceVarDef); } } //方法类似,这里不具体展示了 } 复制代码
通过【来源类】语法树的defs属性就能获得所有的字段和方法,筛选出我们需要的字段和方法之后再通过【目标类】语法树的defs属性的append方法添加就行了
这样我们就把一个类的字段和方法复制到了另一个类上
最后一步,我们需要在resources/META-INF/services下添加一个javax.annotation.processing.Processor的文件,并在文件中添加我们实现类的全限定类名
这一步也可以使用下面的方式自动生成compileOnly "com.google.auto.service:auto-service:1.0.1" annotationProcessor "com.google.auto.service:auto-service:1.0.1" 复制代码@AutoService(Processor.class) public class InheritProcessor extends AbstractProcessor { } 复制代码
引入auto-service包后,在我们实现的InheritProcessor上标注@AutoService(Processor.class)注解就会在编译的时候自动生成对应的文件
到此我们的注解处理器就开发完成了
我们只需要用compileOnly和annotationProcessor引入我们的包就可以啦Intellij Plugin
虽然我们实现了注解处理器,但是IDEA上是不会有提示的,这就需要另外开发IDEA的插件来实现对应的功能了
推荐一下大佬写的小册《IntelliJ IDE 插件开发指南》,能够比较系统的了解IDEA的插件开发
这是我的 推广链接,如果大家真的要买的,那就顺手点我的 推广链接 买吧,嘿嘿
所以项目搭建之类的我就不啰嗦了
IDEA提供了很多接口用于扩展,这里我们要用到的就是PsiAugmentProvider这个接口public class InheritPsiAugmentProvider extends PsiAugmentProvider { @Override protected @NotNull List getAugments(@NotNull PsiElement element, @NotNull Class type) { return new ArrayList<>(); } } 复制代码
getAugments方法就是用于获得额外的元素
其中第一个参数PsiElement element就是扩展的主体,以我们当前需要实现的功能来说,如果这个参数是类并且类上标注了我们指定的注解,那么我们就需要进行处理
第二个参数是需要的类型,以我们当前需要实现的功能来说,如果这个类型是字段或方法,那么我们就需要进行处理
直接看代码会清晰一点public class InheritPsiAugmentProvider extends PsiAugmentProvider { @Override protected @NotNull List getAugments(@NotNull PsiElement element, @NotNull Class type) { //只处理类 if (element instanceof PsiClass) { if (type.isAssignableFrom(PsiField.class)) { //如果标记了注解,则返回额外的字段 } if (type.isAssignableFrom(PsiMethod.class)) { //如果标记了注解,则返回额外的方法 } } return new ArrayList<>(); } } 复制代码
也就是说扩展的字段和方法是分开来获取的,另外需要注意是额外的字段和方法,不是全部的字段和方法
接下来我们需要先获得类上的注解private Collection findAnnotations(PsiClass targetClass) { Collection annotations = new ArrayList<>(); for (PsiAnnotation annotation : targetClass.getAnnotations()) { if ("注解的全限定名".contains(annotation.getQualifiedName())) { annotations.add(annotation); } if ("@Repeatable注解的全限定名".contains(annotation.getQualifiedName())) { handleRepeatableAnnotation(annotation, annotations); } } return annotations; } /** * 获得 Repeatable 中的实际注解 */ private void handleRepeatableAnnotation(PsiAnnotation annotation, Collection annotations) { PsiAnnotationMemberValue value = annotation.findAttributeValue("value"); if (value != null) { PsiElement[] children = value.getChildren(); for (PsiElement child : children) { if (child instanceof PsiAnnotation) { annotations.add((PsiAnnotation) child); } } } } 复制代码
获得注解之后,我们就可以通过注解获得注解的属性了Collection sources = findTypes(annotation.findAttributeValue("sources")); private Collection findTypes(PsiElement element) { Collection types = new HashSet<>(); findTypes0(element, types); return types; } private void findTypes0(PsiElement element, Collection types) { if (element == null) { return; } if (element instanceof PsiTypeElement) { PsiType type = ((PsiTypeElement) element).getType(); types.add(type); } for (PsiElement child : element.getChildren()) { findTypes0(child, types); } } 复制代码
这里需要注意,Psi是文件树而不是语法树
比如这样的写法@InheritClass(sources = {A.class, B.class})
我们通过findAttributeValue("sources")得到的是{A.class, B.class},最上层是{},{}的子节点才是A.class, B.class,所以Psi体现的是文件的结构
接着我们就可以获得对应的字段和方法了PsiClass sourceClass = PsiUtil.resolveClassInType(PsiType); /** * 获得所有字段 */ public static Collection collectClassFieldsIntern(@NotNull PsiClass psiClass) { if (psiClass instanceof PsiExtensibleClass) { return new ArrayList<>(((PsiExtensibleClass) psiClass).getOwnFields()); } else { return filterPsiElements(psiClass, PsiField.class); } } /** * 获得所有方法 */ public static Collection collectClassMethodsIntern(@NotNull PsiClass psiClass) { if (psiClass instanceof PsiExtensibleClass) { return new ArrayList<>(((PsiExtensibleClass) psiClass).getOwnMethods()); } else { return filterPsiElements(psiClass, PsiMethod.class); } } private static Collection filterPsiElements(@NotNull PsiClass psiClass, @NotNull Class desiredClass) { return Arrays.stream(psiClass.getChildren()).filter(desiredClass::isInstance).map(desiredClass::cast).collect(Collectors.toList()); } 复制代码
上面这几个方法我都是从Lombok里面复制过来的,至于else分支我也看不懂,可能会有多种情况,我也没遇到过,hhh
然后我们就可以对字段和方法进行复制啦String fieldName = field.getName(); LightFieldBuilder fieldBuilder = new LightFieldBuilder( manager, fieldName, field.getType()); //访问限定 fieldBuilder.setModifierList(new LightModifierList(field)); //初始化 fieldBuilder.setInitializer(field.getInitializer()); //所属的Class fieldBuilder.setContainingClass(targetClass); //是否 Deprecated fieldBuilder.setIsDeprecated(field.isDeprecated()); //注释 fieldBuilder.setDocComment(field.getDocComment()); //导航 fieldBuilder.setNavigationElement(field); 复制代码LightMethodBuilder methodBuilder = new LightMethodBuilder( manager, JavaLanguage.INSTANCE, method.getName(), method.getParameterList(), method.getModifierList(), method.getThrowsList(), method.getTypeParameterList()); //返回值 methodBuilder.setMethodReturnType(method.getReturnType()); //所属的 Class methodBuilder.setContainingClass(targetClass); //导航 methodBuilder.setNavigationElement(method); 复制代码
这里大家一定要新实例化所有的字段和方法,不要直接返回【来源类】的字段和方法,因为【来源类】的字段和方法是和【来源类】关联的,而我们返回的是【目标类】的字段和方法,两者不匹配会导致IDEA直接报错
最后我们只需要在plugin.xml中添加这个扩展就行了 复制代码
最后的最后,需要发布一下插件或是本地集成结束
附一张插件图
如果大家有兴趣可以看看这个库,还有很多其他的功能
链接:https://juejin.cn/post/7202272345834094652
拉着地球太阳一起转杂谈拉格朗日点想必各位不久前都被一条新闻刷屏了詹姆斯韦伯空间望远镜成功于2021年12月25日发射升空,并于2022年1月24日,成功抵达最终目的地距离地球约150万千米的日地系统拉格朗日L2点
3月天象剧场将启幕,金木相合不容错过北京日报客户端记者牛伟坤3月天象剧场即将启幕。北京天文馆专家介绍,3月2日的金星合木星推荐观测,将于3月24日上演的月掩金星也不容错过。3月2日19时左右,金星与木星上演亲密相拥,
2。5亿年前的化石吐露植物爱睡觉的秘密新华社讯我国古生物学家从距今2。5亿年的化石中发现了植物爱睡觉的秘密。该成果于北京时间2月16日在线发表在国际知名期刊当代生物学上。在自然界,有些植物拥有一种类似睡觉的现象它们的叶
SpaceX的载人飞船取消了最后一刻的发射在检测到火箭出现问题后,美国SpaceX和美国宇航局今天取消了向国际空间站(ISS)运送宇航员的航天器发射任务。2月27日,龙飞船位于佛罗里达州肯尼迪航天中心的SpaceX猎鹰9号
记者试乘雄安智能网联巴士将纳入城市公交系统图为航拍智能网联巴士。韩冰摄中新网雄安2月28日电(记者崔涛)能够自动驾驶的智能网联巴士正在河北雄安新区容东片区进行道路测试,即将纳入该片区的城市公交系统。近日,在雄安的智能网联巴
信义光能(00968。HK)年度纯利下降22。4至38。2亿港元末期息10港仙信义光能(00968。HK)年度纯利下降22。4至38。2亿港元末期息10港仙财联社2月27日电,信义光能(00968。HK)发布公告,截至2022年12月31日止年度,实现收益2
雷吉有预感自己可能会被快船交易北京时间2月28日,掘金球员雷吉杰克逊近期接受了TA记者采访。雷吉在采访中表示,自己有预感可能会被快船交易。我是球队唯一即将到期的合同而你仅有的年轻资产是特伦斯曼恩,他接替了首发控
国产手机的技术优势,有哪些方面?1。制造成本国产手机在制造过程中使用的劳动力和原材料都比其他国家便宜,所以国产手机制造商在价格上具有明显的优势,可以提供价格相对较低的手机,来吸引消费者。2。品牌营销国产手机制造商
七百三十二章讲解先手与反先借助五六炮反攻马体系第七百三十二章讲解先手与反先借助五六炮反攻马体系1。炮二平五马进2。马二进三炮平3。车一平二马进4。兵三进一卒进5。马八进九象进6。炮八平六车平图(732。1)7。红方出车后形成上
TA切尔西仍支持波特,认为对他的任命是着眼长期的直播吧2月28日讯据TheAthletic网报道,切尔西还没有打算解雇主教练波特,并且会继续给他时间去扭转局面。波特执教切尔西最近的15场英超比赛,只取得了两场胜利,因此面临的压力
我国多措并举提高罕见病诊疗能力2023。02。28要闻热点我国多措并举提高罕见病诊疗能力2月28日是第16个国际罕见病日,目前,全世界约有7000多种罕见病,我国罕见病患者约有2000万人。近年来,有关部门大力
春到山水间作者刘成章小时候常听人说二月二,龙抬头。抬头做甚?不得而知。今天的孩子们,恐怕更是难以明白。那么,我打个比方吧。飞机一旦抬头,就是要起飞了。龙也是这样,抬起头就要一冲上天。龙行天上
山海泉港,邂逅峰尾山海泉港,邂逅峰尾来过峰尾古城,那也已经是年前的事了,不曾想过此次又能以另一种方式去认识他,此次受泉港网信办邀请峰尾采风活动,才能更近一步了解泉港,了解峰尾。古城古港福船福地,这里
外摆经济阔步走来促消费扬帆远航殷建光树荫下的咖啡外摆区夜晚路边接地气的小吃和大排档万物复苏的季节里,各地线下消费也迎来了春天,而在人群熙攘的商圈中,商业外摆正在成为一道独特的风景。商业外摆并非简单的摆地摊,而是
华为很大方,骁龙6805000mAh,降至1519元让人喜欢作为国产手机的老大哥,华为手机的一举一动备受全球媒体和用户的关注。为了给即将到来的华为P60系列全面让路,华为对老旗舰机型的价格做出了大幅度的调整。其中,华为畅享50Pro就是最好
夜读做到这三点,幸福会如约而至不必焦虑心急,也无需迷茫犹豫。冬去春来,雪融花开。所有的美好,如约而至。在春天,拂去心尘,迎接新的自己,做一个幸福的人。在耕耘中成长万物因时光流转而生生不息。人生也在耕耘与成长中,
一句下辈子不来了,瞬间破防做最闲那条鱼第871篇头条原创文章。未经授权请勿进行任何形式的转载前几天一个9秒钟的小视频刷爆全网。一个戴着口罩的小姐姐眼里闪着泪花,配文只有简单一句下辈子不来了。短短6个字,评论
泰国帝后夫妇见大使!苏提达穿复古粉裙把人惊艳,越来越有少女感泰国苏提达王后在着装方面确实有了很大的进步,近日,泰国国王和王后一起接见了大使,在这样的外交场合,苏提达正好可以将自己优越的英语水平展示一下,还真是很给泰王撑场面呢!泰王的大女儿帕
多省份试点现房销售,预售制要退出了?中国经济周刊记者王红茹全国两会报道2022年被一些网友戏称之为房地产业的至暗时刻。这一年,中国房地产市场出现了罕见的量价齐跌,部分房地产企业现金流断裂,甚至申请破产。表现在数据上,
零件数是复兴号高铁的13倍,这艘邮轮建造难度有多大?我国在建的第一艘大型邮轮预计今年底前交付,国产大型邮轮在中国船舶集团也被称作一号工程。这艘船的建造难度到底有多大?我们来了解一下。在有限空间实现智能安全等设计总台央视记者李宁为了建
讲好中国经济高质量发展故事来源经济日报习近平经济思想是习近平新时代中国特色社会主义思想的重要组成部分,是我国经济高质量发展全面建设社会主义现代化国家的科学指南。经济日报社聚焦习近平经济思想的研究与实践,连续
以色列商界人士期待继续扩大在华业务据新华社耶路撒冷电(记者王卓伦陈君清)在以色列经济中心特拉维夫市日前举办的中国浙江以色列投资贸易合作交流会上,多位以色列商界人士表示看好中国经济发展前景和广阔市场,期待继续扩大在华