本文内容基于 JDK8。注解是 JDK5 引入的,后续 JDK 版本扩展了一些内容,本文中没有明确指明版本的注解都是 JDK5 就已经支持的注解。

:notebook: 本文已归档到:「blog」

:keyboard: 本文中的示例代码已归档到:「javacore」

简介

注解的形式

Java 中,注解是以 @ 字符开始的修饰符。如下:

@Override
void mySuperMethod() { ... }
复制代码

注解可以包含命名或未命名的属性,并且这些属性有值。

@Author(name = "Benjamin Franklin",date = "3/27/2003"
)
class MyClass() { ... }
复制代码

如果只有一个名为 value 的属性,那么名称可以省略,如:

@SuppressWarnings("unchecked")
void myMethod() { ... }
复制代码

如果注解没有属性,则称为标记注解。如:@Override

什么是注解

从本质上来说,注解是一种标签,其实质上可以视为一种特殊的注释,如果没有解析它的代码,它并不比普通注释强。

解析一个注解往往有两种形式:

  • 编译期直接的扫描 - 编译器的扫描指的是编译器在对 java 代码编译字节码的过程中会检测到某个类或者方法被一些注解修饰,这时它就会对于这些注解进行某些处理。这种情况只适用于 JDK 内置的注解类。
  • 运行期的反射 - 如果要自定义注解,Java 编译器无法识别并处理这个注解,它只能根据该注解的作用范围来选择是否编译进字节码文件。如果要处理注解,必须利用反射技术,识别该注解以及它所携带的信息,然后做相应的处理。

注解的作用

注解有许多用途:

  • 编译器信息 - 编译器可以使用注解来检测错误或抑制警告。
  • 编译时和部署时的处理 - 程序可以处理注解信息以生成代码,XML 文件等。
  • 运行时处理 - 可以在运行时检查某些注解并处理。

作为 Java 程序员,多多少少都曾经历过被各种配置文件(xml、properties)支配的恐惧。过多的配置文件会使得项目难以维护。个人认为,使用注解以减少配置文件或代码,是注解最大的用处。

注解的代价

凡事有得必有失,注解技术同样如此。使用注解也有一定的代价:

  • 显然,它是一种侵入式编程,那么,自然就存在着增加程序耦合度的问题。
  • 自定义注解的处理需要在运行时,通过反射技术来获取属性。如果注解所修饰的元素是类的非 public 成员,也可以通过反射获取。这就违背了面向对象的封装性。
  • 注解所产生的问题,相对而言,更难以 debug 或定位。

但是,正所谓瑕不掩瑜,注解所付出的代价,相较于它提供的功能而言,还是可以接受的。

注解的应用范围

注解可以应用于类、字段、方法和其他程序元素的声明。

JDK8 开始,注解的应用范围进一步扩大,以下是新的应用范围:

类实例初始化表达式:

new @Interned MyObject();
复制代码

类型转换:

myString = (@NonNull String) str;
复制代码

实现接口的声明:

class UnmodifiableList<T> implements@Readonly List<@Readonly T> {}
复制代码

抛出异常声明:

void monitorTemperature()throws @Critical TemperatureException {}
复制代码

内置注解

JDK 中内置了以下注解:

  • @Override
  • @Deprecated
  • @SuppressWarnnings
  • @SafeVarargs(JDK7 引入)
  • @FunctionalInterface(JDK8 引入)

@Override

@Override 用于表明被修饰方法覆写了父类的方法。

如果试图使用 @Override 标记一个实际上并没有覆写父类的方法时,java 编译器会告警。

@Override 示例:

public class OverrideAnnotationDemo {static class Person {public String getName() {return "getName";}}static class Man extends Person {@Overridepublic String getName() {return "override getName";}/***  放开下面的注释,编译时会告警*//*@Overridepublic String getName2() {return "override getName2";}*/}public static void main(String[] args) {Person per = new Man();System.out.println(per.getName());}
}
复制代码

@Deprecated

@Deprecated 用于标明被修饰的类或类成员、类方法已经废弃、过时,不建议使用。

