浅谈java反射机制

jvm如何创建实例

在学习反射之前,我们首先要理解一下jvm创建一个实例的过程,首先我们先用java写一个类

public class User {
    private int id;
    private String password;

    public boolean login(int id, String password) {
        if (this.id == id && this.password.equals(password))
            return true;
        return false;
    }
}

而在main方法中有这条语句时

User user = new User();
  1. 要通过这条命令编译成.class文件也就是字节码文件

    javac user.java
    
  2. jvm会通过ClassLoader加载.class文件到内存

  3. 生成Class对象Class

  4. 执行new,申请一块内存空间

  5. 构造器执行构造代码块和初始化语句

.class文件

.class文件是编译后的文件,打开后是01代码。是给计算机读的。JVM对.class也有自己的一套读法。

类加载器

类加载器的核心方法只有loadClass(),告诉类名他会加载生成Class对象

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查是否已经加载该类
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 如果尚未加载,则遵循父优先的等级加载机制(所谓双亲委派机制)
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 模板方法模式:如果还是没有加载成功,调用findClass()
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

加载.class文件可以分为3个步骤

  1. 检查文件是否被加载,加载就直接返回
  2. 当前缓存没有该类,遵守父优先加载机制,加载.class文件
  3. 上面步骤行不通,调用findClass()方法加载
public Class<?> findClass(String name) throws ClassNotFoundException {
	try {
        //通过IO流从指定位置读取xxx.class文件得到字节数组
		byte[] datas = getClassData(name);
		if(datas == null) {
			throw new ClassNotFoundException("类没有找到:" + name);
		}
		//调用类加载器本身的defineClass()方法,由字节码得到Class对象
		return defineClass(name, datas, 0, datas.length);
	} catch (IOException e) {
		e.printStackTrace();
		throw new ClassNotFoundException("类找不到:" + name);
	}
}

findClass首先是获取该文件的字节数组,之后通过defineClass方法来创建class对象,defineClass()是native方法。

Class类

首先我们先看下Class的基本属性

   private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

源码中我们看到这个Class类的反射数据类型,这里就很熟悉了把,这是一个类的基本组成部分

  • declaredFields:类中的属性(包括私有)
  • publicFields:类中仅公开的属性
  • declaredMethods:类中所有的方法
  • publicMethods:类中公开的方法
  • declaredConstructors:类中所有构造器
  • publicConstructors:类中公开构造器

我们也很容易看出Field Method Constructor也是类,而一个class类中的相应反射数据就保存在这些类的数组中。

Constructor

我们再来看下构造器这个类,我们获取到一个类的类类型(也就是某个类的Class对象),可以通过newInstance方法来创建该类的实例对象

在这里插入图片描述

在这里插入图片描述

我们可以看到newInstance()底层是获取一个无参的构造器对象,通过这个构造器的newInstance()的方法创建实例对象。

method

我们再看下method这个类的一些基本属性

 private Class<?>            clazz;
    private int                 slot;
    // This is guaranteed to be interned by the VM in the 1.4
    // reflection implementation
    private String              name;
    private Class<?>            returnType;
    private Class<?>[]          parameterTypes;
    private Class<?>[]          exceptionTypes;
    private int                 modifiers;
    // Generics and annotations support
    private transient String              signature;
    // generic info repository; lazily initialized
    private transient MethodRepository genericInfo;
    private byte[]              annotations;
    private byte[]              parameterAnnotations;
    private byte[]              annotationDefault;
    private volatile MethodAccessor methodAccessor;
  • name:方法名
  • returnType:返回类型
  • parameterTypes:参数类型
  • annotations:注解

我们在获取这个类的某个方法时可以用getMethod()

public Method getMethod(String name, Class<?>... parameterTypes)
    throws NoSuchMethodException, SecurityException {
    checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
    Method method = getMethod0(name, parameterTypes, true);
    if (method == null) {
        throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
    }
    return method;
}

可以看到,传入的参数是方法的名字和方法的参数的类型型,底层是在这个类的方法集合中根据这两个参数查找这个方法。

private static Method searchMethods(Method[] methods,
                                    String name,
                                    Class<?>[] parameterTypes)
{
    Method res = null;
    String internedName = name.intern();
    for (int i = 0; i < methods.length; i++) {
        Method m = methods[i];
        if (m.getName() == internedName
            && arrayContentsEq(parameterTypes, m.getParameterTypes())
            && (res == null
                || res.getReturnType().isAssignableFrom(m.getReturnType())))
            res = m;
    }

    return (res == null ? res : getReflectionFactory().copyMethod(res));
}

可以看到查找方法的流程是

  1. 判断方法名是否相等
  2. 比较参数类型是否相等
  3. 比较返回类型是否相等

走到这里,我们其实也可以明白为什么参数中要传类类型了吧,因为每个类的类类型是唯一的,在方法匹配时自然可以判断这个方法的参数是否为需要的类型。也就是说传的是这个类的引用,如果我们传的是类名(如String),那么这个引用就是string的值得引用。那么可以正常的进行方法的查找么?

通过这里我们也可以更深入的理解方法重载了吧。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值