Java-反射机制详解

一、Java反射机制

一)Java反射机制概述

1. 静态语言与动态语言
  • 动态语言
    • 是一类在运行时可以改变结构的语言,比如新的对象、函数、代码在运行时被引进,已有的函数可以被删除或在结构上被改变。换句话说,就是在运行时,允许改变程序的结构或者变量类型
    • 主要语言:C#、JavaScript、PHP、Python等
  • 静态语言
    • 与动态语言相对,在运行时不能改变结果或数据类型的的语言叫做静态语言
    • 例如:C、C++
  • Java语言
    • 是“准动态语言”,Java的动态性主要依靠反射来实现
2. Java反射机制
  • 什么是Java反射机制

    • 在运行状态时,对于任意一个类,如果我们想知道这个类有哪些方法、获得这个类的对象、使用这个类的方法会字段,以普通手段肯定是完不成的,这时就要依靠反射机制了
  • 反射机制的作用

    • 在运行时判断一个对象所属的类
    • 在运行时构造一个类的对象
    • 在运行时判断一个类的成员变量与方法
    • 在运行时获取泛型
    • 在运行时获取注解
    • 在运行时调用对象的字段与方法
    • ……
  • 反射机制的优缺点

    • 优点

    动态实现对象的创建于编译,有很大的灵活性

    • 缺点

    对性能有影响,反射相当于一种解释操作,是我们告诉JVM我们希望做什么,然后JVM才会实现。慢于直接执行相同的操作

  • Java反射机制主要API

    • 在JDK中,实现Java反射机制的类都位于java.lang.reflect包中,一共有5个类:Class类、Field类、Method类、Constructor类、Array类
    • Class类:代表类
    • Field类:代表类的成员变量
    • Method类:代表类的方法
    • Constructor类:代表类的构造器
    • Array类:提供了动态创建数组,以及动态访问数组的元组的静态方法

二)Class类与Class类对象

Class类是反射的基础

1. Class类
  • 作用

    • Class类也是一个类,也可以实例化得到对象,而于普通类不同的是,Class类中存储的是其他类的类型信息(类、成员变量、方法、构造器等),而这些类型信息正是反射机制所需要的
    • 每一个类都有一个相对应的Class类对象,用来存储当前类的类型信息。类先被编译生成.class文件,当这个类被引用的时候(创建对象、静态变量被引用等),类加载器就会将类的.class文件加载到内存中,在加载类时,如果类的Class对象还没有创建,就会优先创建一个该类的Class对象,这个Class对象是这个类唯一的,用来存储当前类的类型信息。一旦该对象被创建,就会使用该Class对象来创建该类的所有对象(也就是说,一个类的所有对象都是使用一个Class对象创建的),所以得到一个类的Class类对象,就能通过Class对象得到该类的所有对象及成员等信息,Class类是反射机制的根源
  • 那些类型可以拥有Class类对象

    • class(包括外部类、内部类、匿名内部类)、interface、数组、enum、注解、基本数据类型、void
    • 代码示例:获取各个类型的Class类对象
    public class GetEachTypeClassObject {//获取各类型的Class类对象
    
        public static void main(String[] args) {
            Class c1 = Object.class; //类
            Class c2 = Comparable.class; //接口
            Class c3 = String[].class; // 一维数组
            Class c4 = String[][].class; // 二维数组
            Class c5 = ElementType.class; //枚举类型
            Class c6 = Override.class; // 注解
            Class c7 = Integer.class; //基本数据类型
            Class c8 = void.class; //void
            Class c9 = Class.class; //反射对象本身
    
            System.out.println(c1);
            System.out.println(c2);
            System.out.println(c3);
            System.out.println(c4);
            System.out.println(c5);
            System.out.println(c6);
            System.out.println(c7);
            System.out.println(c8);
            System.out.println(c9);
        }
    }
    /*运行结果:
    class java.lang.Object
    interface java.lang.Comparable
    class [Ljava.lang.String;
    class [[Ljava.lang.String;
    class java.lang.annotation.ElementType
    interface java.lang.Override
    class java.lang.Integer
    void
    class java.lang.Class*/
  • 常用方法

