JavaSE之——反射


一、静态语言VS动态语言

  • 动态语言
    • 动态语言是指在运行时,代码可以根据某些条件改变自身结构的语言:例如新的函数、对象、代码被引进,已有的函数可以被删除等。
    • 主要的动态语言:Object-C、C#、JavaScript、PHP、Python等
  • 静态语言
    • 静态语言是指在运行时结构不可变的语言,例如:Java、C、C++
    • Java虽然不是动态语言,但可以称之为“准动态语言”,即Java具有一定的动态性,因为我们可以利用反射机制获得类似动态语言的特性。

二、Java Refletion

2.1 反射概念

  • Java之所以被称之为动态语言,是因为有着反射机制,反射机制允许程序在执行期间借助Java Reflection API
    • 获取任何的内部信息
    • 操作任何对象的内部属性和方法
  • 加载完类之后,会在对内存的方法区中产生一个Class类的对象(一个类只会有一个Class对象),这个Class对象包含了完整的类结构信息。我们可以通过这个Class对象获取类的结构信息,这就叫反射。

在这里插入图片描述

2.2 Java反射机制提供的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时获取范型信息
  • 在运行时调用任意一个对象的成员变量和方法
  • 在运行时处理注解
  • 生成动态代理
  • 其它。。。

2.3 Java反射相关的API

  • java.lang.Class : 代表一个类
  • java.lang.reflect.Method : 代表类的方法
  • java.lang.reflect.Field : 代表类的成员变量
  • java.lang.reflect.Constructor : 代表类的构造器
  • 其它…

2.4 Java反射的优点和缺点

  • 优点:可以动态创建对象和编译,有非常大的灵活性
  • 缺点:对性能有一定影响。反射基本上是一种解释操作,这类操作总是慢于直接执行相同的操作
/**
 * 主要包含三个属性:id、name和age
 * 还有get、set、toString方法
 */
class User {
    private int id;
    private String name;
    private int age;

    public User() {
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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;
    }

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

//-------------------------------------------------------------------------------------------------

public class test02 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 通过反射获取类的 Class 对象
        Class<?> c1 = Class.forName("AnnotationAndReflect.Reflection.User");
        // 当类被加载后,类的整个结构信息会存放在 Class 对象中
        System.out.println("c1 : " + c1);

        // 有相同的 hashCode 一个类只有一个class对象
        Class<?> c2 = Class.forName("AnnotationAndReflect.Reflection.User");
        System.out.println("c1.hashCode : " + c1.hashCode());
        System.out.println("c1.hashCode : " + c2.hashCode());
    }
}

运行结果:
在这里插入图片描述


三、Class类

在Object类中定义了如下方法,此方法被所有子类继承,对象通过这个方法可以求出类的名称

public final native Class<?> getClass();

对于每个类而言,JRE都为其保留了一个不变的Class类型的对象,一个Class对象包含了特定某个结构( class/interface/enum/annotation/primitive type/void[] )的有关信息

  • Class本身也是一个类
  • Class对象只能由系统建立对象
  • 一个加载的类在JVM中只会有一个Class实例
  • 一个Class对象对应的是一个加载到JVM中的一个 .class 文件
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class可以完整地得到一个类中的所有被加载的结构
  • Class类是反射的根源,针对任何你想动态加载、运行的类,只有获得相应的Class对象

3.1 Class类的常用方法

static Class<?> forName(String className)	//返回指定类名为name的Class对象
Object newInstance()		//调用默认构造函数,返回Class对象的一个实例
getName()					//返回此Class对象所表示实体的名称
Class getSuperClass()		//返回当前Class对象的父类的Class对象
Class[] getInterfaces()		//返回当前Class对象的所有接口
ClassLoader getClassLoader()	//获取该类的类加载器
Constructor[] getConstructors()	//返回一个包含某些Constructor对象的数组
Method getMethod(String name,Class.. T)	//返回一个Method对象,此对象的形参类型为paramType
Field[] getDeclaredFields()		//返回Field对象的一个数组

