Javaの反射

反射

第一章 反射的理解

1.1 正射

我们在编写代码时,当需要使用到某一个类的时候,必定先会去了解这是一个什么类,是用来做什么的,有怎么样的功能。之后我们才对这个类进行实例化,之后再使用这个类的实例化对象进行操作。

Person person = new Person();
person.sleep(“8:00”);

1.2 反射

反射则是在代码一开始编写时不知道要初始化的类是什么。因此,自然也无法使用new关键字来创建对象了。而当我们之后得到我们要初始化的类的名称及路径时,我们就可以使用JDK提供的反射API进行反射调用。
Class clazz = Class.forName(“com.Person”);
Method method = clazz.getMethod(“sleep”, String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, “8:00”);
以上两段代码,其结果都是一样的,但是其实现的过程却有很大的差别:

  • 第一段代码在未运行前就已经确定了要运行的类(Person);
  • 第二段代码则是在整个程序运行时从某些地方(例:配置文件)获取到相应的字符串值才能知道要运行的类(”com.Person”)。
    第二章 反射常用类和常用函数
    2.1 获取Class对象
    在反射中,要获取一个类或调用一个类的方法,首先必须要获取到该类的对象,在Java API中,获取Class类对象三种方法:
    ①:Class.forName(“类的路径名”);
    Class clazz = Class.forName(“com.test.domain.Person”);
    ②:利用已有类对象的getClass()方法;
    Person person = new Person();
    Class clazz = person.getClass();
    ③:对于在编译前就已经知道的类,可以使用.class属性;
    Class clazz = Person.class;
    同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的 Class对象都是同一个。
    2.2 Constructor类
    Constructor是构造方法类,类中的每一个构造方法都是Constructor的对象,通过Constructor对象可以实例化对 象。
  • Constructor getConstructor(Class… parameterTypes) 根据参数类型获取构造方法对象,只能获得public修饰的构造方法。
  • Constructor getDeclaredConstructor(Class… parameterTypes) 根据参数类型获取构造方法对象,包括private修饰的构造方法。
  • Constructor[] getConstructors() 获取所有的public修饰的构造方法
  • Constructor[] getDeclaredConstructors() 获取所有构造方法,包括privat修饰的
    2.3 通过反射创建类对象
    ①:通过class对象的newInstance()方法
    Class clazz = Class.forName(“com.Person”);
    Person person = (Person)clazz.newInstance();
    ②:通过Constructor对象的newInstance()方法;
    Class clazz = Class.forName(“com.Person”);
    Constructor con = clazz.getConstructor();
    Person person = (Person)con.newInstance();
    2.4 Method类
    Method是方法类,类中的每一个方法都是Method的对象,通过Method对象可以调用方法。
    Class类中与Method相关方法:
  • Method getMethod(“方法名”, 方法的参数类型… 类型) 根据方法名和参数类型获得一个方法对象,只能是获取public修饰的
  • Method getDeclaredMethod(“方法名”, 方法的参数类型… 类型) 根据方法名和参数类型获得一个方法对象,包括private修饰的
  • Method[] getMethods() 获取所有的public修饰的成员方法,包括父类中。
  • Method[] getDeclaredMethods() 获取当前类中所有的方法,包含私有的,不包括父类中。
    Method类中常用方法:
  • Object invoke(Object obj, Object… args) 根据参数args调用对象obj的该成员方法 如果obj=null,则表示该方法是静态方法
  • void setAccessible(boolean flag) 暴力反射,设置为可以直接调用私有修饰的成员方法
    2.5 通过反射操作成员变量
    ①:获取所有成员getFields()&getDeclaredFields();
    使用getFields()方法可以获取Class类的成员变量,但是无法获取私有属性。
    使用getDeclaredFields()方法可以获取Class类的所有成员变量。
    Class clazz = Class.forName(“com.Person”);
    Field[] fields = clazz.getFields();
    for (Field field : fields) {
    System.out.print(field.getName());
    }
    ②:获取单个成员getField(String name)&getDeclared(String name)
    ③:修改成员变量的值set(Object obj, Object value)
    Class clazz = Class.forName(“com.test.domain.Person”);
    Person person = (Person)clazz.newInstance();
    Field field = clazz.getField(“name”);
    field.set(person, “张三”);
    ④:当属性为private时,这是我们无法直接使用set()方法修改它的值,此时应该使用setAccessible()方法取得访问权限:
    Class clazz = Class.forName(“com.Person”);
    Person person = (Person)clazz.newInstance();
    Field field = clazz.getDeclaredField(“name”);
    field.setAccessible(true);
    field.set(person, “张三”);
    第三章 反射案例
    需求:写一段程序,在不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。
    步骤:
  • 将需要创建的对象的全类名和需要执行的方法定义在配置文件中
  • 在程序中加载读取配置文件
  • 使用反射技术来加载类文件进内存
  • 创建对象
  • 执行方法
    1.pro.properties
    className=com.Student
    methodName=sleep
    2.Student.java
    public class Student {
    public void sleep(){
    System.out.println(“sleep…”);
    }
    }
    3.Test.java
    public class Test {
    public static void main(String[] args) {
    //1.加载配置文件
    //1.1创建Properties对象
    Properties pro = new Properties();
    //1.2加载配置文件,转换为一个集合
    //1.2.1获取class目录下的配置文件
    ClassLoader classLoader = Test.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream(“pro.properties”);
    pro.load(is);
    //2.获取配置文件中定义的数据
    String className = pro.getProperty(“className”);
    String methodName = pro.getProperty(“methodName”);
    //3.加载该类进内存
    Class cls = Class.forName(className);
    //4.创建对象
    Object obj = cls.newInstance();
    //5.获取方法对象
    Method method = cls.getMethod(methodName);
    //6.执行方法
    method.invoke(obj);
    }
    }
    }

