代码,就是我们身为程序员的名片。
简洁,优雅,统一,是我们的追求。
优秀的代码,会给浏览者一种艺术的美感。如DL大神的JUC包,感兴趣的小伙伴,可以研究一下。
那么日常中,各位看到的优秀代码,有着哪些特点呢?充分利用的工具类(lang3,lombok,Validation等等),完善的注解,统一的代码规范等等。还有的,就是Java语言的诸多高级特性(lambda,stream,io等)。
Java语言中,有三个特性,是高级工程师不可或缺的:
如果代码中,存在这些东西,那么即使应用得还不够合理,也能够从侧面证明这位程序员的技术追求。
这三点是初级工程师很难掌握的,因为缺乏了解与需求(或者说想不到对应的需求)。而高级工程师为了给出更加具有通用性,业务无侵入的代码,就常常需要与这些特性打交道。
在不断积累后的今天,我觉得我可以尝试写一写自己对这些特性的认识了。
今天就从注解开始,阐述我对高级工程师的一些编码认识。
我发现很多小伙伴总是在喜欢记忆一些注解的功能,比如表示非空的@NotNull等。
这里,我要从功能与原理角度说明两点:
只要大家抓住这两个角度去认识注解,那么很快就可以成为注解达人。后续很多阐述都会从这两个角度,去为大家解释。如为什么人们常说注解是无法继承的,为什么需要元注解等等。
其实可以看到,JDK中有关注解的内容很少,非常适合作为三大特性的入门啊。因为注解的实现基础是存在于JVM中的,JDK只是提供了对应的工具。
上面提到注解的底层是接口,这里以图为证。
注意,仔细看这个接口的注释。注释中明确提出,虽然注解的本质是接口。但是直接引用Annotation接口,是无法实现注解功能的。
通俗来说,元注解就是注解的注解。
首先元注解,是Java自带的预置注解。从这个角度,需要与@XXX修饰的自定义注解进行区分。
站在功能上来说,元注解就是专门修饰注解的“注释”,用来告诉编译器,虚拟机,相关的信息(如运行时间,目标对象)。
站在原理上来说,元注解也是注解,也是使用了@interface(底层依旧是继承Annotation接口)。
不过注解,在底层实现已经继承Annotation接口,那么就无法通过继承接口的方式(Java不支持多重继承),来保存元注解的信息(尤其这个信息往往不止一类)。那么注解的元注解信息是如何保存,并交给计算机的呢?答案就是通过RuntimeVisibleAnnotations进行相关信息的保存的。以下就是对DynamicPropertyVerification注解反编译的结果,重点在于反编译结果的最后一段。
Classfile /D:/IDEA_Project/IdeaProjects/learning/demo/target/classes/tech/jarry/learning/demo/common/anno/DynamicPropertyVerification.class Last modified Apr 12, 2020; size 899 bytes MD5 checksum 72657e8b89f0de070bf7085b0dd975da Compiled from "DynamicPropertyVerification.java" public interface tech.jarry.learning.demo.common.anno.DynamicPropertyVerification extends java.lang.annotation.Annotation minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool: #1 = Class #28 // tech/jarry/learning/demo/common/anno/DynamicPropertyVerification #2 = Class #29 // java/lang/Object #3 = Class #30 // java/lang/annotation/Annotation #4 = Utf8 message #5 = Utf8 ()Ljava/lang/String; #6 = Utf8 AnnotationDefault #7 = Utf8 property verification fail #8 = Utf8 groups #9 = Utf8 ()[Ljava/lang/Class; #10 = Utf8 Signature #11 = Utf8 ()[Ljava/lang/Class<*>; #12 = Utf8 payload #13 = Utf8 ()[Ljava/lang/Class<+Ljavax/validation/Payload;>; #14 = Utf8 SourceFile #15 = Utf8 DynamicPropertyVerification.java #16 = Utf8 RuntimeVisibleAnnotations #17 = Utf8 Ljava/lang/annotation/Documented; #18 = Utf8 Ljava/lang/annotation/Target; #19 = Utf8 value #20 = Utf8 Ljava/lang/annotation/ElementType; #21 = Utf8 FIELD #22 = Utf8 Ljava/lang/annotation/Retention; #23 = Utf8 Ljava/lang/annotation/RetentionPolicy; #24 = Utf8 SOURCE #25 = Utf8 Ljavax/validation/Constraint; #26 = Utf8 validatedBy #27 = Utf8 Ltech/jarry/learning/demo/common/anno/DynamicPropertyVerificationValidator; #28 = Utf8 tech/jarry/learning/demo/common/anno/DynamicPropertyVerification #29 = Utf8 java/lang/Object #30 = Utf8 java/lang/annotation/Annotation { public abstract java.lang.String message(); descriptor: ()Ljava/lang/String; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: s#7 public abstract java.lang.Class<?>[] groups(); descriptor: ()[Ljava/lang/Class; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: []Signature: #11 // ()[Ljava/lang/Class<*>; public abstract java.lang.Class<? extends javax.validation.Payload>[] payload(); descriptor: ()[Ljava/lang/Class; flags: ACC_PUBLIC, ACC_ABSTRACT AnnotationDefault: default_value: []Signature: #13 // ()[Ljava/lang/Class<+Ljavax/validation/Payload;>; } SourceFile: "DynamicPropertyVerification.java" RuntimeVisibleAnnotations: 0: #17() 1: #18(#19=[e#20.#21]) 2: #22(#19=e#23.#24) 3: #25(#26=[c#27])
最后一段,通过RuntimeVisibleAnnotations,保存了所需要的元注解信息。
如果对JVM底层原理有了解的小伙伴,应该对RuntimeVisibleAnnotations不陌生。不了解的小伙伴,可以查看 Class RuntimeVisibleAnnotations
元注解是Java自带的,主要分为:
源码:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); }
通过RetentionPolicy枚举表示目标注解的保持策略。
public enum RetentionPolicy { /** * 目标注解会在编译期丢失 */ SOURCE, /** * 默认行为。虽然目标注解会通过编译,保存至.class文件中,但是JVM不会在运行时识别该注解。 */ CLASS, /** * 常用行为。目标注解会保存至.class文件中,JVM会在运行时识别,并记录该注解。所以可以通过反射获取对应的信息。 * 详见 java.lang.reflect.AnnotatedElement */ RUNTIME }
为了便于大家理解,这里再举一些例子。这里挑选一些Java自带的,不用大家再去自己写demo,增加认知负荷:
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); }
通过ElementType枚举表示目标注解的应用目标类型。
public enum ElementType { /** 类,接口(包括注解,即Annotation接口),或者枚举类型 */ TYPE, /** 属性 (包括枚举常量,枚举常量示例:Retention.SOURCE) */ FIELD, /** 方法 */ METHOD, /** 形参(形式参数) */ PARAMETER, /** 构造器 */ CONSTRUCTOR, /** 本地变量 */ LOCAL_VARIABLE, /** 注解类型 */ ANNOTATION_TYPE, /** 包 */ PACKAGE, /** * 类型参数(针对数据类型) * @since 1.8 */ TYPE_PARAMETER, /** * 类型(功能域包含PARAMETER与TYPE_PARAMETER) * @since 1.8 */ TYPE_USE }
这里不会一一举例,只会点出重点:
默认情况下,注解是不出现在 javadoc 中的。通过给目标注解加上 @Documented 元注解,能使目标注解出现在 javadoc 中。
从源码可以看出,@Documented是一个没有任何成员的标记注解。
@Repeatable注解的使用,引用一个不错的 demo 。
package com.zejian.annotationdemo; import java.lang.annotation.*;/** * Created by zejian on 2017/5/20. */ @Target({ElementType.TYPE,ElementType.FIELD,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Repeatable(FilterPaths.class) public @interface FilterPath { String value(); } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface FilterPaths { FilterPath[] value(); } @FilterPath("/web/update") @FilterPath("/web/add") @FilterPath("/web/delete")
上述代码,其实分为两个部分:
@Inherited同样是只能修饰注解的元注解,它所标注的目标注解具有继承性。
这里解释一下这个继承性,这并不是注解间的继承。而是指目标注解可以随着类的继承,而被子类继承。简单说,就是目标注解修饰的类,其后代类也会被该注解标注(可以通过getAnnotation方法获取)。
这里不再赘述,感兴趣的小伙伴,可以查看 Java 注解(Annotation) 中的相关示例。
Java预置的功能注解,主要分为:
到了这里,大家应该对注解不再陌生了。
而在日常开发中,我们常常需要自定义开发一些注解。
自定义注解分为以下步骤:
简单总结一下,本文主要描述了:
至此,Java注解的内容就基本展现了。
最后,还是强调两个方面:
希望对大家有所帮助,还有不清楚的地方,可以查看下列参考目录。
愿与诸君共进步。