Java之反射机制

  • 概述

  • Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期 借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
  • 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个 类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看 到类的结构,所以,我们形象的称之为:反射。

  •  Class类

Class类的理解

  • 在Object类中定义了以下的方法,此方法 将被所有子类继承: public final Class getClass() 该方法返回值的类型是一个Class类, 此类是Java反射的源头,实际上所谓反射 从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。 
  • 对象通过反射(可以理解为照镜子)后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含 了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。
  • 一个加载的类在 JVM 中只会有一个Class实例
    int[] a = new int[10];
    int[] b = new int[100];
    Class c10 = a.getClass();
    Class c11 = b.getClass();
    // 只要数组的元素类型与维度一样,就是同一个Class
    System.out.println(c10 == c11);

Class类常用的方法

  • static Class forName(String name) 返回指定类名 name 的 Class 对象
  • Object newInstance() 调用缺省构造函数,返回该Class对象的一个实例
  • getName() 返回此Class对象所表示的实体(类、接口、数组类、基本类型 或void)名称
  • Class getSuperClass() 返回当前Class对象的父类的Class对象
  • Class [] getInterfaces() 获取当前Class对象的接口
  • ClassLoader getClassLoader() 返回该类的类加载器
  • Class getSuperclass() 返回表示此Class所表示的实体的超类的
  • Class Constructor[] getConstructors() 返回一个包含某些Constructor对象的数组
  • Field[] getDeclaredFields() 返回Field对象的一个数组
  • Method getMethod(String name,Class … paramTypes) 返回一个Method对象,此对象的形参类型为paramType 

 获取Class类的实例

方式1:通过运行时类的对象,调用getClass()

Person p = new Person();
Class<? extends Person> calzz = p.getClass();
System.out.println(calzz);

 方式2:调用Class的静态方法:forName(String classPath)

Class<?> clazz1 = Class.forName("day10.Person");
System.out.println(clazz1);

 方式3:调用运行时类的属性:.class

Class<Person> clazz2 = Person.class;
System.out.println(clazz2);

方式4:使用类的加载器:ClassLoader

ClassLoader classLoader = ClassModelTest.class.getClassLoader();
Class<?> calzz3 = classLoader.loadClass("day10.Person");
System.out.println(calzz3);

理解:万事万物皆对象

Class c1 = Object.class;//类
Class c2 = Comparable.class;//接口
Class c3 = String[].class;
Class c4 = int[][].class;//数组
Class c5 = ElementType.class;//枚举
Class c6 = Override.class;//异常
Class c7 = int.class;//基本数据类型
Class c8 = void.class;//空参
Class c9 = Class.class;
  •  类的加载

类加载的过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过 如下三个步骤来对该类进行初始化。 加载

  • 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。换句话说,Class的实例就对应着一个运行时类。
  • 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

链接

  •  将Java类的二进制代码合并到JVM的运行状态之中的过程。
  • 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。

初始化

  • 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初化。 
  • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
public static void main(String[] args) {
System.out.println(A.m);
}
}
class A {
static {
m = 1;
}
static int m = 2;
}
//第二步:链接结束后m=0
//第三步:初始化后,m的值由<clinit>()方法执行决定
// 这个A的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并
产生,类似于
// <clinit>(){
// m = 1;
// m = 2;
// }

 了解类的初始化

会发生初始化的情况

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量)和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类 

不会发生类的初始化

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化;当通过子类引用父类的静态变量,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常 量池中了) 

  • ClassLoader(类的加载器)的理解

类的加载器作用

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

类加载器的分类

  • 引导类加载器(Bootstap ClassLoader):用C++编写的,是JVM自带的类 加载器,负责Java平台核心库,用来装载核心类 库。该加载器无法直接获取 
  • 扩展类加载器(Extension ClassLoader):负责jre/lib/ext目录下的jar包或 – D java.ext.dirs 指定目录下的jar包装入工作库
  • 系统类加载器(System ClassLoader):负责java –classpath 或 –D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器
  • 自定义类加载器

类加载器常用方法

getParent():获取父类的加载器

public static void main(String[] args) throws ClassNotFoundException {
    //获取一个系统类加载器
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    System.out.println(classLoader);

    //获取系统类加载器的父类加载器,即扩展类加载器
    ClassLoader classLoader1 = classLoader.getParent();
    System.out.println(classLoader1);

    //获取扩展类加载器的父类加载器,即引导类加载器
    ClassLoader classLoader2 = classLoader1.getParent();
    System.out.println(classLoader2);//null

    //获取类由哪个类加载器进行加载
    ClassLoader classLoader4 = Class.forName("java.lang.Object").getClassLoader();
    System.out.println(classLoader4);

}

getResourceAsStream(): 获取类路 径下的指定文件的输入流

应用场景:Properties:用来读取配置文件。