3.2 如何获取Class类的实例

  • 1、若已知具体的类,通过类的class属性获取,该方法性能最高
Class clazz = Person.class;
  • 2、已知某个类的实例,调用该实例的 getClass() 方法获取 Class对象
Class clazz = person.getClass();
  • 3、已知一个类的全类名,且在该类的类路径下,可通过Class类的静态方法forName()获取,可能抛出 ClassNotFoundException
Class clazz = Class.forName("com.qy.Person");
  • 4、内置基本数据类型可以直接使用 类名.Type
  • 5、还可以使用 ClassLoader 类加载器
public class test03 {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        // 一、通过对象获得
        Class c1 = person.getClass();
        // 二、通过类的静态成员class获得
        Class c2 = Person.class;
        // 三、通过字符串(包名+类名)获得
        Class c3 = Class.forName("AnnotationAndReflect.Reflection.Student");
        // 四、针对内置的基本数据类型
        Class c4 = Integer.TYPE;
        // 五、获取父类类型
        Class superclass = c3.getSuperclass();

        System.out.println("c1:" + c1.hashCode());
        System.out.println("c2:" + c2.hashCode());
        System.out.println("c3:" + c3.hashCode());
        System.out.println("c4:" + c4.hashCode());
        System.out.println("superClass:" + superclass.hashCode());
    }
}

class Person {
    String name;
}

class Student extends Person {
    public Student() {
        this.name = "学生";
    }
}

运行结果:
在这里插入图片描述

由C1、C2、C3的 hashCode 相同可以得出,一个类只有一个Class实例

3.3 哪些类型可以有Class对象

  • class : 外部类,成员(成员内容类、静态内部类),局部内部类、匿名内部类
  • interface : 接口
  • enum : 枚举
  • annotation : 注解 @interface
  • [] : 数组
  • primitive type : 基本数据
  • void
public class test04 {
    public static void main(String[] args) {
        // 一、所有类型的Class
        System.out.println("一、所有类型的Class");
        Class c1 = Object.class;
        Class c2 = Comparable.class;
        Class c3 = ElementType.class;
        Class c4 = Override.class;
        Class c5 = String[].class;
        Class c6 = int[][].class;
        Class c7 = Integer.class;
        Class c8 = void.class;
        Class c9 = Class.class;

        System.out.println("c1 : " + c1);
        System.out.println("c2 : " + c2);
        System.out.println("c3 : " + c3);
        System.out.println("c4 : " + c4);
        System.out.println("c5 : " + c5);
        System.out.println("c6 : " + c6);
        System.out.println("c7 : " + c7);
        System.out.println("c8 : " + c8);
        System.out.println("c9 : " + c9);

        // 只要元素类型与维度相同,就是同一个Class
        System.out.println("==================");
        System.out.println("二、判断数组的Class与维度的关系");
        int[] a = new int[10];
        int[] b = new int[100];
        int[][] c = new int[10][10];
        System.out.println("a : " + a.getClass().hashCode());
        System.out.println("b : " + b.getClass().hashCode());
        System.out.println("c : " + c.getClass().hashCode());
    }
}

运行结果:
在这里插入图片描述


四、Java内存分析

4.1 类的加载与ClassLoader的理解

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来对该类进行初始化
1、类的加载( Load ):将类的class文件字节码加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
2、类的链接( Link ):将Java类的二进制代码合并到JVM的运行状态之中的过程

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

3、初始化( Initialize ):

  • 执行类构造器 <clinit>() 方法的过程。类构造器 <clinit>() 方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)
  • 当初始化一个类的时候,入股发现其父类还没有进行初始化,则需要先出发其父类的初始化
  • 虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确加锁和同步