@Deprecated 有一定的延续性:如果我们在代码中通过继承或者覆盖的方式使用了过时的类或类成员,即使子类或子方法没有标记为 @Deprecated,但编译器仍然会告警。

注意: @Deprecated 这个注解类型和 javadoc 中的 @deprecated 这个 tag 是有区别的:前者是 java 编译器识别的;而后者是被 javadoc 工具所识别用来生成文档(包含程序成员为什么已经过时、它应当如何被禁止或者替代的描述)。

@Deprecated 示例:

public class DeprecatedAnnotationDemo {static class DeprecatedField {@Deprecatedpublic static final String DEPRECATED_FIELD = "DeprecatedField";}static class DeprecatedMethod {@Deprecatedpublic String print() {return "DeprecatedMethod";}}@Deprecatedstatic class DeprecatedClass {public String print() {return "DeprecatedClass";}}public static void main(String[] args) {System.out.println(DeprecatedField.DEPRECATED_FIELD);DeprecatedMethod dm = new DeprecatedMethod();System.out.println(dm.print());DeprecatedClass dc = new DeprecatedClass();System.out.println(dc.print());}
}
//Output:
//DeprecatedField
//DeprecatedMethod
//DeprecatedClass
复制代码

@SuppressWarnnings

@SuppressWarnings 用于关闭对类、方法、成员编译时产生的特定警告。

@SuppressWarning 不是一个标记注解。它有一个类型为 String[] 的数组成员,这个数组中存储的是要关闭的告警类型。对于 javac 编译器来讲,对 -Xlint 选项有效的警告名也同样对 @SuppressWarings 有效,同时编译器会忽略掉无法识别的警告名。

@SuppressWarning 示例:

@SuppressWarnings({"rawtypes", "unchecked"})
public class SuppressWarningsAnnotationDemo {static class SuppressDemo<T> {private T value;public T getValue() {return this.value;}public void setValue(T var) {this.value = var;}}@SuppressWarnings({"deprecation"})public static void main(String[] args) {SuppressDemo d = new SuppressDemo();d.setValue("南京");System.out.println("地名:" + d.getValue());}
}
复制代码

@SuppressWarnings 注解的常见参数值的简单说明:

  • deprecation - 使用了不赞成使用的类或方法时的警告;
  • unchecked - 执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
  • fallthrough - 当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
  • path - 在类路径、源文件路径等中有不存在的路径时的警告;
  • serial - 当在可序列化的类上缺少 serialVersionUID 定义时的警告;
  • finally - 任何 finally 子句不能正常完成时的警告;
  • all - 所有的警告。
@SuppressWarnings({"uncheck", "deprecation"})
public class InternalAnnotationDemo {/*** @SuppressWarnings 标记消除当前类的告警信息*/@SuppressWarnings({"deprecation"})static class A {public void method1() {System.out.println("call method1");}/*** @Deprecated 标记当前方法为废弃方法,不建议使用*/@Deprecatedpublic void method2() {System.out.println("call method2");}}/*** @Deprecated 标记当前类为废弃类,不建议使用*/@Deprecatedstatic class B extends A {/*** @Override 标记显示指明当前方法覆写了父类或接口的方法*/@Overridepublic void method1() { }}public static void main(String[] args) {A obj = new B();obj.method1();obj.method2();}
}
复制代码

@SafeVarargs

@SafeVarargs 在 JDK7 中引入。

@SafeVarargs 的作用是:告诉编译器,在可变长参数中的泛型是类型安全的。可变长参数是使用数组存储的,而数组和泛型不能很好的混合使用。

简单的说,数组元素的数据类型在编译和运行时都是确定的,而泛型的数据类型只有在运行时才能确定下来。因此,当把一个泛型存储到数组中时,编译器在编译阶段无法确认数据类型是否匹配,因此会给出警告信息;即如果泛型的真实数据类型无法和参数数组的类型匹配,会导致 ClassCastException 异常。

@SafeVarargs 注解使用范围:

  • @SafeVarargs 注解可以用于构造方法。
  • @SafeVarargs 注解可以用于 staticfinal 方法。

@SafeVarargs 示例:

