反射&注解

深碍√TFBOYSˉ_ 2022-04-13 12:49 346阅读 0赞

今日目标

  1. 获得字节码对象的三种方式
  2. 能够使用反射操作构造方法,变量,方法
  3. 能够获取自定义注解的变量值
  4. 能够自定义注解

1.反射

1.1反射的概述

问题:我们平时书写在idea中的Java程序是如何运行的呢?

1)首先将 .java 源文件编译为class类文件;

2)编译后的类文件是存在硬盘中的,那么我们运行需要在内存中看到效果,那么类文件是如何被加载到内存中的呢,就是jvm通过类加载器ClassLoader把硬盘中的class文件加载到内存中,这样就可以使用这个类中的成员变量和方法了。而被加载到内存中这个class文件就会变成一个Class类的对象。

反射要依赖于Class类。

由于Class表示类文件的字节码文件对象,类字节码文件就是在描述一个类,描述类的成员变量、成员函数和构造函数。

而反射就是从一个类的字节码文件中拿到成员变量、成员函数和构造函数。要想从一个类中拿东西必须拿到这个类的字节码文件对象,所以反射依赖于Class,因此我们在学习反射之前先了解下Class。

1.2获取字节码对象的方式

1.2.1Class类介绍

在Java中使用类来描述所有的事物,而这些描述完的所有程序,在编译完之后统一都会生成各自class文件。

在Java中class文件是所有源代码(.java 文件)程序编译后统一的结果。class文件是一类可以被JVM直接执行的文件。class文件在Java世界中就是存在的一类事物。

Java使用Class类来描述和封装class文件这类事物。class文件也叫作字节码文件。
说明:

1)Class类它可以表示Java中的任何内容;

2)Java中使用Class类表示硬盘上的某个.class文件,启动JVM就会把文件加载到内存中,占用一片空间,称为一个字节码文件对象,这个对象就是Class类的一个实例。不同的类,有自己的字节码文件对象,这些对象都是Class类的实例;

3)说明:在Class类中专门提供了几个获取成员变量 、成员函数 、构造函数的函数。






















方法名 作用
Field getField(String name) 表示获取类中的成员变量的对象
Method getMethod() 表示获取类中的成员函数的对象
Constructor getConstructor() 表示获取类中的构造函数的对象

#

1.2.2三种方式获得字节码对象(掌握)

因为反射技术是通过Class对象来实现把一个类进行解剖的,所以需要先了解怎么样才可以获取到Class对象。

需求:演示获取Class的三种方式:

1)获取Class对象的第一种方式:使用类的class属性直接获取:类名.class。

说明:在任何的一个类中都有一个静态成员变量class,可以直接获取到class文件所属的Class对象。

2)获取Class对象的第二种方式:在Object类中,有个getClass方法,就可以获取到任何一个对象对应的Class对象。对象.getClass()。

3)获取Class对象的第三种方式:在Class类中有个静态的方法:static Class forName(String className),根据类的名称获取类的Class对象。

说明:这里的参数className必须是类的全名(就是带有包名的全名称)。如:Class.forName(“java.lang.String”);

补充:上述三种方式可以获得Class对象,获得完Class对象就可以获取类的基本信息:

获取类的基本信息:

  1. String getName() 获取类的名称,就是获得包名.类名
  2. String getSimpleName() 获取类的简称 类名