public class test05 {
    public static void main(String[] args) {
        A a = new A();
        System.out.println(A.m);
        /*
        1、加载 : 加载到内存,会产生一个类对应class对象
        2、链接 : 链接结束后 m = 0
        3、初始化 :
            <clinit>(){
                System.out.println("A类静态代码块初始化");
                m = 300;
                m = 100;
            }
            最终 : m = 100
         */
    }
}

class A {
    static {
        System.out.println("A类静态代码块初始化");
        m = 300;
    }

    static int m = 100;
    
    public A() {
        System.out.println("A类的无参构造器初始化");
    }
}

运行结果:
在这里插入图片描述

4.2 什么时候回发生类的初始化

  • 类的主动引用(一定会发生类的初始化)
    • 当虚拟机启动后,先初始化 main 方法所在的类
    • new 一个类的对象
    • 调用类的静态成员(除了final常量)和静态方法
    • 使用 java.lang.reflect 包的方法对类进行反射调用
    • 当初始化一个类时,如果其父类没有被初始化,则先会初始化它的父类
  • 类的被动引用(不会发生类的初始化)
    • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:通过子类引用父类的静态变量,不会导致子类的初始化
    • 通过数组定义类引用,不会触发类的初始化
    • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
class Father {
    static int b = 2;

    static {
        System.out.println("父类被加载");
    }
}

class Son extends Father {
    static {
        System.out.println("子类被加载");
        m = 300;
    }

    static int m = 100;
    static final int M = 1;
}

测试一:new一个对象(主动引用)

public class test06 {
    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) {
        Son son = new Son();
    }
}

在这里插入图片描述

  1. 虚拟机启动,先初始化 main() 方法所在的类
  2. 创建子类时,由于父类未被初始化,所以先初始化父类
  3. 最后初始化子类

测试二:使用反射(主动引用)

public class test06 {
    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 反射也会产生主动引用
        Class clazz = Class.forName("AnnotationAndReflect.Reflection.Son");
    }
}

在这里插入图片描述

  1. 虚拟机启动,先初始化 main() 方法所在的类
  2. 使用反射时,由于父类未被初始化,所以先初始化父类
  3. 最后初始化子类

测试三:通过子类调用父类的静态变量(主动引用)

public class test06 {
    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        // 不会产生类的引用的方法
        System.out.println(Son.b);
    }
}

在这里插入图片描述

  1. 虚拟机启动,先初始化 main() 方法所在的类
  2. 通过子类引用父类的静态变量,父类会被初始化
  3. 但是子类不会初始化

测试四:通过数组定义类引用

public class test06 {
    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        Son[] sons = new Son[5];
    }
}

在这里插入图片描述

  1. 虚拟机启动,先初始化 main() 方法所在的类
  2. 通过数组定义类引用,不会触发类的初始化

测试五:引用常量

public class test06 {
    static {
        System.out.println("Main类被加载");
    }

    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(Son.M);
    }
}

在这里插入图片描述

  1. 虚拟机启动,先初始化 main() 方法所在的类
  2. 常量在链接阶段就已经赋值了,所以不会造成类的初始化

4.3 类加载器的作用

  • 类加载的作用:将 class 文件字节码内容加载到内存中,并将这些静态数据转换为方法区运行时数据结构,然后在堆中生成一个能代表这个类的 java.lang.Class 对象,将其作为方法区中类数据的访问入口
  • 类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某个类被加载到类加载器中,将会被缓存一段时间,这些 Class 对象可被 JVM 垃圾回收机制回收

在这里插入图片描述

  • 类加载器的作用:把类( class )加载进内存。JVM规范定义了如下类型的类加载器
    • Bootstrap ClassLoade (引导类加载器):用C++编写,是JVM自带的类加载器,负责Java平台的核心类库,用来装载核心类库。注意:此加载器无法直接获取
    • Extention ClassLoader (拓展类加载器):负责 jre/lib/ext 目录
    • System ClassLoader (系统类加载器):负责 java -classpath-D java.class.path 所指的目录下的类与jar包装入工作,是我们平时最常用的加载器
