正在学习的小白。不当之处,请多多指教
java
目录程序员
java中有四大类型,其中三个是:枚举,接口,类。编程
今天简单认识一下第四个类型:注解。数组
什么是注解bash
Annotation 这里是"注解"的意思。
除此以外,这个单词患有一个“注释”的意思。咱们都知道,注释是给程序员看的。那么注解呢?
注解是给程序看的,因此Annotation既有注解也有注释的意思
复制代码
咱们很早就见过一些注解:jdk中的@Override,@FunctionalInterface,Junit框架的@Test等。框架
枚举,接口,类,这个三个类型咱们都写过(他们的源码 ),基本语法,想必你们都知道。jvm
那么注解有源码吗?答案是确定的,jvm没有那么厉害,不可能仅凭一个@+一个单词就知道程序想表达什么。ide
咱们以@FunctionalInterface(关于这个注解不清楚的能够参考函数式编程的内容)为例:函数式编程
@FunctionalInterface
interface UsB{
void show();
}
复制代码
按住Ctrl点击注解进入,就能够看到@FunctionalInterface的源码:函数
package java.lang;
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
复制代码
先不着急认识这些是什么意思,看懂其构成就能够,而后咱们照猫画虎,本身写一个:
/** * 自定义注解 */
@interface MyAnnotation{ }
复制代码
@interface 注解名字{ }
那么,注解有像其余普通类同样的属性吗?
String name(); 注解中 这是属性,不是接口中的方法。
注解属性赋值语法:String name() default "属性";
(ps:能够赋值,但通常再也不内部赋值,给外部使用者赋值)
很差意思,注解是没有方法的!
注解对属性类型是有要求的:
8个基本数据类型 / 字符串类型 / Class类型 / 注解类型 / 枚举类型 及其一维数组
注解中只有一个属性, 那么请将该属性定义为 value 名称. 好处: 使用该注解时能够省略 value=
以上6点就是注解的基本语法。
以前咱们使用注解,都是固定的,好比@Override只能在(重写)方法使用,@FunctionalInterface只能在(有且仅有一个要实现的方法的)接口使用。乳沟随便使用,就会马上编译报错。 那么,使用咱们刚刚自定义的注解,使用上有限制吗?
@MyAnnotation("省略了value")//能够用在类上
public class AnnotationDemo {
@MyAnnotation("zhangsan") String name;//能够用在属性上
@MyAnnotation("show") //能够用在方法上
public void show(){}
}
//自定义注解
@interface MyAnnotation{
//属性
String value();
}
复制代码
这样看来,咱们目前自定义的注解是没有任何使用位置的限制的,再回头看看,前面@FunctionalInterface注解的源码,或者@Override的源码,发现咱们自定义的注解少了几样东西。没错,少了一些注解。准确说,少了一些 “元注解”。
package java.lang;
import java.lang.annotation.*;
//三个元注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
复制代码
什么是元注解,“元”意为:最开始,初始的意思,那么元注解就是其实的注解,或者叫,注解的注解。是用来修饰说明注解的。
元注解都来自于:java.lang.annotation.* 下
先来认识一下元注解:
意为目标,也就是说明注解的使用范围
来看一下@Target的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}
复制代码
能够看到,他也有本身的元注解,和一个一维数组属性ElementType[],进入ElementType[],咱们能够看到,ElementType是一个枚举类(控制文本长度,去除了全部源码的注释):
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
CONSTRUCTOR,
LOCAL_VARIABLE,
ANNOTATION_TYPE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE
}
复制代码
这些枚举类型很容易看出,Target注解的value值,能够是这些枚举元素,例如:FIELD表示使用在属性上,METHOD可使用在方法上,等等,不在一一说明。
使用: 、
@Target({ElementType.FIELD,ElementType.METHOD}) 注意,多个值用中括号,属性名为value,可省略
能够加在咱们前面写的自定义注解上看看效果。
意为,保留策略,又称之为生命周期。
咱们继续进入@Retention的源码:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}
复制代码
也只有一个属性,进入RetentionPolicy查看:
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
复制代码
RetentionPolicy也是一个枚举类,只有三个值。
这三个值很好理解,他与java程序的声明周期是一一对应的:
源码阶段(SOURCE),编译阶段(CLASS),运行阶段(RUNTIME)。
@Retention的属性不是数组,因此只能选择一个值
如:@Retention(RetentionPolicy.CLASS)
ps : RetentionPolicy.RUNTIME 最经常使用,由于一般和反射结合使用,而反射是在运行时操做类。
(不了解反射能够参考个人另外一篇笔记:Java 反射机制那些事)
咱们经过代码的方式,简单说明下,如何利用反射解析注解
先来准备一个 Student类:
public class Student {
//一个属性
public String name;
public Student() { }
//一个构造
public Student(String name) { this.name = name; }
//一个方法
public void show(String msg){
System.out.println("show方法 = " + msg);
}
//重写toString
@Override
public String toString() {
return "Student{name= "+name+"}';
}
}
复制代码
再来写一个自定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.CONSTRUCTOR})
public @interface MyAnnotation{
String value();
}
复制代码
而后,根据自定义注解的做用范围,在Student类上加上咱们自定义的注解,而后赋上一些值:
@MyAnnotation("小明")
public Student(String name) {
this.name = name;
}
@MyAnnotation("小红来了")
public void show(String msg){
System.out.println("show方法 = " + msg);
}
复制代码
在新建一个类,实现咱们的反射部分代码,这里个人类就叫作:AnnotationDemo
补充:
注解 某种意义上讲能够做为 一种配置文件(常见的配置文件 .properties 或者 .xml)
既然写了一个文件,就要去对他进行一些 读写 操做,若是不去读取并使用他的内容,呢这个(配置)文件存在在程序中有什么意义??文件是保存信息数据的,因此文件不读出来,是没有意义的。
而读出配置文件的内容 称之为 解析!
注解有个解析技术,叫作: 反射!
写了注解,就至关于,写了配置文件不读取!是没有意义的。
反射是在运行时,操做Class对象,注解写在Student类中,因此,反射能够操做Student的Class对象。
咱们就先AnnotationDemo类中在利用反射获取一个Student里面的自定义注解:
public class AnnotationDemo {
public static void main(String[] args) throws NoSuchMethodException {
//1.获取Student的Class对象
Class<?> clazz = Student.class;
//2.先从构造器开刀,找到构造器
Constructor<?> constructor = clazz.getConstructor(String.class);
/** * 3.使用构造起的方法 isAnnotationPresent(), * 方法意为:有没有(参数)注解存在?注意:(一个方法。类等能够有多个注解) * 参数:注解类型的class对象 * 返回值:存在(true), */
boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
//若是存在,来获取这个注解
if (annotation) {
/* getAnnotation(注解.class)获取注解 此时获取到 Student满参构造上的注解 */
MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
//注解有个属性叫value
String value = myAnnotation.value();
System.out.println("value = " + value); //value = 小明
}
}
}
复制代码
以上代码,就将 Student满参构造上的注解的属性值读取出来了。
仍是和反射同样的问题?这样解析注解有什么用?
这要结合具体场景,有些项目中可能要本身定义注解使用,而最多使用的地方就是框架。
题外话——引入IOC概念
上面的示例代码咱们能够看到,AnnotationDemo类中的一系列代码,就获取到了Student的满参构造的注解的value值。那么获取到注解的属性值,咱们就能够将值反转到(传入)这个构造里面去。
这个就叫作控制反转(IOC)。
怎么传值呢?继续看代码!
//部分代码和上面同样,注释省略
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Student.class;
Constructor<?> constructor = clazz.getConstructor(String.class);
boolean annotation = constructor.isAnnotationPresent(MyAnnotation.class);
if (annotation) {
MyAnnotation myAnnotation = constructor.getAnnotation(MyAnnotation.class);
String value = myAnnotation.value();
System.out.println("value = " + value); //value = 小明
/* 利用 newInstance 方法,就能够得到Student实例 */
Object obj = constructor.newInstance(value);
System.out.println("obj = " + obj);
}
}
复制代码
咱们能够想一下,假如AnnotationDemo类和自定义注解,不是咱们所写,是一种别人写的框架,本身历来不知道这样一些代码,而只是用一个注解,传了个值,就构造出了一个实例对象。这就是框架技术的一部分底层原理。
接下来解析Student的show方法的注解:
public class AnnotationDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取Class对象
Class<?> clazz = Student.class;
//获取方法
Method show = clazz.getMethod("show", String.class);
boolean b = show.isAnnotationPresent(MyAnnotation.class);
if (b) {
//每一个反射对象都有这样一个方法,获取注解
MyAnnotation annotation = show.getAnnotation(MyAnnotation.class);
String value = annotation.value();
show.invoke(clazz.newInstance(), value);
//运行,查看结果
//show方法 = 小红来了
}
}
}
复制代码