@Test
public void test2() throws IOException {
    Properties pros = new Properties();

    //读取配置文件的方式1;
    //此时的文件默认在当前的module的src下下。
    ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
    InputStream is = classLoader.getResourceAsStream("jdbc1.properties");
    pros.load(is);

    //读取配置文件的方式2:
    //此时的文件默认在当前的module下。
//        FileInputStream fis = new FileInputStream("jdbc.properties");
//        pros.load(fis);

    String user = pros.getProperty("user");
    String possWord = pros.getProperty("passWord");
    System.out.println("user=" + user + "\t" + "passWord=" + possWord);
}
  • 创建运行时类的对象

通过调用Class对象的newInstance()方法

前提条件

  • 类必须有一个无参数的构造器。
  • 类的构造器的访问权限需要足够。(通常,设置为public)

小知识点:在javabean中为什么要求提供一个public的空参构造器?

  •  便于通过反射,创建运行时类的对象
  • 便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器

代码演示

@Test
public void test4() throws Exception {
    Class<Person> clazz = (Class<Person>) Class.forName("day10.Person");
    Person person = clazz.newInstance();
    System.out.println(person);//Person{name='null', age=0}
}

使用非空构造器创建对象

@Test
public void test5() throws Exception {
    Person p = new Person();
    Class<? extends Person> clazz = p.getClass();
    //获取指定的构造器,声明构造器的参数列表
    Constructor<? extends Person> constructor = clazz.getDeclaredConstructor(String.class);
    //保证次构造器可以访问(构造器的权限为public时,该步可省略)
    constructor.setAccessible(true);
    //使用获取的该构造器创造对象
    Person person = constructor.newInstance("嘻戏");
    System.out.println(person);
}
  • 获取运行时类的完整结构

运行时类的属性

获取允许时类的属性及相关数据

  • getFields()获取当前运行时类及其父类中声明为public访问权限的属性
  • getDeclaredFields()获取当前运行时类中声明的所有属性。(不包含父类中声明的属性)
  • getModifiers()获取属性的权限修饰符
  • getType()获取属性的数据类型
  • getName()获取属性的名称
@Test
public void test6() throws ClassNotFoundException {
    ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    Class<?> clazz = classLoader.loadClass("day10.Person");
    Field[] fields = clazz.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
        System.out.println(fields[i]);
        //获取权限修饰符
        int modifiers = fields[i].getModifiers();
        System.out.print("权限修饰符:" + Modifier.toString(modifiers) + "\t");
        //获取属性数据类型
        Class<?> type = fields[i].getType();
        System.out.print("数据类型:" + type.getName() + "\t");
        //获取变量名称
        String fName = fields[i].getName();
        System.out.print("变量名" + fName);

        System.out.println();
    }
}

设置运行时类的属性

@Test
public void test6() throws Exception {
    //实例化运行实例对象
    Class<?> clazz1 = Class.forName("day10.Person");
    //创建运行时类的对象
    Person person = (Person) clazz1.newInstance();
    //获取运行时类中指定变量名的属性
    Field name = clazz1.getDeclaredField("name");
    //保证当前属性是可访问的
    name.setAccessible(true);
    //设置属性的值
    name.set(person, "Jane");
    //获取属性的值
    System.out.println(name.get(person));
}

运行时类方法

获取运行时类的方法及相关数据

  •  getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
  • getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
  • getAnnotations():获取方法声明的注解
  • getModifiers():获取方法权限修饰符
  • getReturnType():获取方法返回值类型
  • getName():获取方法名
  • getParameterTypes():获取方法形参列表
  • getExceptionTypes():获取方法抛出的异常
@Test
public void test7(){
    Person p = new Person();
    Class<? extends Person> clazz = p.getClass();
    Method[] methods = clazz.getDeclaredMethods();
    for (Method m : methods) {
        //获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
        System.out.println("运行时类方法:" + m + "\t");
        //获取方法声明的注解
        Annotation[] annotations = m.getAnnotations();
        for (Annotation a : annotations) {
            System.out.print("注解:" + a + "\t");
        }
        //获取方法权限修饰符
        System.out.print("方法权限修饰符:" + Modifier.toString(m.getModifiers()) + "\t");
        //获取方法返回值类型
        System.out.print("返回值类型:" + m.getReturnType().getName() + "\t");
        //获取方法名
        System.out.print("方法名:" + m.getName() + "\t");
        //获取方法形参列表
        Class<?>[] parameterTypes = m.getParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            if (i == parameterTypes.length -1) {
                System.out.print("args_" + i + "=" + parameterTypes[i].getName() + "\t");
                break;
            }
            if (i == 0)
                System.out.print("形参列表:" + "args_" + i + "=" + parameterTypes[i].getName() + ",");
            System.out.print("args_" + i + "=" + parameterTypes[i].getName() + ",");

        }
        //获取方法抛出的异常
        Class<?>[] exceptionTypes = m.getExceptionTypes();
        if (exceptionTypes.length > 0) {
            System.out.print("异常:");
            for (int i = 0; i < exceptionTypes.length; i++) {
                if (i == exceptionTypes.length - 1) {
                    System.out.print(exceptionTypes[i].getName());
                    break;
                }
                System.out.print(exceptionTypes[i].getName() + ",");
            }
        }

        System.out.println();
    }

}

 设置运行时类方法

 方法有返回值则返回对应的返回值,如果调用的运行时类中的方法没有返回值,则此invoke()返回null