public class test07 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println("系统类加载器:" + systemClassLoader);
        // 获取系统类加载器的父类加载器-->拓展类加载器
        ClassLoader ExtentionClassLoader = systemClassLoader.getParent();
        System.out.println("拓展类加载器:" + ExtentionClassLoader);
        // 获取拓展类加载器的父类加载器-->根加载器(C/C++)----->跟加载器不可获得,为null
        ClassLoader BootstrapClassLoader = ExtentionClassLoader.getParent();
        System.out.println("根加载器:" + BootstrapClassLoader);
        // 测试当前类是哪个类加载器加载的
        ClassLoader classLoader = Class.forName("AnnotationAndReflect.Reflection.test07").getClassLoader();
        System.out.println("加载当前类的类加载器:" + classLoader);
        // 测试JDK内置的类是谁加载的----->由根加载器加载,结果为 null
        ClassLoader JDKclassLoader = Class.forName("java.lang.Object").getClassLoader();
        System.out.println("加载JDK内置类的类加载器:" + JDKclassLoader);
    }
}

在这里插入图片描述

注意:由于根加载器无法获得,所以根加载器的输出为 null


五、获取运行时类的完整结构

我们可以通过反射来获取运行时类的完整结构,例如: FieldMethodConstructorSuperclassInterfaceAnnotation

  • 获取类实现的全部接口
  • 获取类所继承的父类
  • 获取类中全部的构造器
  • 获取类中全部的方法
  • 获取类中全部的属性
  • 获取注解

代码演示:

/**
 * 父类
 * 有一个私有字段,一个公共字段
 * 有一个公共无参构造器,一个有参私有构造器
 * 公共的set、get方法
 */
public class Father01 {
    private String name;
    public int age;

    public Father01() {
    }

    private Father01(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}
/**
 * 子类
 * 有一个私有字段,一个公共字段
 * 有一个公共无参构造器,一个有参私有构造器
 * 公共的set、get方法
 * 还有一个私有的sayHi方法
 */
public class Son01 extends Father01 {
    private boolean gender;
    public String address;

    public Son01() {
    }

    private Son01(Boolean gender, String address) {
        this.gender = gender;
        this.address = address;
    }

    private void sayHi() {
    }

    public boolean isGender() {
        return gender;
    }

    public void setGender(boolean gender) {
        this.gender = gender;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
/**
 * 测试类
 * 通过反射获取Class对象、类属性、类方法、类构造器
 */
public class test08 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
        // 一、获取Class对象
        // 1.1 方法一
        System.out.println("# 通过全限定类名");
        Class clazz = Class.forName("AnnotationAndReflect.Reflection.Son01");
        System.out.println(clazz.getName());        // 包名+类名
        System.out.println(clazz.getSimpleName());  // 类名
        // 1.2 方法二
        System.out.println("# 通过 Object.getClass()方法");
        Son01 son01 = new Son01();
        clazz = son01.getClass();
        System.out.println(clazz.getName());        // 包名+类名
        System.out.println(clazz.getSimpleName());  // 类名

        System.out.println("====================================");

        // 二、获取类属性
        // 2.1 getFields() : 只能获取本类及其父类的 public 方法
        Field[] fields = clazz.getFields();
        System.out.println("# getFields方法:");
        for (Field field : fields) {
            System.out.println(field);
        }
        // 2.2 getDeclaredFields() : 可以获得本类的所有方法,包括 private 方法
        System.out.println("# getDeclaredFields方法:");
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        // 2.3 获取指定类属性
        Field name = clazz.getDeclaredField("gender");
        System.out.println("# 获取指定类属性:" + name);

        System.out.println("======================================");

        // 三、类方法
        // 3.1 getMethods() : 获得本类及其父类的 public 方法
        System.out.println("# getMethods方法:");
        Method[] methods = clazz.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }
        // 3.2 getDeclaredMethods方法 : 获得本类的所有方法,包括 private 方法
        System.out.println("# getDeclaredMethods方法:");
        Method[] declaredMethods = clazz.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }
        // 3.3 获取指定方法
        Method getName = clazz.getMethod("getName", null);
        Method setName = clazz.getMethod("setName", String.class);
        System.out.println("# 获取指定方法01:" + getName);
        System.out.println("# 获取指定方法02:" + setName);

