天天看点

Java反射详解Java反射详解

Java反射详解

原文地址:反射详解

为什么单独又写一篇呢。。。。因为到后面框架时,我发现自己有点基础不牢,而且排版好像也有点问题(不知道为什么从本地copy到博客的md编辑器后有些地方给我变成代码块了)。。。所以单独写一篇详细点的。

Class类

阅读时请注意大小写

在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的

java.lang

包中。java程序编译生成

.class

文件,

.class

里面的内容就是你编译的

.java

文件的内容,不过是被编译为字节形式,给JVM查看的。而用来描述

.class

文件的类,就是

Class

Class类被创建后的对象就是Class对象。实际上在Java中每个类都有一个Class对象,每当我们编写并且编译一个新创建的类就会产生一个对应Class对象并且这个Class对象会被保存在同名.class文件里,编译后的字节码文件保存的就是Class对象,那为什么需要这样一个Class对象呢?是这样的,当我们new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。而且Class对象只能由JVM来进行创建和销毁。

另外需要知道的一点是

在JVM创建的Class类型的对象中

成员变量被封装为Field对象

构造方法被封装为Constructor对象

成员方法被封装为Method对象

其实在这里我初学时就有些疑惑,既然Class类是按照我自己定义的类一起创建的,而且跟我自己定义的类是一模一样的。当我需要调用里面的方法时,我可以使用自己定义的类来调用啊。只不过要把方法定义为静态方法罢了。

但是,我后面想到一个问题,这是面对的普通的方法,那么在最初的构造方法,我要怎么来调用呢。也就是说,我怎么通过类来调用构造方法。

上面是我们构造一个A类对象时需要进行的一步。众所周知,对象是能够调用方法的,那么我如何通过对象a来调用A类的构造方法?

调用不到,对吧。因为a本来就是由构造方法构造出来的,a根本就调用不到构造方法。而且,再进一步想,A这个类也调用不到自己的构造方法。

那么,如果,我要在某些情况下,使用到A的构造方法,或者说,将A的构造方法作为一个函数的参数传递,那该怎么办?

这就需要用到下面将写的反射技术,Class类也是反射的基础

类的加载

第一次使用类的信息时,

.class

字节码文件会被加载到内存当中,存储在方法区中。

JVM会为加载到方法区的

.class

文件创建一个Class类型对象,该对象保存在堆内存中,等同于堆内存中的Class类型的对象,指向了方法区中的

.class

文件

一个类只会被加载一次,所以Class类型的对象只有一个,且任意类型都有对应的Class类型的对象

反射

那么,什么叫反射呢?我这里用我的理解来讲一下,反射就是通过Class类来获取

.class

文件的内容,因为

.class

的内容记载了类的所有属性(成员变量,构造方法,成员方法等)。

当然,具体的定义。。。可以去查看官方文档

获取Class对象

  1. 通过

    java.lang.Object

    类的成员方法

    public Class<?> getClass()

    Person p = new Person();
    Class c1 = p.getClass();
    System.out.println(c1);
               
  2. 通过class属性
    Class<Person> c2 = Person.class;
    System.out.println(c2);
               
  3. 通过

    java.lang.Class

    类的静态方法

    public static Class<?> forName(String className)

    : 获取方法参数指定类名对应的Class类型的对象(使用得最多,因为不需要知道具体的类的名字)
    Class<?> c3 = Class.forName("domain.Person");
    System.out.println(c3);
               

总结一下

获取class对象方式 作用 应用场景
Class.forName(“全类名”) 通过指定的字符串路径获取 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
类名.class 通过类名的属性class获取 多用于参数的传递
对象.getClass() 通过对象的getClass()方法获取 多用于对象的获取字节码的方式
其他的一些方法
  1. String getSimpleName(); 获得简单类名,只是类名,没有包

    String getName(); 获取完整类名,包含包名+类名

  2. T newInstance() ;创建此 Class 对象所表示的类的一个新实例。要求:类必须有public的无参数构造方法

获取构造方法对象