代码演示如下所示:

  1. package com.SiyualChen.day03.test04;
  2. /*
  3. * 演示:获取Class的三种方式
  4. * 方式1:类名.class
  5. * 方式2:对象.getClass()
  6. * 方式3:static Class forName(String className)根据类的名称获取类的Class对象
  7. * 注意:这里的className必须是类的全名!
  8. *
  9. * 获取类的基本信息:
  10. * String getName() 获取类的名称 包名.类名
  11. * String getSimpleName() 获取类的简称 类名
  12. */
  13. public class ClassDemo01 {
  14. public static void main(String[] args) throws ClassNotFoundException {
  15. //方式1:类名.class
  16. Class<String> stringClass = String.class;
  17. System.out.println(stringClass.getName()); //java.lang.String
  18. System.out.println(stringClass.getSimpleName()); //String
  19. System.out.println("------华丽的分割线------");
  20. //方式2:对象.getClass()
  21. Class<? extends String> stringClass1 = "SiyualChen".getClass();
  22. System.out.println(stringClass1.getName()); //java.lang.String
  23. System.out.println(stringClass1.getSimpleName()); //String
  24. System.out.println("------华丽的分割线------");
  25. //方式3:static Class forName(String className)根据类的名称获取类的Class对象
  26. Class<?> stringClass2 = Class.forName("com.SiyualChen.day03.Person");
  27. System.out.println(stringClass2.getName()); //com.SiyualChen.day03.Person
  28. System.out.println(stringClass2.getSimpleName()); //Person
  29. }
  30. }

运行结果:

  1. java.lang.String
  2. String
  3. ------华丽的分割线------
  4. java.lang.String
  5. String
  6. ------华丽的分割线------
  7. com.SiyualChen.day03.Person
  8. Person
  9. Process finished with exit code 0

总结:上述三种方式都是用来获取Class对象的,那么在开发中一般使用哪种获取方式呢?

在开发中我们会使用第三种获取方式。

说明:第三种的好处就是加载一个类却不需要知道这个类是什么,通过第一种方式获取前提是必须得先知道类名,然后才能通过类名.class获取。而第二种方式必须知道对象才能通过对象.getClass()来获取。

而第三种不需要知道类名或者对象就可以直接获取,或者可以这样理解,我们在真实开发中,类的名字是不知道的,都是通过IO流来读取配置文件读取回来的。

也就是说我们读取配置文件的时候根据key来获取String类型的value,这样就可以把String类型的value作为forName(String className)函数的参数,而不需要在程序代码中体现出类名字样就可以获得Class对象了。
代码演示如下:

  1. package com.SiyualChen.day03.test04;
  2. /**
  3. * 8种基本数据类型、void叫做预定义对象,一共有9种
  4. */
  5. public class ClassDemo02 {
  6. public static void main(String[] args) {
  7. 获取int类型的Class对象
  8. Class<Integer> integerClass = int.class;
  9. System.out.println(integerClass); //int
  10. // 获取void类型的Class对象
  11. Class<Void> voidClass = void.class;
  12. System.out.println(voidClass); //void
  13. }
  14. }

运行结果:

  1. int
  2. void
  3. Process finished with exit code 0

总结:任何一种数据都有自己的字节码。

1.4反射操作构造方法

说明:

1)使用Class类的对象即字节码对象可以获取class文件中的所有构造函数,具体应该借助Class类中的如下函数:

  1. Constructor getDeclaredConstructor(Class ... parameterTypes)根据参数列表获取指定的构造函数 包括私有的

说明:由于这里需要Class类的对象,所以在给参数的时候,直接使用实参类型的类获取Class对象即可。

  1. 举例:假设需要获取String类型的构造函数,那么这里直接使用String.class作为getDeclaredConstructor(Class ... parameterTypes)的参数。

2)获取到构造函数对象之后,就可以使用获取的构造函数对象创建某个类的真实对象。我们通过反射已经获取到构造函数,查阅Constructor类中的描述,发现Constructor类中的newInstance(Object… initargs) 方法,这个方法可以动态的创建出这个构造函数对象所表示的那个类的真实对象。

​ 说明: Object… initargs 创建对象的时候需要传递的真实的数据,就是构造函数所需要的实际参数。

代码如下所示:

需求:获取Student类的公共构造函数,并使用获取的构造函数创建对象。
学生类:

  1. package com.SiyualChen.day03.test04;
  2. public class Student {
  3. private String name;
  4. private int age;
  5. //公共构造
  6. public Student(String name) {
  7. this.name = name;
  8. }
  9. public Student() {
  10. }
  11. //私有化构造方法
  12. private Student(String name, int age) {
  13. this.name = name;
  14. this.age = age;
  15. }
  16. @Override
  17. public String toString() {
  18. return "Student{" +
  19. "name='" + name + '\'' +
  20. ", age=" + age +
  21. '}';
  22. }
  23. public String getName() {
  24. return name;
  25. }
  26. public void setName(String name) {
  27. this.name = name;
  28. }
  29. public int getAge() {
  30. return age;
  31. }
  32. public void setAge(int age) {
  33. this.age = age;
  34. }
  35. }