        System.out.println("=============================================");

        // 四、类构造器
        // 4.1 getConstructors方法 : 获取本类中的 public 方法
        System.out.println("# getConstructors方法:");
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor constructor : constructors) {
            System.out.println(constructor);
        }
        // 4.2 getDeclaredConstructors方法 : 获得本类中的所有构造器方法,包括 private 方法
        System.out.println("# getDeclaredConstructors方法:");
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }
        // 4.3 获取指定类构造器
        Constructor declaredConstructor = clazz.getDeclaredConstructor(Boolean.class, String.class);
        System.out.println("# 指定类构造器:" + declaredConstructor);
    }
}

六、动态创建对象执行方法

有了 Class 对象我们可以做什么呢?
我们可以用它来创建类的对象,调用指定的方法,操作相关属性

  • 创建类的对象
    • 方法一:调用 Class 对象的 newInstance() 方法。此时要注意,其本质是调用类的无参构造器,且类构造器的访问权限要足够
    • 方法二:通过Class类的 getDeclaredConstructor(Class … parameterTypes) 来获取指定形参类型的构造器。但要向其传递构造器所需参数的对象数组,然后通过Constructor类实例化对象
  • 调用指定方法
    • 通过Class类的 getMethod(String name,Class...parameterTypes) 方法取得一个 Method对象 ,并设置此方法操作时所需要的参数类型
    • 然后利用Method对象使用 invoke(Object obj,Object[] args) 方法,并向方法中传递使用这个方法的obj对象和所需的参数信息
    • Object对应原方法的返回值,若原方法无返回值,此时返回null
    • 若原方法为静态方法,此时形参中的 Object obj 可以为 null;若原方法形参列表为空,则 Object[] args 为 null
    • 若原方法声明为 private,那么在调用 invoke() 方法前,需要显示调用方法对象的 setAccessible(true) 方法来关闭程序的安全检测,然后就可以访问 private 方法了
/**
 * 示例对象Boy
 * 有一个public属性,一个private属性
 * 一个公共的空参构造器,一个公共的有参构造器
 */
class Boy {
    public String name;
    private int age;

    // 空参构造器
    public Boy() {
    }

    // 有参构造器
    public Boy(String name, int age) {
        this.name = name;
        this.age = age;
    }

    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;
    }
}
/**
 * 测试类
 * 通过反射来
 * 1、创建对象
 * 2、调用方法
 * 3、操作属性
 */
public class test09 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        // 获取Class对象
        Class c1 = Class.forName("AnnotationAndReflect.Reflection.Boy");

        // 一、创建对象
        // 1.1 通过newInstance()获得对象
        Boy boy1 = (Boy) c1.newInstance();
        System.out.println(boy1);
        // 1.2 通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class);
        Boy boy2 = (Boy) constructor.newInstance("QY", 23);
        System.out.println(boy2);
        
        // 二、通过反射获取并调用普通方法
        Boy boy3 = (Boy) c1.newInstance();
        Method setName = c1.getDeclaredMethod("setName", String.class);
        setName.invoke(boy3, "Qy"); // invoke : 激活的意思
        System.out.println(boy3.getName());

        // 三、通过反射操作属性
        Boy boy4 = (Boy) c1.newInstance();
        // 3.1 public 属性
        Field name = c1.getField("name");
        name.set(boy4, "QY");
        System.out.println(boy4.getName());
        // 3.2 private 属性
        Field age = c1.getDeclaredField("age");
        age.setAccessible(true);    // 不能直接操作私有属性,所以我们需要关闭程序的安全检测
        age.set(boy4, 18);
        System.out.println(boy4.getAge());
    }
}