方法名方法说明
static Class forName(String name)返回指定类的Class对象
Object newInstance()调用默认的无参构造,返回CLass对象
getName()返回此Class对象所表示实体(类、接口、数组等)的名称
Class getSuperClass()得到当前Class对象的父类的Class对象
Class[] getinterfaces()得到当前Class对象的所有接口
ClassLoader getClassLoader()得到Class对象所表示类的默认无参构造
Constructor[] getConstructors()得到Class对象所表示的类的所有构造
Method getMothed(String name,Class… T)得到Class对象表示的类的指定的方法
Field[] getDeclaredFields()得到Class对象所表示的类的所有成员变量,包括私有的
2. 获取Class类的对象

一共有3种方法,Class类的forName()方法、类字面常量(类名调用.class属性)、对象的getClass()方法、基本数据类型调用 类名.Type

  • Class类的forName()方法:已知类的全类名,使用全类名调用forName()方法,可能会抛出异常

  • 类字面常量(类名调用.class):已知具体的类,使用类的class属性获取,此方法最为安全可靠、程序性能高

  • 对象的getClass()方法:已知类的对象,使用类的对象调用getClass()方法,得到类的Class对象

  • 内置基本数据类型:调用Type属性

  • 代码演示:获取一个类的Class对象

package edu.xiyou.reflections;

public class CreateClassObject {//创建自定义类的Class类对象

    public static void main(String[] args) throws ClassNotFoundException {
        //使用Class类的forName()方法创建对应的Class类对象
        Class class1 = Class.forName("edu.xiyou.reflections.PersonClass");
        //使用自定义类的类名调用class属性得到对应的Class类对象
        Class class2 = PersonClass.class;
        //使用自定义类的对象调用getClass()方法得到对应的Class类对象
        Class class3 = new PersonClass().getClass();
        //得到基本类型包装类的Class类对象
        Class type = Integer.TYPE;

        System.out.println(class1);
        System.out.println(class2);
        System.out.println(class3);
        System.out.println(type);
    }
}
/*运行结果:
  class edu.xiyou.reflections.PersonClass
  class edu.xiyou.reflections.PersonClass
  class edu.xiyou.reflections.PersonClass
  int*/

/**
 * 自定义的类
 */
class PersonClass {
    private String name;
    private int ID;
    private int age;

    public PersonClass(String name, int ID, int age) {
        this.name = name;
        this.ID = ID;
        this.age = age;
    }

    public PersonClass() {
    }

    public String getName() {
        return name;
    }

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

    public int getID() {
        return ID;
    }

    public void setID(int ID) {
        this.ID = ID;
    }

    public int getAge() {
        return age;
    }

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

    @Override
    public String toString() {
        return "PersonClass{" +
                "name='" + name + '\'' +
                ", ID=" + ID +
                ", age=" + age +
                '}';
    }
}

三)类的加载与类加载器ClassLoader

1. Java内存分析
  • 堆:存放所有new出来的东西(引用类型),可以被所有的线程所共享
  • 栈:存放基本变量类型(含值)和引用数据类型的变量名(对象的地址),是线程私有的
  • 方法区:包含了所有的class和静态的东西,可以被所有的线程所共享
2. 类的加载过程
  • 类的加载(Load)

    将类的class文件读入内存,并且将类中的静态数据转换成方法区的运行时数据结构,生成一个代表该类的Class对象。此过程由类加载器完成。

  • 类的链接(Link)

    将类的二进制代码合并到JVM的与逆行状态中

    • 验证:确保加载的类信息符合JVM的规范,确保没有安全问题
    • 准备:正式为静态变量分配内存并设置变量的默认初始值,静态变量的内存在方法区中进行分配
    • 解析:JVM常量池中的常量名(符号引用)替换为地址(直接引用)
  • 类的初始化(Initialize)

    • 执行类构造器()方法的过程,类构造器clinit()方法由编译期自动收集类中所有的静态变量的赋值过程静态代码块中的语句合并而来的。(类构造器clinit()是用来构造类信息,而不是构造对象)
    • 初始化一个类的时候,如果其父类还没有初始化,先初始化父类
    • 虚拟机保证clinit()方法在多线程的环境中被正确的加锁与同步