测试类

  1. package com.SiyualChen.day03.test04;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.InvocationTargetException;
  4. public class ClassDemo03 {
  5. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  6. //获取Student的字节码对象
  7. Class<?> clazz = Class.forName("com.SiyualChen.day03.test04.Student");
  8. //获取学生构造方法对象public Student(String name)
  9. Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class); //这里有个坑 如果没有String.class会错 wrong number of arguments
  10. //创建学生类对象
  11. Student stu = (Student) declaredConstructor.newInstance("张三");
  12. //获取属性值
  13. System.out.println(stu.getName());
  14. }
  15. }

运行结果:

  1. 张三
  2. Process finished with exit code 0

需求:获取Student类的私有构造函数,并使用获取的构造函数创建对象。

  1. package com.SiyualChen.day03.test04;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.InvocationTargetException;
  4. /**
  5. * 需求:获取Student类的私有构造函数,并使用获取的构造函数创建对象。
  6. */
  7. public class ClassDemo04 {
  8. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  9. //获取Student的字节码对象
  10. Class<?> stringClass = Class.forName("com.SiyualChen.day03.test04.Student");
  11. //获取Student的构造方法 private Student (String name ,int age)
  12. Constructor<?> declaredConstructor = stringClass.getDeclaredConstructor(String.class,int.class);
  13. //创建Student 对象。
  14. Student stu = (Student) declaredConstructor.newInstance("李四", 23);
  15. System.out.println(stu.getName() +" "+stu.getAge());
  16. }
  17. }

运行结果:

  1. Exception in thread "main" java.lang.IllegalArgumentException: wrong number of arguments
  2. at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
  3. at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
  4. at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
  5. at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:488)
  6. at com.SiyualChen.day03.test04.ClassDemo04.main(ClassDemo04.java:16)
  7. Process finished with exit code 1

可以看到上述代码发生了异常,原因如下:
通过Class类中的Constructor getDeclaredConstructor(Class … parameterTypes)函数可以获得类中的所有的构造函数,包括私有的构造函数,但是私有的构造函数我们在其他类中是无法使用的,如果要想使用必须强制取消Java对私有成员的权限检测或者可以理解暴力访问。暴力访问提升权限

  1. setAccessible()方法

代码如下:

  1. package com.SiyualChen.day03.test04;
  2. import java.lang.reflect.Constructor;
  3. import java.lang.reflect.InvocationTargetException;
  4. /**
  5. * 需求:获取Student类的私有构造函数,并使用获取的构造函数创建对象。
  6. */
  7. public class ClassDemo04 {
  8. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
  9. //获取Student的字节码对象
  10. Class<?> stringClass = Class.forName("com.SiyualChen.day03.test04.Student");
  11. //获取Student的构造方法 private Student (String name ,int age)
  12. Constructor<?> declaredConstructor = stringClass.getDeclaredConstructor(String.class,int.class);
  13. //创建Student 对象。
  14. //使用获得的构造函数创建对象 newInstance(Object... initargs)
  15. //参数列表与构造方法的参数列表要一一对应public Student(String name,int age)
  16. //在访问构造方法之前 设置暴力访问
  17. declaredConstructor.setAccessible(true);
  18. Student stu = (Student) declaredConstructor.newInstance("李四", 23);
  19. System.out.println(stu.getName() +" "+stu.getAge());
  20. }
  21. }

运行结果:

  1. 李四 23
  2. Process finished with exit code 0

1.5反射操作成员变量

需求:演示:反射获取成员变量。

  1. Field getDeclaredField(String name)根据变量名获取指定的成员变量 包括私有成员变量

问题:拿到字段能干嘛?

  1. 使用Field 类中的函数获取或修改字段的值:
  2. get(Object obj)获取指定对象上当前字段的值。
  3. set(Object obj,Object value) obj对象上此 Field 表示的字段设置为指定的值