public class SafeVarargsAnnotationDemo {/*** 此方法实际上并不安全,不使用此注解,编译时会告警*/@SafeVarargsstatic void wrongMethod(List<String>... stringLists) {Object[] array = stringLists;List<Integer> tmpList = Arrays.asList(42);array[0] = tmpList; // 语法错误,但是编译不告警String s = stringLists[0].get(0); // 运行时报 ClassCastException}public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("A");list.add("B");List<String> list2 = new ArrayList<>();list.add("1");list.add("2");wrongMethod(list, list2);}
}
复制代码

以上代码,如果不使用 @SafeVarargs ,编译时会告警

[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 某些输入文件使用了未经检查或不安全的操作。
[WARNING] /D:/Codes/ZP/Java/javacore/codes/basics/src/main/java/io/github/dunwu/javacore/annotation/SafeVarargsAnnotationDemo.java: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。
复制代码

@FunctionalInterface

@FunctionalInterface 在 JDK8 引入。

@FunctionalInterface 用于指示被修饰的接口是函数式接口。

需要注意的是,如果一个接口符合"函数式接口"定义,不加 @FunctionalInterface 也没关系;但如果编写的不是函数式接口,却使用 @FunctionInterface,那么编译器会报错。

什么是函数式接口?

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。

函数式接口的特点:

  • 接口有且只能有个一个抽象方法(抽象方法只有方法定义,没有方法体)。
  • 不能在接口中覆写 Object 类中的 public 方法(写了编译器也会报错)。
  • 允许有 default 实现方法。

示例:

public class FunctionalInterfaceAnnotationDemo {@FunctionalInterfacepublic interface Func1<T> {void printMessage(T message);}/*** @FunctionalInterface 修饰的接口中定义两个抽象方法,编译时会报错* @param <T>*//*@FunctionalInterfacepublic interface Func2<T> {void printMessage(T message);void printMessage2(T message);}*/public static void main(String[] args) {Func1 func1 = message -> System.out.println(message);func1.printMessage("Hello");func1.printMessage(100);}
}
复制代码

元注解

JDK 中虽然内置了几个注解,但这远远不能满足开发过程中遇到的千变万化的需求。所以我们需要自定义注解,而这就需要用到元注解。

元注解的作用就是用于定义其它的注解

Java 中提供了以下元注解类型:

  • @Retention
  • @Target
  • @Documented
  • @Inherited(JDK8 引入)
  • @Repeatable(JDK8 引入)

这些类型和它们所支持的类在 java.lang.annotation 包中可以找到。下面我们看一下每个元注解的作用和相应分参数的使用说明。

@Retention

@Retention 指明了注解的保留级别。

@Retention 源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {RetentionPolicy value();
}
复制代码

RetentionPolicy 是一个枚举类型,它定义了被 @Retention 修饰的注解所支持的保留级别:

  • RetentionPolicy.SOURCE - 标记的注解仅在源文件中有效,编译器会忽略。
  • RetentionPolicy.CLASS - 标记的注解在 class 文件中有效,JVM 会忽略。
  • RetentionPolicy.RUNTIME - 标记的注解在运行时有效。

@Retention 示例:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {public String name() default "fieldName";public String setFuncName() default "setField";public String getFuncName() default "getField";public boolean defaultDBValue() default false;
}
复制代码

@Documented

@Documented 表示无论何时使用指定的注解,都应使用 Javadoc(默认情况下,注释不包含在 Javadoc 中)。更多内容可以参考:Javadoc tools page。

@Documented 示例:

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {public String name() default "fieldName";public String setFuncName() default "setField";public String getFuncName() default "getField";public boolean defaultDBValue() default false;
}
复制代码

@Target

@Target 指定注解可以修饰的元素类型。

@Target 源码:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {ElementType[] value();
}
复制代码

ElementType 是一个枚举类型,它定义了被 @Target 修饰的注解可以应用的范围:

  • ElementType.ANNOTATION_TYPE - 标记的注解可以应用于注解类型。
  • ElementType.CONSTRUCTOR - 标记的注解可以应用于构造函数。
  • ElementType.FIELD - 标记的注解可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE - 标记的注解可以应用于局部变量。
  • ElementType.METHOD - 标记的注解可以应用于方法。
  • ElementType.PACKAGE - 标记的注解可以应用于包声明。
  • ElementType.PARAMETER - 标记的注解可以应用于方法的参数。
  • ElementType.TYPE - 标记的注解可以应用于类的任何元素。

@Target 示例:

@Target(ElementType.TYPE)
public @interface Table {/*** 数据表名称注解,默认值为类名称* @return*/public String tableName() default "className";
}@Target(ElementType.FIELD)
public @interface NoDBColumn {}
复制代码

@Inherited

@Inherited 表示注解类型可以被继承(默认情况下不是这样)

表示自动继承注解类型。 如果注解类型声明中存在 @Inherited 元注解,则注解所修饰类的所有子类都将会继承此注解。

注意:@Inherited 注解类型是被标注过的类的子类所继承。类并不从它所实现的接口继承注解,方法并不从它所覆写的方法继承注解。

此外,当 @Inherited 类型标注的注解的 @RetentionRetentionPolicy.RUNTIME,则反射 API 增强了这种继承性。如果我们使用 java.lang.reflect 去查询一个 @Inherited 类型的注解时,反射代码检查将展开工作:检查类和其父类,直到发现指定的注解类型被发现,或者到达类继承结构的顶层。

@Inherited
public @interface Greeting {public enum FontColor{ BULE,RED,GREEN};String name();FontColor fontColor() default FontColor.GREEN;
}
复制代码

@Repeatable

@Repeatable 表示注解可以重复使用。

以 Spring @Scheduled 为例:

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Schedules {Scheduled[] value();
}@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {// ...
}
复制代码

应用示例:

public class TaskRunner {@Scheduled("0 0/15 * * * ?")@Scheduled("0 0 12 * ?")public void task1() {}
}
复制代码

自定义注解

使用 @interface 自定义注解时,自动继承了 java.lang.annotation.Annotation 接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface 用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过 default 来声明参数的默认值。

这里,我会通过实现一个名为 RegexValid 的正则校验注解工具来展示自定义注解的全步骤。

1. 注解的定义

注解的语法格式如下:

public @interface 注解名 {定义体}
复制代码

我们来定义一个注解:

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {}
复制代码

说明:

通过上一节对于元注解 @Target@Retention@Documented 的说明,这里就很容易理解了。

  • 上面的代码中定义了一个名为 @RegexValid 的注解。
  • @Documented 表示 @RegexValid 应该使用 javadoc。
  • @Target({ElementType.FIELD, ElementType.PARAMETER}) 表示 @RegexValid 可以在类成员或方法参数上修饰。
  • @Retention(RetentionPolicy.RUNTIME) 表示 @RegexValid 在运行时有效。

此时,我们已经定义了一个没有任何属性的注解,如果到此为止,它仅仅是一个标记注解。作为正则工具,没有属性可什么也做不了。接下来,我们将为它添加注解属性。

2. 注解属性

注解属性的语法形式如下:

[访问级别修饰符] [数据类型] 名称() default 默认值;
复制代码

例如,我们要定义在注解中定义一个名为 value 的字符串属性,其默认值为空字符串,访问级别为默认级别,那么应该定义如下:

String value() default "";
复制代码

注意:在注解中,我们定义属性时,属性名后面需要加 ()

定义注解属性有以下要点:

  • 注解属性只能使用 public 或默认访问级别(即不指定访问级别修饰符)修饰

  • 注解属性的数据类型有限制要求。支持的数据类型如下:

    • 所有基本数据类型(byte、char、short、int、long、float、double、boolean)
    • String 类型
    • Class 类
    • enum 类型
    • Annotation 类型
    • 以上所有类型的数组
  • 注解属性必须有确定的值,建议指定默认值。注解属性只能通过指定默认值或使用注解时指定属性值,相较之下,指定默认值的方式更为可靠。注解属性如果是引用类型,不可以为 null。这个约束使得注解处理器很难判断注解属性是默认值,或是使用注解时所指定的属性值。为此,我们设置默认值时,一般会定义一些特殊的值,例如空字符串或者负数。

  • 如果注解中只有一个属性值,最好将其命名为 value。因为,指定属性名为 value,在使用注解时,指定 value 的值可以不指定属性名称。

// 这两种方式效果相同
@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
@RegexValid(value = "^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")
复制代码

示例:

了解了注解属性的定义要点,让我们来为 @RegexValid 注解定义几个属性。

@Documented
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface RegexValid {enum Policy {// @formatter:offEMPTY(null),DATE("^(?:(?!0000)[0-9]{4}([-/.]?)(?:(?:0?[1-9]|1[0-2])\\1(?:0?[1-9]|1[0-9]|2[0-8])|(?:0?[13-9]|1[0-2])\\1"+ "(?:29|30)|(?:0?[13578]|1[02])\\1(?:31))|(?:[0-9]{2}(?:0[48]|[2468][048]|[13579][26])|"+ "(?:0[48]|[2468][048]|[13579][26])00)([-/.]?)0?2\\2(?:29))$"),MAIL("^[A-Za-z0-9](([_\\.\\-]?[a-zA-Z0-9]+)*)@([A-Za-z0-9]+)(([\\.\\-]?[a-zA-Z0-9]+)*)\\.([A-Za-z]{2,})$");// @formatter:onprivate String policy;Policy(String policy) {this.policy = policy;}public String getPolicy() {return policy;}}String value() default "";Policy policy() default Policy.EMPTY;
}
复制代码

说明:

在上面的示例代码中,我们定义了两个注解属性:String 类型的 value 属性和 Policy 枚举类型的 policy 属性。Policy 枚举中定义了几个默认的正则表达式,这是为了直接使用这几个常用表达式去正则校验。考虑到,我们可能需要自己传入一些自定义正则表达式去校验其他场景,所以定义了 value 属性,允许使用者传入正则表达式。

至此,@RegexValid 的声明已经结束。但是,程序仍不知道如何处理 @RegexValid 这个注解。我们还需要定义注解处理器。

3. 注解处理器

如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了。使用注解的过程中,很重要的一部分就是创建于使用注解处理器。JDK5 扩展了反射机制的 API,以帮助程序员快速的构造自定义注解处理器。

java.lang.annotation.Annotation 是一个接口,程序可以通过反射来获取指定程序元素的注解对象,然后通过注解对象来获取注解里面的元数据

Annotation 接口源码如下:

public interface Annotation {boolean equals(Object obj);int hashCode();String toString();Class<? extends Annotation> annotationType();
}
复制代码

除此之外,Java 中支持注解处理器接口 java.lang.reflect.AnnotatedElement ,该接口代表程序中可以接受注解的程序元素,该接口主要有如下几个实现类:

  • Class - 类定义
  • Constructor - 构造器定义
  • Field - 累的成员变量定义
  • Method - 类的方法定义
  • Package - 类的包定义

java.lang.reflect 包下主要包含一些实现反射功能的工具类。实际上,java.lang.reflect 包所有提供的反射 API 扩充了读取运行时注解信息的能力。当一个注解类型被定义为运行时的注解后,该注解才能是运行时可见,当 class 文件被装载时被保存在 class 文件中的注解才会被虚拟机读取。 AnnotatedElement 接口是所有程序元素(Class、Method 和 Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement 对象之后,程序就可以调用该对象的如下四个个方法来访问注解信息:

  • getAnnotation - 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回 null。
  • getAnnotations - 返回该程序元素上存在的所有注解。
  • isAnnotationPresent - 判断该程序元素上是否包含指定类型的注解,存在则返回 true,否则返回 false。
  • getDeclaredAnnotations - 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

了解了以上内容,让我们来实现 @RegexValid 的注解处理器:

import java.lang.reflect.Field;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class RegexValidUtil {public static boolean check(Object obj) throws Exception {boolean result = true;StringBuilder sb = new StringBuilder();Field[] fields = obj.getClass().getDeclaredFields();for (Field field : fields) {// 判断成员是否被 @RegexValid 注解所修饰if (field.isAnnotationPresent(RegexValid.class)) {RegexValid valid = field.getAnnotation(RegexValid.class);// 如果 value 为空字符串,说明没有注入自定义正则表达式,改用 policy 属性String value = valid.value();if ("".equals(value)) {RegexValid.Policy policy = valid.policy();value = policy.getPolicy();}// 通过设置 setAccessible(true) 来访问私有成员field.setAccessible(true);Object fieldObj = null;try {fieldObj = field.get(obj);} catch (IllegalAccessException e) {e.printStackTrace();}if (fieldObj == null) {sb.append("\n").append(String.format("%s 类中的 %s 字段不能为空!", obj.getClass().getName(), field.getName()));result = false;} else {if (fieldObj instanceof String) {String text = (String) fieldObj;Pattern p = Pattern.compile(value);Matcher m = p.matcher(text);result = m.matches();if (!result) {sb.append("\n").append(String.format("%s 不是合法的 %s !", text, field.getName()));}} else {sb.append("\n").append(String.format("%s 类中的 %s 字段不是字符串类型,不能使用此注解校验!", obj.getClass().getName(), field.getName()));result = false;}}}}if (sb.length() > 0) {throw new Exception(sb.toString());}return result;}
}
复制代码

说明:

以上示例中的注解处理器,执行步骤如下:

  1. 通过 getDeclaredFields 反射方法获取传入对象的所有成员。
  2. 遍历成员,使用 isAnnotationPresent 判断成员是否被指定注解所修饰,如果不是,直接跳过。
  3. 如果成员被注解所修饰,通过 RegexValid valid = field.getAnnotation(RegexValid.class); 这样的形式获取,注解实例化对象,然后,就可以使用 valid.value()valid.policy() 这样的形式获取注解中设定的属性值。
  4. 根据属性值,进行逻辑处理。

4. 使用注解

完成了以上工作,我们就可以使用自定义注解了,示例如下:

public class RegexValidDemo {static class User {private String name;@RegexValid(policy = RegexValid.Policy.DATE)private String date;@RegexValid(policy = RegexValid.Policy.MAIL)private String mail;@RegexValid("^((\\+)?86\\s*)?((13[0-9])|(15([0-3]|[5-9]))|(18[0,2,5-9]))\\d{8}$")private String phone;public User(String name, String date, String mail, String phone) {this.name = name;this.date = date;this.mail = mail;this.phone = phone;}@Overridepublic String toString() {return "User{" + "name='" + name + '\'' + ", date='" + date + '\'' + ", mail='" + mail + '\'' + ", phone='"+ phone + '\'' + '}';}}static void printDate(@RegexValid(policy = RegexValid.Policy.DATE) String date){System.out.println(date);}public static void main(String[] args) throws Exception {User user = new User("Tom", "1990-01-31", "xxx@163.com", "18612341234");User user2 = new User("Jack", "2019-02-29", "sadhgs", "183xxxxxxxx");if (RegexValidUtil.check(user)) {System.out.println(user + "正则校验通过");}if (RegexValidUtil.check(user2)) {System.out.println(user2 + "正则校验通过");}}
}
复制代码

小结

参考资料

  • Java 编程思想
  • JAVA 核心技术(卷 1)
  • Effective java
  • Oracle 官方文档之注解篇
  • 深入理解 Java:注解(Annotation)自定义注解入门
  • blog.csdn.net/briblue/art…

转载于:https://juejin.im/post/5ca755676fb9a05e4868c973

深入理解 Java 注解相关推荐

  1. java 注解_怎样理解 Java 注解和运用注解编程?

    怎样理解 Java 注解和运用注解编程? 注解和使用 先来看下概念首先从注释来看: 注释:给代码添加说明和解释,注释帮助开发人员理解程序.(Comment)说白点就是注释是给人看的. 注解:给代码添加 ...

  2. 深圳java培训:怎样理解 Java 注解和运用注解编程?

    深圳java培训:怎样理解 Java 注解和运用注解编程? 注解和使用 先来看下概念首先从注释来看: 注释:给代码添加说明和解释,注释帮助开发人员理解程序.(Comment)说白点就是注释是给人看的. ...

  3. 深入理解Java注解类型(@Annotation)

    [版权申明]未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/71860633 出自[zejian ...

  4. 深入理解Java注解Annotation之注解处理器

    如果没有用来读取注解的方法和工作,那么注解也就不会比注释更有用处了.使用注解的过程中,很重要的一部分就是创建于使用注解处理器.Java SE5扩展了反射机制的API,以帮助程序员快速的构造自定义注解处 ...

  5. java注释和注解_深入理解JAVA注解(Annotation)以及自定义注解

    Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制.Java 语言中的类.方法.变量.参数和包等都可以被标注.注解可以看作是一种特殊的标记,在程序在编译或 ...

  6. 理解java注解的实现原理

    JAVA 注解的基本原理(重点) https://www.cnblogs.com/yangming1996/p/9295168.html 从以下4个方面来系统的学习一下java注解 什么是注解 注解的 ...

  7. java注释的理解,java注解原理——记录一下自己的理解

    最近因为系统可能要更换成java语言,于是每天都在拼命的研究java的相关知识和框架.之前学习注解的时候,没有太深入的去理解它,只是觉得标注一下挺好用,但是现在在学到spring aop的时候,突然发 ...

  8. 一篇文章通俗易懂的让你彻底理解 Java 注解

    很多Java程序员,对Java的注解一知半解,更有甚者,有的人可能连注解是什么都不知道 本文我们用最简单的 demo , 最通俗最短的语言,带你了解注解到底是什么? 先来简单回顾一下基础,我们知道,J ...

  9. 简述java中的注释以及用法_怎样理解 Java 注解和运用注解编程?

    正好最近在公众号(BetterAndroid)发了一篇关于注解的文章,贴在这里吧,希望对题主有帮助. 一.什么是注解 我们都知道在Java代码中使用注释是为了提升代码的可读性,也就是说,注释是给人看的 ...

最新文章

  1. java数组中最小的k个元素_java – 在数组中找到k个最小整数
  2. mysql 主从复制
  3. CentOS下安装protobuf
  4. 【TensorFlow】笔记5:图像数据处理
  5. python识别图片数字traceract_如何将图形调用打印为树?
  6. [C++11]常量表达式函数
  7. Centos7下安装Python3.5
  8. 将serversocket 写在按钮事件中连接不上_看 Netty 在 Dubbo 中如何应用
  9. Java使用独立数据库连接池(DBCP为例)
  10. 一个低时延高吞吐的日志方案设计-2021年6月4日
  11. windows 10安装python2.7、python3.6并配置环境变量
  12. 图片旋转_系统自带画图程序,图片旋转翻转调整大小修改转换格式图文教程
  13. Laravel 开发笔记
  14. windows优化大师8周年纪念版_P5SPS4体验版上线|青之驱魔师10周年纪念新卷发售【搞趣日报】...
  15. Spring Boot 项目启动的七种方式
  16. fullpage得基本使用
  17. 【NAS备份】摆脱丢数据的噩梦,群晖备份硬核实战教程分享
  18. 外卖优惠券返利分销系统外卖返利系统公众号小程序源码saas系统
  19. html中加hover啥意思,css中hover是什么意思
  20. is invalid, transitive dependencies (if any) will not be available解决方案

热门文章

  1. python 解压缩 tar 包 或 tar.gz包
  2. 目标检测--Accurate Single Stage Detector Using Recurrent Rolling Convolution
  3. 手机CNN网络模型--MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications
  4. Latex使用技巧01:改变数学公式字体的颜色
  5. 1. 训练集、开发集、测试集(Train/Dev/Test sets)
  6. LeetCode 316. Remove Duplicate Letters--贪心--Java,C++,Python解法
  7. 2020-10-26runtime error: member access within null pointer of type ‘struct ListNode‘ (solution.cpp)错
  8. iOS性能优化:Instruments使用实战
  9. IIS APPPOOL\DefaultAppPool 登录失败的解决方法
  10. python中的队列和栈_python的队列和栈