3. 类什么时候进行初始化
  • 类的主动引用一定会发生初始化
    • 含有main()方法的类,当虚拟机启动时先初始化
    • 创建一个类的对象
    • 调用类的静态成员变量(除了常量,常量在常量池中)与静态方法
    • 使用java.lang.reflect包中的方法对类进行反射的时候
    • 初始化子类时,父类优先初始化
  • 类的被动引用不会发生初始化
    • 子类引用父类的静态变量不会导致子类初始化。当访问一个静态域时,只有真正声明这个静态域的类才会被初始化
    • 通过数组定义类引用(比如:String[]),不会导致类的初始化
    • 引用常量不会导致类的初始化,因为在链接阶段就将常量存入类的常量池中了
4. 类加载器ClassLoader的作用
  • 作用

    将class文件字节码内容加载到内存中,并且将字节码文件中的静态数据转换成方法区的运行时数据结构,然后在堆中生成代表该类的Class类对象,作为方法区中静态数据的访问入口。

  • 类缓存

    标准的JavaSE类加载器可以按照要求查找类,一旦类被加载到类加载器中,Class对象将维持加载一段时间。JVM回收机制会回收Class对象。

  • 类加载器分类

    • 引导类加载器:JVM自带的类加载器,负责Java的核心类库,无法直接获取
    • 扩展类加载器:负责jre/lib/ext目录下的jar包或-D java.ext.dirs所指定目录下的jar包装入工作库
    • 系统类加载器:负责java -classpath或-D java.class.path所指定目录下的类和jar包装入工作库,是最常用的类加载器

四)反射获取运行时类的结构

​ 通过反射可以得到运行时类的完整结构,包括注解、接口、父类、构造器、字段、方法