获取字段类型:

  1. Class getType() 获取字段的数据类型的Class对象

需求:反射获取Person类中的name、age、address属性值并修改其值。
Person类

  1. package com.SiyualChen.day03.test04;
  2. public class Person {
  3. private String name;
  4. public int age;
  5. private String address;
  6. @Override
  7. public String toString() {
  8. return "Person{" +
  9. "name='" + name + '\'' +
  10. ", age=" + age +
  11. ", address='" + address + '\'' +
  12. '}';
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public int getAge() {
  21. return age;
  22. }
  23. public void setAge(int age) {
  24. this.age = age;
  25. }
  26. public String getAddress() {
  27. return address;
  28. }
  29. public void setAddress(String address) {
  30. this.address = address;
  31. }
  32. public Person() {
  33. }
  34. public Person(String name, int age, String address) {
  35. this.name = name;
  36. this.age = age;
  37. this.address = address;
  38. }
  39. }

测试类

  1. package com.SiyualChen.day03.test04;
  2. import java.lang.reflect.Field;
  3. public class ClassDemo05 {
  4. public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
  5. //方法2获得字节码对象
  6. Person person = new Person("SiyualChen",19,"北京");
  7. //获取字节码对象
  8. Class<? extends Person> stringClass = person.getClass();
  9. //方法三获得字节码对象
  10. Class<?> stringClass1 = Class.forName("com.SiyualChen.day03.test04.Person");
  11. //Field getDeclaredField(String name)根据变量名获取指定的成员变量
  12. Field fieldName = stringClass.getDeclaredField("name");
  13. Field fieldAge = stringClass.getDeclaredField("age");
  14. System.out.println(fieldName);
  15. System.out.println(fieldAge);
  16. //暴力拆解
  17. fieldName.setAccessible(true);
  18. String name = (String)fieldName.get(person);
  19. System.out.println(name);
  20. //获取age
  21. int age = (int)fieldAge.get(person);
  22. System.out.println(age);
  23. //获取地址
  24. Field filedAddress = stringClass.getDeclaredField("address");
  25. //暴力拆解
  26. filedAddress.setAccessible(true);
  27. String address =(String) filedAddress.get(person);
  28. System.out.println(address);
  29. //修改名字,年龄,地址
  30. fieldName.set(person,"Chensiyuan");
  31. fieldAge.set(person,22);
  32. filedAddress.set(person,"上海");
  33. String name2 = (String) fieldName.get(person);
  34. int age2 = (int)fieldAge.get(person);
  35. String address2 = (String) filedAddress.get(person);
  36. System.out.println(name2+age2+address2);
  37. }
  38. }

运行结果:

  1. private java.lang.String com.SiyualChen.day03.test04.Person.name
  2. public int com.SiyualChen.day03.test04.Person.age
  3. SiyualChen
  4. 19
  5. 北京
  6. Chensiyuan22上海
  7. Process finished with exit code 0

1.6反射操作成员方法

  1. Method getDeclaredMethod(String name,Class ... parameterTypes)获取某个方法。

说明:

1)在Class类中提供的getDeclaredMethod方法上接收一个String name,name表示的是需要反射的那个方法的名字。

​ 因为在一个类中可以有多个不同名的方法。在反射的时候需要指定这个方法的名字,同时在一个类中还可能出现方法的重载,这时还需要指定具体反射的是哪个方法参数类型。

3)让反射到的一个方法运行,需要使用Method类中的invoke方法 :

​ Object invoke(Object obj, Object… args)

​ invoke方法中的第一个参数 Object obj:表示的是当前需要调用这个方法的那个对象

​ invoke方法中的第二个参数Object… args:

​ 表示的是真正需要运行的某个类中被反射的那个方法需要接收的真实参数

​ 在调用Method类中的invoke方法的时候,其实底层是在运行被反射的那个方法,

​ 既然是某个方法在运行,那么方法运行完之后可能会有返回值。

举例:需求:我们想通过反射技术获得Person类中的setName()函数,并让其执行。