在这里插入图片描述


七、setAccessible(true)效率测试

  • Constructor、Method 和 Field 对象都有 setAccessible() 方法,它的作用是启动和禁用访问安全检查的开关
    • 参数为 true : 反射的对象在使用时,取消Java语言访问检查
    • 参数为 false : 反射的对象在使用时,实施Java语言访问检查
  • 当参数设置为true时
    • 可以提高反射的效率。如果代码中必须使用反射,且该代码需要被频繁调用,建议设置为true
    • 访问私有成员
public class test10 {
    /**
     * 一、普通方法
     */
    public static void test1() {
        Boy boy = new Boy();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            boy.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次:" + (endTime - startTime) + "ms");
    }

    /**
     * 二、放射方式(不关闭检测)
     */
    public static void test2() throws Exception {
        Boy boy = new Boy();
        Class c1 = boy.getClass();
        Method method = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            method.invoke(boy, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式(不关闭检测)执行10亿次:" + (endTime - startTime) + "ms");
    }

    /**
     * 三、反射方式(关闭检测)
     */
    public static void test3() throws Exception {
        Boy boy = new Boy();
        Class c1 = boy.getClass();
        Method method = c1.getDeclaredMethod("getName", null);
        method.setAccessible(true); // 关闭检测
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            method.invoke(boy, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式(关闭检测)执行10亿次:" + (endTime - startTime) + "ms");
    }

    public static void main(String[] args) throws Exception {
        test1();
        test2();
        test3();
    }
}

在这里插入图片描述

从运行结果可以看出,使用反射方式时,关闭检测所需执行的时间远小于开启检测所需执行时间


八、获取泛型信息

  • Java中的泛型采用了泛型擦除机制,其仅仅是给编译器javac使用的,保证数据的安全性和免去类型转换的问题,但是一旦编译完成,所有和泛型有关的类型全部擦除
  • 为了能够通过反射操作这些数据类型,Java新增了 ParameterizedTypeGenericArrayTypeTypeVariableWildcardType 这几种类型
    • ParameterizedType : 表示一种参数化类型,例如 : Collection
    • GenericArrayType : 表示元素类型是参数化类型或者类型变量 的数组类型
    • TypeVariable : 是各种类型变量的公共父接口
    • WildcardType : 代表一种通配符类型表达式
public class test11 {
    public void test01(Map<String, Boy> map, List<Boy> list) {
        System.out.println("test01");
    }

    public Map<String, Boy> test02() {
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        // 一、获取参数中的泛型
        Method method = test11.class.getMethod("test01", 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);
                }
            }
        }

        System.out.println("====================================================");

        // 二、获取返回值中的泛型
        method = test11.class.getMethod("test02", null);
        Type genericReturnType = method.getGenericReturnType();
        System.out.println("#" + genericReturnType);
        if (genericReturnType instanceof ParameterizedType) {
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

在这里插入图片描述

1、在获取参数中的泛型时,我们先获取方法,然后获取这个方法的 genericParameterTypes ,也就是 Map<String, Boy>List<Boy> ,打印出的结果为:

  • java.util.Map<java.lang.String, AnnotationAndReflect.Reflection.Boy>
  • java.util.List<AnnotationAndReflect.Reflection.Boy>

然后分别获取它们的 actualTypeArguments ,对于 Map<String, Boy> 而言就是 StringBoy ,对于 List<Boy> 而言就是 Boy
2、在获取返回值中的泛型时,我们先获取那个方法,然后获取这个方法的 genericReturnType ,也就是 Map<String, Boy> ,打印的结果为:

  • java.util.Map<java.lang.String, AnnotationAndReflect.Reflection.Boy> ,然后再获取它的 actualTypeArguments ,也就是 StringBoy

参考信息

  • 【狂神说Java】注解和反射 : https://www.bilibili.com/video/BV1p4411P7V3
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值