注解

第一章 注解概述

1.1 定义

注解(Annotation),也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

1.2 作用分类

  • 编写文档:通过代码里标识的注解生成文档【例如,生成文档doc文档】
  • 代码分析:通过代码里标识的注解对代码进行分析【例如,注解的反射】
  • 编译检查:通过代码里标识的注解让编译器能够实现基本的编译检查【例如,Override】

1.3 常见注解

  • @author:用来标识作者名。

  • @version:用于标识对象的版本号,适用范围:文件、类、方法。

  • @Override :用来修饰方法声明,告诉编译器该方法是重写父类中的方法,如果父类不存在该方法,则编译失败。

第二章 自定义注解

定义格式:
元注解
public @interface 注解名称{
属性列表;
}
注解本质上就是一个接口,该接口默认继承Annotation接口。

2.1 注解的属性

    1. 属性的作用
      可以让用户在使用注解时传递参数,让注解的功能更加强大。
    1. 属性的格式
      格式1:数据类型 属性名();
      格式2:数据类型 属性名() default 默认值;
    1. 属性定义示例
      public @interface Student {
      String name(); // 姓名
      int age() default 18; // 年龄
      String gender() default “男”; // 性别
      }
      // 该注解就有了三个属性:name,age,gender
  1. 属性适用的数据类型
  • 八种基本数据类型(int,float,boolean,byte,double,char,long,short)

  • String类型,Class类型,枚举类型,注解类型

  • 以上所有类型的一维数组

2.2 使用自定义注解

定义注解:Book

  • 包含属性:String value() 书名

  • 包含属性:double price() 价格,默认值为 100

  • 包含属性:String[] authors() 多位作者
    代码实现:
    public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 多位作者
    String[] authors();
    }
    使用注解:
    public class BookShelf {
    @Book(value = “西游记”,price = 998,authors = {“吴承恩”,”白求恩”})
    public void showBook(){
    }
    }
    如果属性有默认值,则使用注解的时候,这个属性可以不用赋值。
    如果属性没有默认值,那么在使用注解时一定要给属性赋值。

第三章 注解之元注解

默认情况下,注解可以用在任何地方,比如类,成员方法,构造方法,成员变量等地方。如果要限制注解的使用位置怎么办?那就要学习一个新的知识点:元注解。

  • @Target

  • @Retention