Person类

  1. package com.SiyualChen.day03.test04;
  2. public class Person {
  3. private String name;
  4. public int age;
  5. private String address;
  6. @Override
  7. public String toString() {
  8. return "Person{" +
  9. "name='" + name + '\'' +
  10. ", age=" + age +
  11. ", address='" + address + '\'' +
  12. '}';
  13. }
  14. public String getName() {
  15. return name;
  16. }
  17. public void setName(String name) {
  18. this.name = name;
  19. }
  20. public int getAge() {
  21. return age;
  22. }
  23. public void setAge(int age) {
  24. this.age = age;
  25. }
  26. public String getAddress() {
  27. return address;
  28. }
  29. public void setAddress(String address) {
  30. this.address = address;
  31. }
  32. public Person() {
  33. }
  34. public Person(String name, int age, String address) {
  35. this.name = name;
  36. this.age = age;
  37. this.address = address;
  38. }
  39. }

测试类:

  1. package com.SiyualChen.day03.test04;
  2. import java.lang.reflect.InvocationTargetException;
  3. import java.lang.reflect.Method;
  4. public class ClassDemo06 {
  5. public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
  6. //获取字节码对象
  7. Class<?> stringClass = Class.forName("com.SiyualChen.day03.test04.Person");
  8. /*
  9. * 反射成员方法:
  10. * public void setName(String name)
  11. * 类中的非静态的成员方法,需要对象调用,我们反射到方法之后,最后肯定是要运行这个方法
  12. * 这时肯定还是需要对象的
  13. *
  14. * Method getDeclaredMethod(String name, Class<?>... parameterTypes)
  15. * String name 反射的方法的名字
  16. * Class<?>... parameterTypes 反射的方法接受的参数类型
  17. */
  18. Method declaredMethod = stringClass.getDeclaredMethod("setName", String.class);
  19. //类中的非静态的成员方法,需要对象调用,我们反射到方法之后,最后肯定是要运行这个方法
  20. Person person = new Person();
  21. //通过反射的方式执行setName函数
  22. /*
  23. * 让反射到的一个方法运行,需要使用Method类中的invoke方法
  24. *
  25. * Object invoke(Object obj, Object... args)
  26. *
  27. * invoke方法中的第一个参数 Object obj:表示的是当前需要调用这个方法的那个对象
  28. * invoke方法中的第二个参数Object... args:
  29. * 表示的是真正需要运行的某个类中被反射的那个方法需要接收的真实参数
  30. * 在调用Method类中的invoke方法的时候,其实底层是在运行被反射的那个方法,
  31. * 既然是某个方法在运行,那么方法运行完之后可能会有返回值
  32. */
  33. //执行setName函数 这句代码就是在调用反射到Person类中的setName方法
  34. Object obj = declaredMethod.invoke(person, "SiyualChen");
  35. System.out.println(person);
  36. System.out.println(obj);
  37. System.out.println(person.getName());
  38. }
  39. }

运行结果:

  1. Person{name='SiyualChen', age=0, address='null'}
  2. null
  3. SiyualChen
  4. Process finished with exit code 0

2.注解

判断一个子类方法是否是重写父类的,我们已经使用过类似@Override这种注解

什么是注解:Annotation注解,是一种代码级别的说明。其实注解就是具有特殊含义的标记,注解是给机器阅读的。它是JDK1.5及以后版本引入的一个特性,与类、接口是在同一个层次。

对比注释:注释是给开发人员阅读的;注解是给jvm看的,给计算机看的。

注解使用格式:@注解名

注解的作用

  1. 编译检查:通过代码里标识注解,让编译器能够实现基本的编译检查。例如:@Override。表示复写的意思。

说明:如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。

  1. 编写文档:通过代码里标识注解,辅助生成帮助文档对应的内容 例如我们使用的java api有些方法上有@Deprecated 表示过时的意思。

2.1JDK提供的注解

**JDK官方提供了三个注解 **

**@Override: **限定重写父类方法, 该注解只能用于方法 ——— 编译时检查,不构成覆盖 报错

