愿你如阳光,明媚不忧伤。
目録
1. 什么是注解
Annotation(注解)也叫元数据(描述数据属性的信息)用@interface表示。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。简单来说注解其实就是代码中的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相对应的处理,以便于其他工具补充信息或者进行部署。但注解不会改变代码逻辑。
2. Java 提供的元注解(第四节自定义注解中有用到)
meta-annotation(元注解)的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation 类型作说明,这些类型和它们所支持的类在java.lang.annotation包中可以找到。
注解(annotations) |
|
---|---|
Target | 对象范围: packages,types,类型成员,方法参数,本地变量 |
Retention | 保留期:源码保留,class保留(默认),运行时保留 |
Documented | 文档: 被修饰的注解类将被javadoc工具提取成文档,可以定制Javadoc不支持的文档属性 |
Inherited | 继承:父类注解会被子类继承得到,如果某个父类使用了@Xxx,其子类将自动被@Xxx修饰(只有@Target被定义为ElementType.TYPE时有效) |
Repeatable(1.8) | 可重复:表示在同一个位置重复相同的注解,相当于一个容器注解(数组) |
Native(1.8) | 本地:可以从本地代码引用定义常量值的字段(字段是一个常量) |
- 示例
******************* @Target - ElementType ******************
/* 取值(ElementType)有:
1.CONSTRUCTOR:用于描述构造器
2.FIELD:用于描述属性
3.LOCAL_VARIABLE:用于描述局部变量
4.METHOD:用于描述方法
5.PACKAGE:用于描述包
6.PARAMETER:用于描述参数
7.TYPE:用于描述类、接口(包括注解类型) 或enum声明
8.ANNOTATION_TYPE:用于给一个注解进行注解
9.TYPE_PARAMETER(1.8)用于类型变量的声明语句中
10.TYPE_USE(1.8)用于标注类型的任意语句中(不包括class)
11.MODULE(9)用于模块声明 */
******************* @Retention - RetenionPolicy *******************
/* 取值(RetenionPolicy)有:
1.CLASS 编译器把该注解记录在class文件中,可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到。这是默认值!
2.RUNTIME 编译器把该注解记录在class文件中。当运行java程序时,JVM可获取注解信息,程序可以通过反射获取该注解信息,实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME
3.SOURCE 该注解只保存在源代码中,编译器直接丢弃该注解,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到 */
package com.example.annotation.controller;
import java.lang.annotation.*;
public class MetaAnnotation {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
@Documented
@Inherited
public @interface Demo {
// 可以为成员变量指定初始值
public String value() default "";
}
@Target({ElementType.PACKAGE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DemoTable {
public String tableName() default "className";
}
@interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@interface Person {
String role() default "";
}
@Person(role = "IT")
@Person(role = "God")
@Person(role = "Road")
public class SuperMan {
}
}
3. Java 提供的基本内置注解
注解(annotations) |
|
---|---|
Override | 限定父类重写方法:当子类重写父类方法时,子类可以加上这个注解,确保子类确实重写了父类的方法,避免出现低级错误 |
Deprecated | 标示已过时:表示某个程序元素类,方法等已过时,当其他程序使用已过时的类,方法时编译器会给出警告(删除线) |
SuppressWarnings | 抑制编译器警告:被该注解修饰的元素以及该元素的所有子元素取消显示编译器警告 |
SafeVarargs | 抑制堆污染警告1: 告诉编译器可变长参数中的泛型是类型安全的 |
FunctionalInterface | 函数式接口2:用来指定该接口是函数式接口,保证这个接口只有一个抽象方法,方便转换为 Lambda 表达式 |
- 示例
******************* @SuppressWarnings - Arguments ******************
/* 1.deprecation:使用了不赞成使用的类或方法时的警告;
2.unchecked:执行了未检查的转换时的警告,例如当使用集合时没有用泛型 (Generics) 来指定集合保存的类型;
3.fallthrough:当 Switch 程序块直接通往下一种情况而没有 Break 时的警告;
4.path:在类路径、源文件路径等中有不存在的路径时的警告;
5.serial:当在可序列化的类上缺少 serialVersionUID 定义时的警告;
6.finally:任何 finally 子句不能正常完成时的警告;
7.all:关于以上所有情况的警告。*/
package annotation;
import java.util.Arrays;
import java.util.List;
public class InternalAnnotation {
// 方法名会有删除线表示
@Deprecated
public void say() {
System.out.println("Noting has to say!");
}
public void speak() {
System.out.println("I have a dream!");
}
@SuppressWarnings(value = { "deprecation" })
public void test() {
say();
speak();
}
@SafeVarargs
static void unsafe(List<String>... stringLists) {
Object[] array = stringLists;
List<Integer> tmpList = Arrays.asList(66);
// 语义无效,但编译时没有警告
array[0] = tmpList;
// ClassCastException在运行时出现!
String s = stringLists[0].get(0);
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
}
4. 自定义注解
4.1 基本语法
注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
- 注解声明
public @interface CustomAnnotation {
}
4.2 注解类型元素的实现
根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。
- 注解类型元素
1.访问修饰符必须为public,不写默认为public;
2.该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一位数组;
3.该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
4.() 不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
5.default代表默认值,值必须和第2点定义的类型一致;
6.如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
public @interface CustomAnnotation {
public String name();
int age() default 18;
int[] array();
}
4.3 注解自定义注解
一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!
// 指定Target @CustomAnnotation 被限定只能使用在类、接口或方法上面
@Target(value = { ElementType.TYPE, ElementType.METHOD })
// 指定Retention @CustomAnnotation 在运行时有效可以被读取到
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAnnotation {
public String name();
int age() default 18;
int[] array();
}
4.4 自定义注解的配置使用
首先,需要定义一个注解、和一个供注解修饰的简单Java类。
- 上文中定义好的注解CustomAnnotation的
@Target
定义为ElementType.TYPE, ElementType.METHOD,那么它书写的位置应该在方法定义的上方或者是类的上方;- 在CustomAnnotation中定义的有注解类型元素,而且有些元素是没有默认值的,这要求我们在使用的时候必须在标记名后面打上
()
,并且在()
内以元素名=元素值
的形式挨个填上所有没有默认值的注解类型元素(有默认值的也可以填上重新赋值),中间用,
号分割;
- 特殊语法
1.如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!
2.如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)
3.如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!
4.如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
public class AnnotationTest {
String name;
public static void main(String[] args) {
AnnotationTest a = new AnnotationTest();
a.person();
}
@CustonAnnotation(name = "Ouseki", array = { 1, 2, 3 })
public void person() {
System.out.println(name);
}
}
--------------------------------------------------------------------
【console】
null
运行上面的程序会发现,输出的结果为null,虽然配置了自定义注解也赋值了,但是却没有获取到值。因此我们需要进一步操作。
4.5 反射操作获取注解
只有当注解的保留期处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。
- 反射实例
1.如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
2.isAnnotationPresent(Class<? extends Annotation> annotationClass)
方法是专门判断该元素上是否配置有某个指定的注解;
3.getAnnotation(Class<A> annotationClass)
方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
4.反射对象上还有一个方法getAnnotations()
,该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。
了解更多 → 【Reflection】阅读过本文之后,反手在简历上写下:熟悉Java的反射机制,不服来问!
public class AnnotationTest {
private String name;
public void setName(String name) {
this.name = name;
}
@CustonAnnotation(name = "Ouseki", array = { 1, 2, 3 })
public void person() {
System.out.println(name);
}
public static void main(String[] args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException, InstantiationException {
try {
// 获取AnnotationTest的Class对象
Class<?> annotationTestClass = Class.forName("com.ITGodRoad.controller.AnnotationTest");
// 获取AnnotationTest的Method对象
Method annotationTestMethod = annotationTestClass.getMethod("person");
if (annotationTestMethod.isAnnotationPresent(CustonAnnotation.class)) {
System.out.println("AnnotationTest类上配置了CustonAnnotation注解!");
// 获取该元素上指定类型的注解
CustonAnnotation custonAnnotation = annotationTestMethod.getAnnotation(CustonAnnotation.class);
System.out.println("name: " + custonAnnotation.name() + ", age: " + custonAnnotation.age() + ", number: " + custonAnnotation.array()[0]);
// 实例化对象:newInstance()从jdk1.9已废弃,因为绕过了编译时异常;推荐使用下面的方法
Object obj = annotationTestClass.getDeclaredConstructor().newInstance();
Method setMethod = annotationTestClass.getMethod("setName", String.class);
setMethod.invoke(obj, custonAnnotation.name());
// 执行person()方法,查看注解的值是否已经成功注入到类中
((AnnotationTest) obj).person();
} else {
System.out.println("AnnotationTest类上没有配置CustonAnnotation注解!");
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
}
--------------------------------------------------------------------
【console】
AnnotationTest类上配置了CustonAnnotation注解!
name: Ouseki, age: 18, number: 1
Ouseki
【每日一面】
java 和 javax 的区别与联系
java 是Java的API(Application Programming Interface)包,java是核心包。java类库是java发布之初就确定了的基础库。
javax 也是Java的API(Application Programming Interface)包,javax的x是extension的意思,也就是扩展包。javax类库是在java基础库上面增加的一层东西,就是为了保持版本兼容要保存原来的,但有些东西有了更好的解决方案,所以,就加上些,典型的就是awt(Abstract Windowing ToolKit)和swing。
将扩展从javax包移动到java包太麻烦了,最终会破坏一堆现有的代码。所以实际上java和javax没有区别,都是java标准API的一部分