3.1 元注解之@Target

作用:指明此注解用在哪个位置,如果不写默认是任何地方都可以使用。
可选的参数值在枚举类ElemenetType中包括:

  • TYPE: 用在类,接口上

  • FIELD:用在成员变量上

  • METHOD: 用在方法上

  • PARAMETER:用在参数上

  • CONSTRUCTOR:用在构造方法上

  • LOCAL_VARIABLE:用在局部变量上

3.2 元注解之@Retention

作用:定义该注解的生命周期(有效范围)。
可选的参数值在枚举类型RetentionPolicy中包括:

  • SOURCE:注解只存在于Java源代码中,编译生成的字节码文件中就不存在了。

  • CLASS:注解存在于Java源代码、编译以后的字节码文件中,运行的时候内存中没有,默认值。

  • RUNTIME:注解存在于Java源代码中、编译以后的字节码文件中、运行时内存中,程序可以通过反射获取该注解。

3.3 元注解使用示例

@Target({ElementType.METHOD,ElementType.TYPE})
@interface Stu{
String name();
}
// 类
@Stu(name=”jack”)
public class AnnotationDemo02 {
// 成员变量
@Stu(name = “lily”) // 编译失败
private String gender;
// 成员方法
@Stu(name=”rose”)
public void test(){
}
// 构造方法
@Stu(name=”lucy”) // 编译失败
public AnnotationDemo02(){}
}

第四章 注解解析

通过java技术获取注解数据的过程叫做注解解析。

4.1 与注解解析相关的接口

  • Anontation:所有注解类型的公共接口,类似所有类的父类是Object。
  • AnnotatedElement:定义了与注解解析相关的方法,常用方法以下四个
    • boolean isAnnotationPresent(Class annotationClass); 判断当前对象是否有指定的注解,有则返回true,否则 返回false。
    • T getAnnotation(Class annotationClass); 获得当前对象上指定的注解对象。
    • Annotation[] getAnnotations(); 获得当前对象及其从父类上继承的所有的注解对象。
    • Annotation[] getDeclaredAnnotations();获得当前对象上所有的注解对象,不包括父类的。

4.2 获取注解数据的原理

注解作用在哪个成员上,就可以通过反射获取该成员的对象,通过该成员对象获取该成员的注解。
如注解作用在方法上,就通过方法(Method)对象得到它的注解:
// 得到方法对象
Method method = clazz.getDeclaredMethod(“方法名”);
// 根据注解名得到方法上的注解对象
Book book = method.getAnnotation(Book.class);
如注解作用在类上,就通过Class对象得到它的注解:
// 获得Class对象
Class c = 类名.class;
// 根据注解的Class获得使用在类上的注解对象
Book book = c.getAnnotation(Book.class);

第五章 使用反射获取注解的数据