JDK5.0 override注解只能用于方法覆盖 JDK6.0 该注解可以用于接口的方法实现

**@Deprecated: **用于表示某个程序元素(类, 方法等)已过时 ——- 在编译器进行编译时 报出一个警告

为什么会有过时方法: 提供了新的方法取代之前方法、发现某个方法存在安全隐患,不建议用户再使用

@SuppressWarnings:[sə’pres’wɔːnɪŋs]抑制编译器警告. —— 通知编译器在编译时 不要报出警告信息

使用 all 忽略所有警告信息

rawtypes 忽略类型安全

unused 忽略不使用

serial 忽略序列号

all 忽略所有

  1. @Override JDK1.5表示复写父类的方法

我们书写一个Parent这个类,里面定义一个show()方法,然后再创建一个类Son继承它。我们重写show()方法,发现这个方法上面有个

@Override注解。特指这个方法是重写的方法。

  1. @SuppressWarnings 表示抑制警告,不知道大家有没有发现,我们所书写的java代码中经常出现一些黄色的标识。

这是一个警告,不是一个错误。但是我们可以通过@SupperssWarings注解来消除相应的警告。被修饰的类或方法如果存在编译警告,将被编译器忽略。

  1. package com.SiyualChen.day03.test05;
  2. import java.util.ArrayList;
  3. public class test01 {
  4. /*
  5. * SuppressWarnings 表示抑制警告
  6. * all 表示忽略所有的警告
  7. */
  8. @SuppressWarnings("all")
  9. public void show(){
  10. ArrayList list = new ArrayList();
  11. list.add("chen");
  12. }
  13. }

2.2自定义注解

2.2.1定义注解

上面我们学习了一些官方提供的注解。那么,同样我们开发者可以根据自己的需求来实现自定义注解。

注解和类,接口是属于同一级别。所以创建注解我们需要使用@interface关键字。

自定义MyAnnotation1注解,步骤如下:

  1. package com.SiyualChen.day03.test05;
  2. public @interface MyAnnotation1 {
  3. }

说明:

1)
A:定义类: class

B:定义接口:interface

C:定义注解使用关键字: @interface

2)可以自定义没有属性的注解,这时候我们已经可以使用该注解,可以在我们的方法或者变量上写上@MyAnnotation1。注解的使用格式:@注解名。

只不过此时,当前这个注解只是定义了,没有具体的意义。想要让他有意义。我们需要通过后面的案例来给大家介绍。

新建一个Test02类来使用上述自定义的注解。

  1. package com.SiyualChen.day03.test05;
  2. public class test02 {
  3. @MyAnnotation1
  4. public void show(){
  5. System.out.println("今天天气真好");
  6. }
  7. }

上面我们定义的注解里面没有任何东西,相当于我们定义了一个class,里面没有任何东西。接下来我们学习一下定义带有属性的注解。

3)定义带有属性的注解。

属性声明的格式:public abstract 属性类型 属性名() default 属性的默认值;

举例:

  1. public abstract String value() default “黑旋风”;
  2. public abstract int value2() default 19;

A:需要注意的是 public abstract 是固定值,可以省略。写与不写是一样的,这点和接口中的方法的固定修饰符是一样的。

B:注解中的属性类型可以是:基本类型、字符串String、Class、注解,以及以上类型的一维数组。不能是其他的数据类型,比如自定义类型Person。

C:属性名自定义,属于标识符。但是同一注解中属性名也不能相同。

D:default 属性的默认值 : 也是可以写,可以不写。

在其他类中使用注解格式:@注解名(key=value)

关于自定义注解的一个完整案例:

  1. package com.SiyualChen.day03.test05;
  2. /**
  3. * 关于自定义注解的完整案例
  4. */
  5. public @interface MyAnnotation2 {
  6. //自定义属性 定义一个String类型的属性,没有默认值
  7. public abstract String value();
  8. //定义int类型的属性,没有默认值
  9. int age();
  10. //定义String类型的数组,没有默认值
  11. String[] names();
  12. }
  13. package com.SiyualChen.day03.test05;
  14. public class Test03 {
  15. @MyAnnotation2(age=10,names={"chen","Siyuan"},value = "哈哈哈")
  16. public void method(){
  17. System.out.println("method.....");
  18. }
  19. }