1. 获得类的构造器与对象
  • 获得类的构造器

    含有Declared的方法都可以获得私有的。后面加s表示获得所有的集合,不含s只能获得指定的单个。其他的获得方法、字段等的方法也一样

    方法方法描述
    getConstructor()根据参数列表得到本类中指定的构造器(不含私有)
    getConstructors()得到本类中所有的构造器(不含私有)
    getDeclaredConstructor()根据参数列表得到本类中指定的构造器(包含私有)
    getDeclaredConstructors()得到本类中所有的构造器(包含私有)
    newInstance()使用Class对象或类的构造器调用此方法,得到一个该类的对象
    setAccessible(true)用来开启私有的字段、方法、构造器等的访问,参数true表示已经开启
  • 代码示例:创建对象,并打印对象

    使用反射得到的对象与正常new出来的对象使用方法一致,可以调用方法、给字段赋值等。

    public class CreateObject {
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
            //获得类的Class对象
            Class personClass = PersonClass2.class;//获得类对应的Class类对象
    
            //使用类的Class对象得到类的对象
            PersonClass2 liSi = (PersonClass2) personClass.newInstance();//调用的是默认的无参构造
            liSi.setName("李四");
            liSi.setID(1);
            liSi.setAge(24);
            System.out.println(liSi);
    
            //使用构造器得到类的对象
            /*获得类构造器的方法
            personClass2Class.getDeclaredConstructor();//根据参数列表得到本类中指定的构造器(包含私有)
            personClass2Class.getDeclaredConstructors();得到本类中所有的构造器(包含私有)
            personClass2Class.getConstructor();//根据参数列表的阿斗本类中指定的构造器(不含私有)
            personClass2Class.getConstructors();//得到本类中所有的构造器(不含私有)
            */
            Constructor constructor = personClass.getDeclaredConstructor(String.class, int.class, int.class);//根据参数列表得到本类中指定的构造器(包含私有)
            constructor.setAccessible(true);//因为类中的成员可能是私有的,需要使用setAccessible()方法破坏私有
            //调用有参构造器的newInstance()方法 创建该类的对象
            PersonClass2 zhangSan = (PersonClass2) constructor.newInstance("张三", 2, 23);
            System.out.println(zhangSan);//打印对象属性
        }
    }
    /*
    运行结果:
    PersonClass{name='李四', ID=1, age=24}
    PersonClass{name='张三', ID=2, age=23}
    */
    
    /**
     * 自定义的类
     */
    class PersonClass2 {
        private String name;
        private int ID;
        private int age;
    
        private PersonClass2(String name, int ID, int age) {//私有有参构造
            this.name = name;
            this.ID = ID;
            this.age = age;
        }
    
        //公开无参构造
        public PersonClass2() {
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getID() {
            return ID;
        }
    
        public void setID(int ID) {
            this.ID = ID;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "PersonClass{" +
                    "name='" + name + '\'' +
                    ", ID=" + ID +
                    ", age=" + age +
                    '}';
        }
    }
2. 获取类的字段与方法
  • 获得字段与方法的前提是获得Class类的对象,在使用字段与方法的时候,需要传入操作的对象和参数值

    方法方法说明
    getField();得到指定的字段(不含本类私有)
    getFields();得到所有的字段(不含本类私有)
    getDeclaredField();得到指定的字段(含本类私有)
    getDeclaredFields();得到所有的字段(含本类私有)
    getMethod();获得指定名称的方法(父类及自己的,不含私有)
    getMethods();获得所有的方法(父类及自己的,不含私有)
    getDeclaredMethod();获得指定的方法(自己本类的,含私有)
    getDeclaredMethods();获得所有方法(自己本类的,含私有)
    invoke();调用指定对象的方法
  • 代码练习:使用反射得到的字段和方法分别为对象进行赋值

public class GetFiledMethod {//获取类的字段与方法

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建Class对象
        Class personClass = PersonClass3.class;
        //创建自定义类的对象
        PersonClass3 zhangSan = (PersonClass3) personClass.newInstance();

        /*获得字段的方法
        personClass.getField("name");//得到指定的字段(不含本类私有)
        personClass.getFields();//得到所有的字段(不含本类私有)
        personClass.getDeclaredField("name");//得到指定的字段(含本类私有)
        personClass.getDeclaredFields();//得到所有的字段(含本类私有)
        */
        //得到自定义类的字段
        Field nameField = personClass.getDeclaredField("name");
        Field idField = personClass.getDeclaredField("ID");
        Field ageField = personClass.getDeclaredField("age");
        //破坏私有性
        nameField.setAccessible(true);
        idField.setAccessible(true);
        ageField.setAccessible(true);
        //赋值
        nameField.set(zhangSan, "张三");
        idField.setInt(zhangSan, 1);
        ageField.setInt(zhangSan, 23);
        //打印对象的字段信息
        System.out.println(zhangSan);

        /*获得成员方法的方法
        personClass.getMethod();//获得指定名称的方法(父类及自己的,不含私有)
        personClass.getMethods();//获得所有的方法(父类及自己的,不含私有)
        personClass.getDeclaredMethod();//获得指定的方法(自己本类的,含私有)
        personClass.getDeclaredMethods();//获得所有方法自己本类的,含私有)
        invoke();//调用目标对象的方法
        */
        //得到私有的成员方法setValues()
        Method setValues = personClass.getDeclaredMethod("setValues", String.class, int.class, int.class);
        PersonClass3 liSi = (PersonClass3) personClass.newInstance();//得到对象liSi
        setValues.setAccessible(true);//破除方法的私有性
        setValues.invoke(liSi, "李四", 2, 24);//使用方法对对象liSi进行赋值
        System.out.println(liSi);
    }
}
/*运行结果:
PersonClass{name='张三', ID=1, age=23}
PersonClass{name='李四', ID=2, age=24}
*/

/**
 * 自定义的类
 */
class PersonClass3 {
    private String name;
    private int ID;
    private int age;

    private PersonClass3(String name, int ID, int age) {//私有有参构造
        this.name = name;
        this.ID = ID;
        this.age = age;
    }

    //公开无参构造
    public PersonClass3() {
    }

    public String getName() {
        return name;
    }

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

    public int getID() {
        return ID;
    }

    public void setID(int ID) {
        this.ID = ID;
    }

    public int getAge() {
        return age;
    }

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

    private void setValues(String name, int ID, int age) {
        this.name = name;
        this.ID = ID;
        this.age = age;
    }

    @Override
    public String toString() {
        return "PersonClass{" +
                "name='" + name + '\'' +
                ", ID=" + ID +
                ", age=" + age +
                '}';
    }
}
3. 获取类的泛型

主要是获得方法的泛型,可以分为方法参数的泛型与方法返回值的泛型

方法方法说明
getGenericParameterTypes();获得方法中的所有的参数
getActualTypeArguments();获得某一个参数中的具体的泛型类型
getGenericReturnType();h获得方法的返回值的类型
  • 代码练习:获得并打印方法返回值与参数中的泛型类型
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