需求说明

  1. 定义注解Book,要求如下:
  • 包含属性:String value() 书名
  • 包含属性:double price() 价格,默认值为 100
  • 包含属性:String[] authors() 多位作者
  • 限制注解使用的位置:类和成员方法上
  • 指定注解的有效范围:RUNTIME
  1. 定义BookStore类,在类和成员方法上使用Book注解

  2. 定义TestAnnotation测试类获取Book注解上的数据
    代码实现
    注解Book
    @Target({ElementType.METHOD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Book {
    // 书名
    String value();
    // 价格
    double price() default 100;
    // 作者
    String[] authors();
    }
    BookStore类
    @Book(value = “红楼梦”,authors = “曹雪芹”,price = 998)
    public class BookStore {

    @Book(value = “西游记”,authors = “吴承恩”)
    public void buyBook(){
    }
    }
    TestAnnotation类
    public class TestAnnotation {
    public static void main(String[] args) throws Exception{
    System.out.println(“———获取类上注解的数据———-“);
    test01();
    System.out.println(“———获取成员方法上注解的数据———-“);
    test02();
    }
    /**

  • 获取BookStore类上使用的Book注解数据
    /
    public static void test01(){
    // 获得BookStore类对应的Class对象
    Class c = BookStore.class;
    // 判断BookStore类是否使用了Book注解
    if(c.isAnnotationPresent(Book.class)) {
    // 根据注解Class对象获取注解对象
    Book book = (Book) c.getAnnotation(Book.class);
    // 输出book注解属性值
    System.out.println(“书名:” + book.value());
    System.out.println(“价格:” + book.price());
    System.out.println(“作者:” + Arrays.toString(book.authors()));
    }
    }
    /
    *

  • 获取BookStore类成员方法buyBook使用的Book注解数据
    */
    public static void test02() throws Exception{
    // 获得BookStore类对应的Class对象
    Class c = BookStore.class;
    // 获得成员方法buyBook对应的Method对象
    Method m = c.getMethod(“buyBook”);
    // 判断成员方法buyBook上是否使用了Book注解
    if(m.isAnnotationPresent(Book.class)) {
    // 根据注解Class对象获取注解对象
    Book book = (Book) m.getAnnotation(Book.class);
    // 输出book注解属性值
    System.out.println(“书名:” + book.value());
    System.out.println(“价格:” + book.price());
    System.out.println(“作者:” + Arrays.toString(book.authors()));
    }
    }
    }
    作业
    1.定义Person类,私有成员变量有name,age,公开成员变量有sex,成员方法包括gette和setter,构造方法有无参和全参,用反射去创建一个Person对象,用2种方式。
    2.在第一题中加入方法show,打印一串字符串,利用反射执行该方法。
    4.在第一题基础之上,利用反射设置name为“张三”,sex为“男”。
    Person.java
    public class Person {
    private String name;
    private int age;
    private String sex;

    @Override
    public String toString() {
    return “Person{“ +
    “name=’” + name + ‘'‘ +
    “, age=” + age +
    “, sex=’” + sex + ‘'‘ +
    ‘}’;
    }

    public void gogogo(){
    System.out.println(“通过读取配置文件得到的gogogo方法在运行!”);
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getSex() {
    return sex;
    }

    public void setSex(String sex) {
    this.sex = sex;
    }

    public Person() {
    }

    public Person(String name, int age, String sex) {
    this.name = name;
    this.age = age;
    this.sex = sex;
    }
    public void show(){
    System.out.println(“这是一个无参的show方法”);
    }
    }
    public class Demo1 {
    public static void main(String[] args) throws Exception{
    //1.定义Person类,私有成员变量有name,age,公开成员变量有sex,成员方法包括gette和setter,
    // 构造方法有无参和全参,用反射去创建一个Person对象,用2种方式。
    //第一种
    Class clazz = Class.forName(“com.hmwk124.Person”);
    Person p = (Person)clazz.newInstance();
    //第二种
    Class clazz1 = Class.forName(“com.hmwk124.Person”);
    Constructor constructor = clazz.getConstructor();
    Person p1 = (Person)constructor.newInstance();
    //2.在第一题中加入方法show,打印一串字符串,利用反射执行该方法。
    Method show = clazz1.getMethod(“show”);
    show.invoke(p1);
    //4.在第一题基础之上,利用反射设置name为“张三”,sex为“男”。
    Field name = clazz1.getDeclaredField(“name”);
    Field sex = clazz1.getDeclaredField(“sex”);
    name.setAccessible(true);
    name.set(p1,”张三”);
    sex.setAccessible(true);
    sex.set(p1,”男”);
    Method toString = clazz1.getMethod(“toString”);
    System.out.println(toString.invoke(p1));
    }
    }
    3.编写一个类A,增加一个实例方法showString,用于打印一条字符串,再编写一个测试类TestA ,在测试类中,用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创建该类的对象,并调用该对象中的方法showString。
    //3.编写一个类A,增加一个实例方法showString,用
    // 于打印一条字符串,再编写一个测试类TestA ,在测试类中,
    // 用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创
    // 建该类的对象,并调用该对象中的方法showString。
    public class A {
    public void showString(){
    System.out.println(“这是A类的showString方法!”);
    }
    }
    public class TestA {
    //3.编写一个类A,增加一个实例方法showString,用
    // 于打印一条字符串,再编写一个测试类TestA ,在测试类中,
    // 用键盘输入一个字符串,该字符串就是类A的全名,使用反射机制创
    // 建该类的对象,并调用该对象中的方法showString。
    public static void main(String[] args) {
    String s;
    Scanner sc = new Scanner(System.in);
    System.out.print(“请输入完整的类名:”);
    s = sc.nextLine();
    Class clazz = null;
    try {
    clazz = Class.forName(s);
    Constructor constructor = clazz.getConstructor();
    A a = (A) constructor.newInstance();
    a.showString();
    } catch (ClassNotFoundException e) {
    System.out.println(“找不到该类!”);
    } catch (NoSuchMethodException e) {
    e.printStackTrace();
    } catch (InstantiationException e) {
    e.printStackTrace();
    } catch (IllegalAccessException e) {
    e.printStackTrace();
    } catch (InvocationTargetException e) {
    e.printStackTrace();
    }
    }
    }
    5.在第一题基础上,写一个Properties格式的配置文件,配置类的完整名称和所要执行的方法,写一个程序,读取这个Properties配置文件,获得类的完整名称并加载这个类,并执行读取的方法。
    public class Demo5 {
    //在第一题基础上,写一个Properties格式的配置文件,配置类的完整名
    // 称和所要执行的方法,写一个程序,读取这个Properties配置文件,获
    // 得类的完整名称并加载这个类,并执行读取的方法。
    public static void main(String[] args) throws Exception {
    Properties pro = new Properties();
    ClassLoader cll = Demo5.class.getClassLoader();
    InputStream is = cll.getResourceAsStream(“com/hmwk124/pro.properties”);
    pro.load(is);
    String className = pro.getProperty(“className”);
    String methodName = pro.getProperty(“methodName”);
    Class clazz = Class.forName(className);
    Object o = clazz.newInstance();
    Method method = clazz.getMethod(methodName);
    method.invoke(o);
    }
    }
    配置文件:
    className=com.hmwk124.Person
    methodName=gogogo
    1.自定义注解Hello,包含属性value,name,age(默认值100),设置只能作用于方法和成员变量,生命周期为RUNTIME,创建普通类Person,类中定义成员变量name,方法show(),分别在之上加入注解,利用反射解析注解。
    Hello.java
    @Target({ElementType.METHOD,ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Hello {
    String value();
    String name();
    int age() default 100;
    }
    Person.java
    public class Person {
    @Hello(value = “10”,name=”李四”,age = 20)
    private String name;
    @Hello(value = “100”,name = “张三”,age = 18)
    public void show(){
    }
    }
    Demo6.java
    public class Demo6 {
    public static void main(String[] args) throws Exception {
    System.out.println(“获取成员变量上注解的数据”);
    test01();
    System.out.println(“获取方法上注解的数据”);
    test02();
    }
    //获取Person类的成员变量上使用的Hello注解数据
    public static void test01() throws NoSuchFieldException {
    Class clazz = Person.class;
    Field field = (Field) clazz.getDeclaredField(“name”);
    System.out.println(field.isAnnotationPresent(Hello.class));
    if(field.isAnnotationPresent(Hello.class)){
    Hello hello = (Hello)field.getAnnotation(Hello.class);
    //输出Hello注解属性值
    System.out.println(“value:” + hello.value());
    System.out.println(“name:” + hello.name());
    System.out.println(“age:” + hello.age());
    }
    }
    //获取方法show上对象的Method对象
    public static void test02() throws Exception{
    Class clazz1 = Person.class;
    Method show = clazz1.getMethod(“show”);
    //判断是否使用注解
    if(show.isAnnotationPresent(Hello.class)){
    Hello hello = (Hello)show.getAnnotation(Hello.class);
    //输出注解属性值
    System.out.println(“value:” + hello.value());
    System.out.println(“name:” + hello.name());
    System.out.println(“age:” + hello.age());
    }
    }
    }