说明:

如果自定义注解的属性类型是数组的时候,那么属性的写法key={value1,value2…},但是如果属性值只有一个的话,那么属性的写法key=value或者key={value}

.2.2使用注解

刚刚在上面我们自定义2个注解,那么下面我们来使用这些注解。直接创建一个类,在类上写上注解,注解使用的格式如下。

代码如下:我们这里定义的2个注解可以写在类上,可以写在方法上,可以写在变量上。

使用格式:@注解类名( 属性名= 值 , 属性名 = 值 , …)

  1. package com.SiyualChen.day03.test05;
  2. public class Test03 {
  3. @MyAnnotation1(value = "嘻嘻嘻")
  4. @MyAnnotation2(age=10,names={"chen","Siyuan"},value = "哈哈哈")
  5. public void method(){
  6. System.out.println("method.....");
  7. }
  8. }

注解使用的注意事项:

注解可以没有属性,如果有属性需要使用小括号括住。例如:@MyAnnotation1或@MyAnnotation2()

属性格式:属性名=属性值,多个属性使用逗号分隔。

如果属性类型为数组,设置内容格式为:{ 1,2,3 }。

如果属性类型为数组,值只有一个{} 可以省略的。

一个方法上,相同注解只能使用一次,不能重复使用。

2.2.3解析注解

如果给类、方法等添加注解,如果需要获得注解上设置的数据,那么我们就必须对注解进行解析。

在解析自定义注解之前,我们首先要学习下一个新的概念———》元注解。

2.3.3.1元注解介绍

元注解:用于修饰注解的注解。作用:限制定义的注解的特性。

我们要学习的元注解有两种。

1、@Target 用于确定被修饰的注解使用的位置。

注解属性:

  1. u ElementType.TYPE 修饰类、接口;
  2. u ElementType.CONSTRUCTOR 修饰构造方法;
  3. u ElementType.METHOD 修饰方法;
  4. u ElementType.FIELD 修饰字段;

对于注解@Override是不能修饰类的,只能修饰方法,而抑制警告注解@SuppressWarnings是可以修饰类、方法等,那么具体哪个注解修饰方法或者类,是由什么决定的呢?其实主要就是由元注解决定的。

对于@Override注解只能修饰方法。

对于@SuppressWarnings注解可以修饰所有的内容。

总结:所以对于元注解**@Target**表示修饰的注解作用范围,能够使用在哪里。
案例:需求一:在自定义注解MyAnnotation2上添加一个元注解,要求自定义注解MyAnnotation2只能使用在方法上。

  1. package com.SiyualChen.day03.test05;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Target;
  4. /**
  5. * 关于自定义注解的完整案例
  6. */
  7. @Target(ElementType.METHOD)
  8. public @interface MyAnnotation2 {
  9. //自定义属性 定义一个String类型的属性,没有默认值
  10. public abstract String value();
  11. //定义int类型的属性,没有默认值
  12. int age();
  13. //定义String类型的数组,没有默认值
  14. String[] names();
  15. }

2、@Retention 用于确定被修饰的自定义注解生命周期。就是使用这个元注解修饰的注解可以存活到什么时候。

在元注解@Retention中可以使用如下几个属性:

u RetentionPolicy.SOURCE 被修饰的注解只能存在源码中,即.java文件中。字节码.class文件中没有。用途:提供给编译器使用。

u RetentionPolicy.CLASS 被修饰的注解只能存在源码和字节码中,运行时内存中没有。用途:JVM, java虚拟机使用。

u RetentionPolicy.RUNTIME 被修饰的注解存在**源码、字节码、内存(运行时)。**用途:取代xml配置。(xml属于配置文件的一种,我们后面会学习)注意:
注意:上面三个属性表示当使用在元注解中,元注解所修饰的注解能够使用到什么时候。
接下来我们就开始解析注解中的属性值:

1)首先定义一个注解,并设置属性值,记住,在开发中这个注解不需要我们自己书写。