在通过反射获取构造方法之前,请一定保证存在了相应的Class对象。

  1. public Constructor[] getConstructors()

    : 获取public修饰的所有的构造方法,每个构造方法被封装成了Constructor类型的对象,被存储数组中。Constructor类是专门用来描述构造方法的一个类
    Constructor<?>[] cons = c.getConstructors();
    //遍历
    for (Constructor<?> con : cons) {
    	System.out.println(con);
    }
               
  2. public Constructor<T> getConstructor(Class... parameterTypes)

    : 获取指定参数类型的public修饰的构造方法。如果不存在对应的构造方法,则会抛出

    java.lang.NoSuchMethodException

    异常。

    参数说明:

    Class... parameterTypes

    : 必须传递数据类型对应的Class对象,而且是可变参数,可以传递数组,参数列表,不传递(获取空参)。

    例如:获取 构造方法

    public Person(String name, int age) { ...}

    对应的Construtor对象,填写的参数列表为

    String.class

    int.class

    //获取public修饰的空参构造方法对象
    Constructor<?> con = c.getConstructor();
    System.out.println(con);
    
    //获取public修饰的第一个参数是String类型,第二个参数是int类型的构造方法对象
    Constructor<?> con2 = c.getConstructor(String.class, int.class);
    System.out.println(con2);
               

私有方法也能通过其他方法反射出来,但最好不要这么做这样的暴力反射,因为破坏了封装性。如果有这样的需求,查阅API文档:

getDeclaredConstructor

等相关方法

使用构造方法对象构造对象

  1. java.lang.reflect.Constructor

    类的成员方法:

    public Object newInstance(Object... params)

    :执行构造方法对象,创建一个具体的对象
    //获取Class类型的对象
    Class<?> c = Class.forName("domain.Person");
    
    //通过Class类型的对象获取空参构造方法对象
    Constructor<?> con = c.getConstructor();
    
    //执行构造方法对象,创建一个具体的对象
    //执行的是空参构造方法对象,不需要传递参数,返回的类型是Object,强转为Person类
    Person p = (Person)con.newInstance();
               
  2. java.lang.Class

    类的成员方法:

    public T newInstance()

    执行空参构造方法,创建一个对象。
    //获取Class类型的对象
    Class<?> c = Class.forName("domain.Person");
    
    //java.lang.Class类 成员方法: newInstance
    Person p = (Person)c.newInstance();
               

获取成员方法对象

在获取成员方法之前,需要获取Class类型的对象

  1. java.lang.Class

    类的成员方法:

    public Method[] getMethods()

    :获取所有public修饰的成员方法,包含继承下来的方法,每个成员方法被封装成了一个Method对象,存储Method数组中。

    java.lang.reflect.Method

    类是用来描述成员方法的一个类。
    //获取Class类型的对象
    Class<?> c = Class.forName("domain.Person");
    
    //获取所有public修饰的成员方法,包含继承下来的
    Method[] ms = c.getMethods();
    for (Method m : ms) {
    	System.out.println(m);
    }
               
  2. java.lang.Class

    类的成员方法:

    public Method getMethod(String name, Class... parameterTypes)

    获取public修饰的指定方法名称,指定参数类型对应的成员方法对象。参数列表中

    String name

    为方法名称,

    Class... parameterTypes

    为必须传递数据类型对应的Class对象,而且是可变参数,可以传递数组,参数列表,不传递则获取空参。
    //获取Class类型的对象
    Class<?> c = Class.forName("domain.Person");
    
    //获取public修饰的名称为toString的没有参数的方法
    Method m1 = c.getMethod("toString");
    System.out.println(m1);
    
    //获取public修饰的名称为setName的参数为String类型的方法
    Method m2 = c.getMethod("setName", String.class);
    System.out.println(m2);
               

使用成员方法对象执行方法

使用

java.lang.reflect.Method

类的成员方法

public Object invoke(Object obj,Object... args)

来对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。参数

Object obj

为包含成员方法的对象,

Object... arg

执行方法时,该方法需要的具体的参数。返回值在没有返回值的方法返回一个null,有返回值的方法则封装为具体结果。

//1.获取Class类型的对象
Class<?> c = Class.forName("domain.Person");

//成员方法的调用,需要包含成员方法的对象
//创建一个对象
Object obj = c.newInstance();

//2.获取setName方法对象
Method setNameMethod = c.getMethod("setName", String.class);
//3.执行setName方法对象
Object result = setNameMethod.invoke(obj, "茶叶蛋");
System.out.println(result);//null
System.out.println(obj);// Person{name='茶叶蛋',age=0}

//2.获取getName方法对象
Method getNameMethod = c.getMethod("getName");

//3.执行getName方法对象
result = getNameMethod.invoke(obj);
System.out.println(result);
           

类加载器

将class文件(硬盘)加载到内存生成Class对象。

详情参见

https://chayedan.site/index.php/archives/23/