@Test
public void test7() throws Exception {
    //实例化运行实例对象
    Class<Person> clazz1 = Person.class;
    //创建运行时类的对象
    Person person = clazz1.newInstance();
    //获取指定的某个方法
    Method show = clazz1.getDeclaredMethod("show", String.class, int.class);
    //保证当前方法是可访问的
    show.setAccessible(true);
    //设置方法的形参
    Object returnValue = show.invoke(person, "Jane", 23);
    System.out.println(returnValue);

    Method voidModel = clazz1.getDeclaredMethod("voidModel", Person.class);
    voidModel.setAccessible(true);
}

调用静态方法

Method model = clazz1.getDeclaredMethod("model");
model.setAccessible(true);
//        Object o = model.invoke(null);
Object o = model.invoke(Person.class);//该处null/Person.class没有意义,因为invoke方法需要传入参数
System.out.println(o);

运行时类构造器

获取运行时类的构造器及相关数据 

  • getConstructors():获取当前运行时类中声明为public的构造器
  • getDeclaredConstructors():获取当前运行时类中声明的所有的构造器 
@Test
public void test8() throws ClassNotFoundException {
    Class<?> clazz = Class.forName("day10.Person");
    Constructor<?>[] constructors = clazz.getDeclaredConstructors();
    for (Constructor c : constructors) {
        System.out.println(c);
    }
}

调用运行时类中的指定的构造器

  • getConstructor/getDeclaredConstructor(Class<?>... parameterTypes) 通过指明构造器的参数列表获取指定的构造器
@Test
public void test9() throws Exception {
    Person p = new Person();
    Class<? extends Person> clazz = p.getClass();
    //获取指定的构造器
    Constructor<? extends Person> constructor = clazz.getDeclaredConstructor(String.class);
    //保证此构造器是可访问的
    constructor.setAccessible(true);
    //调用此构造器创建运行时类的对象
    Person person = constructor.newInstance("Jane");
    System.out.println(person);
}

 运行时类父类

  • getSuperClass():获取运行时类的父类
  • getGenericSuperClass():获取运行时类的带泛型的父类
@Test
public void test10() throws Exception {
    ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    Class<?> clazz = classLoader.loadClass("day10.Person");
    //获取运行时类的父类
    Class<?> superclass = clazz.getSuperclass();
    System.out.println(superclass);
    //获取运行时类的带泛型的父类
    Type genericSuperclass = clazz.getGenericSuperclass();
    System.out.println(genericSuperclass);
}
  • getActualTypeArguments():获取运行时类的带泛型的父类的泛型
@Test
public void test11() throws ClassNotFoundException {
    Class<?> clazz = Class.forName("day10.Person");
    Type genericSuperclass = clazz.getGenericSuperclass();
    ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
    Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
//        System.out.println(actualTypeArguments[0].getTypeName());
    System.out.println(((Class)actualTypeArguments[0]).getName());
}

 运行时类实现的接口

  • getInterfaces():获取运行时类实现的接口 
  • getSuperClass().getInterfaces():获取运行时类的父类实现的接口
@Test
public void test12(){
    Person p = new Person();
    Class<? extends Person> clazz = p.getClass();
    //获取运行时类实现的接口
    Class<?>[] interfaces = clazz.getInterfaces();
    for (Class c: interfaces) {
        System.out.println(c);
    }
    System.out.println();
    //获取运行时类父类实现的接口
    Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
    for (Class c1 : interfaces1) {
        System.out.println(c1);
    }
}

获取运行时类所在的包

  • getPackage():获取运行时类所在的包
@Test
public void test13() throws Exception {
    ClassLoader classLoader = ClassModelTest.class.getClassLoader();
    Class<?> clazz = classLoader.loadClass("day10.Person");
    Package aPackage = clazz.getPackage();
    System.out.println(aPackage);
}

获取运行时类声明的注解

  • getAnnotations():获取运行时类声明的注解
@Test
public void test14(){
    Class<Person> clazz = Person.class;
    Annotation[] annotations = clazz.getAnnotations();
    for (Annotation a : annotations) {
        System.out.println(a);
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值