说明:自定义的注解要求可以使用在方法和类上。

  1. import java.lang.annotation.ElementType;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. import java.lang.annotation.Target;
  5. /**
  6. * 关于自定义注解的完整案例
  7. */
  8. @Target({ElementType.METHOD,ElementType.ANNOTATION_TYPE})
  9. @Retention(RetentionPolicy.RUNTIME)
  10. public @interface MyAnnotation2 {
  11. //自定义属性 定义一个String类型的属性,没有默认值
  12. public abstract String value();
  13. //定义int类型的属性,没有默认值
  14. int age();
  15. //定义String类型的数组,没有默认值
  16. String[] names();
  17. }

2)在定义一个Test03类,然后在这个类中定义方法method,在这个方法上使用注解MyAnnotation2 并给属性赋值。记住,在开发中,这个类是我们自己写的。

  1. package com.SiyualChen.day03.test05;
  2. public class Test03 {
  3. @MyAnnotation2(age=10,names={"chen","Siyuan"},value = "哈哈哈")
  4. public void method(){
  5. System.out.println("method.....");
  6. }
  7. }

3)在定义解析类,用来解析注解上的属性值,注意,在开发中这个解析类是框架底层定义的,不需要我们自己书写。

  1. package com.SiyualChen.day03.test05;
  2. import java.lang.reflect.Method;
  3. public class MyAnnotationParser {
  4. //当框架执行时,执行main方法内部的代码
  5. public static void main(String[] args) throws NoSuchMethodException {
  6. //使用反射技术解析使用了注解的类或者方法
  7. //1.获得类的字节码文件对象
  8. Class<Test03> test03Class = Test03.class;
  9. //在Class类中存在getAnnotation方法,可以根据注解的字节码获取注解
  10. // MyAnnotation2 ann = (MyAnnotation2) clazz.getAnnotation(MyAnnotation2.class);
  11. // System.out.println(ann.age());
  12. //2.获得使用注解的方法
  13. Method m = test03Class.getDeclaredMethod("method");
  14. //3.获得该方法上的注解对象,与字节码相关的对象上都存在一个getAnnotation方法
  15. MyAnnotation2 annotation = m.getAnnotation(MyAnnotation2.class);
  16. //4.获得注解的属性值names={"chen","Siyuan"}
  17. System.out.println(annotation.names());
  18. String[] names = annotation.names();
  19. for (String name:names) {
  20. System.out.println(name);
  21. }
  22. //age;
  23. int age = annotation.age();
  24. System.out.println(age);
  25. //value
  26. String value = annotation.value();
  27. System.out.println(value);
  28. //5.获得属性值后根据业务需求做一些功能
  29. }
  30. }

说明:在Class类和Method类中都存在方法:T getAnnotation(Class annotationClass) 表示获得当前对象上指定的注解。
**

还有个重磅栗子明天上!

**

发表评论

表情:
评论列表 (有 0 条评论,346人围观)

还没有评论,来说两句吧...

相关阅读

    相关 反射注解

    目录 一、反射——框架设计的灵魂 二、反射获取字节码Class对象的三种方式 三、Class对象的功能概述 四、注解 4.1 JDK中预定义的注解 1.限定父类重

    相关 注解反射

    注解(Annotation)是Java中的一种元数据,它提供了一种为程序元素(类、方法、变量等)添加元数据的方式。注解可以用来描述程序元素的特性、用途和约束条件等信息。Java

    相关 注解反射

    注解(Annotation)是Java中的一种元数据,它提供了一种为程序元素(类、方法、变量等)添加元数据的方式。注解可以用来描述程序元素的特性、用途和约束条件等信息。Java

    相关 反射注解

    反射与注解 一、反射 1. 使用反射机制可以动态的获取当前class的信息,比如方法的信息、注解信息、方法的参数、属性 2. 反射目的:方便开发者对框架的拓展,

    相关 反射&注解

    今日目标 1. 获得字节码对象的三种方式 2. 能够使用反射操作构造方法,变量,方法 3. 能够获取自定义注解的变量值 4. 能够自定义注解 1.反射 1.