public class GetGeneric {
    public static void main(String[] args) throws NoSuchMethodException {
        //获得指定方法的参数泛型信息
        //获取指定的方法
        Method method = MyTest.class.getMethod("test", Map.class, List.class);
        //获得方法的所有参数
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        //遍历方法的所有参数
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("方法参数:" + genericParameterType);//打印参数名称
            //判断,如果当前参数的类型属于泛型
            if (genericParameterType instanceof ParameterizedType) {
                //获得当前参数中的所有泛型类型
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                //遍历打印所有的参数泛型类型
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println("参数泛型类型:" + actualTypeArgument);
                }
            }
        }

        //获得指定的方法的返回值泛型信息
        //得到指定的方法
        Method test02 = MyTest.class.getDeclaredMethod("test02", null);
        Type genericReturnType = test02.getGenericReturnType();  //获得返回值类型
        //判断返回值的类型
        if (genericReturnType instanceof ParameterizedType) {
            //如果返回值是泛型,得到泛型中的所有类型
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            //遍历打印
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println("返回值泛型类型:" + actualTypeArgument);
            }
        }
    }
}
/*运行结果:
方法参数:java.util.Map<java.lang.String, edu.xiyou.reflections.MyTest>
参数泛型类型:class java.lang.String
参数泛型类型:class edu.xiyou.reflections.MyTest
方法参数:java.util.List<edu.xiyou.reflections.MyTest>
参数泛型类型:class edu.xiyou.reflections.MyTest
返回值泛型类型:class java.lang.Integer
返回值泛型类型:class edu.xiyou.reflections.MyTest
*/

class MyTest {//自定义测试类
    public void test(Map<String, MyTest> map, List<MyTest> list) {
        System.out.println("test01");
    }

    public Map<Integer, MyTest> test02() {
        System.out.println("test02");
        return null;
    }
}
4. 获取类的注解
  • 代码练习:使用反射得到类中的注解
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ReadAnnotations {//读取ParamAnnotation类中的注解

    public static void main(String[] args) throws ClassNotFoundException {
        getTypeAnnotations();//得到类注解信息
        getFieldAnnotations();//得到字段注解信息
        getMethodAnnotations();//得到方法注解信息
    }

    //得到类上的注解内容
    private static void getTypeAnnotations() throws ClassNotFoundException {
        Class pAClass = Class.forName("edu.xiyou.annotations.ParamAnnotation");//得到类的Class变量

        Annotation[] annotations = pAClass.getAnnotations();//得到类的所有注解
        for (Annotation annotation : annotations) {//遍历注解
            MyAnnotation3 myAnnotation3 = (MyAnnotation3) annotation;//强制转换成自定义注解类型

            //打印注解信息
            System.out.println("类注解\t名字:" + myAnnotation3.name() +
                    "\t类型:" + myAnnotation3.type() +
                    "\t描述:" + myAnnotation3.describe());
        }
    }

    //得到字段上的注解内容
    private static void getFieldAnnotations() {
        Field[] fields = ParamAnnotation.class.getFields();//得到类中的所有字段
        for (Field field : fields) {//遍历字段
            boolean b = field.isAnnotationPresent(MyAnnotation3.class);//判断字段是否含有注解

            if (b) {//如果有
                MyAnnotation3 annotation = field.getAnnotation(MyAnnotation3.class);//得到这个注解
                //打印注解的信息
                System.out.println("字段注解\t名字:" + annotation.name() +
                        "\t类型:" + annotation.type() +
                        "\t描述:" + annotation.describe());
            }
        }
    }

    //得到方法上的注解内容
    private static void getMethodAnnotations() {
        Method[] methods = ParamAnnotation.class.getMethods();//得到类中的所有方法
        for (Method method : methods) {//遍历方法
            boolean b = method.isAnnotationPresent(MyAnnotation3.class);//判断方法是否含有注解

            if (b) {//如果有
                MyAnnotation3 annotation = method.getAnnotation(MyAnnotation3.class);//拿到注解
                //打印注解信息
                System.out.println("方法注解\t名字:" + annotation.name() +
                        "\t类型:" + annotation.type() +
                        "\t描述:" + annotation.describe());
            }
        }
    }
}

/*运行结果
类注解		名字:ParamAnnotation	类型:	描述:注解参数的测试类
字段注解	名字:zhangSan	类型:String	描述:测试字段
方法注解	名字:Test	类型:	描述:我的测试方法*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值