日常敲码中,如果想要在程序运行阶段访问某个类的所有信息,并支持修改类的状态或者行为的话,肯定会用到反射,而反射靠的就是Class类。Java的动态代理也用到了这个东西,所以了解其基本操作在苦逼的CRUD中会添加一丝丝乐趣(有点意思)。

首先来看看Class的操作有哪些?

public final class Class<T> {}

上述代码可知,Class是一个由final修饰的泛型类,所以并不能直接通过new Class()获取其实例。那么应该如何获取呢?

//直接通过类的静态变量来获取
Class<Integer> intClass = Integer.class; //通过实例变量的getClass方法
Integer integer = new Integer(0);
Class<? extends Integer> aClass = integer.getClass(); //通过Class.forName("类的全限定名")
Class<?> aClass1 = Class.forName("java.lang.Integer");

上述三种就是获取某个Class的class实例的方式,需要注意的是,JVM只会加载一个Class实例,也就是说上述三种方式获取到的class实例都是一样的。

而在运用反射的时候,Class.forName是最常用的一种方式。而Class.forName底层会指向forName0这个本地方法

(1)name:类的全限定名

(2)initialize:是否初始化这个类

(3)loader:类加载器

(4)caller:调用Class.forName所在类的Class,比如A类代码块里有Class.forName,那么caller就是A的class实例。

通过Class类可以获取类的实例,构造方法,字段,成员方法,接口等信息。获取之后可以通过API进行相应的操作。

接下来看一下获取到class实例之后怎么获取当前类的实例以及构造方法。

上述两种方式都是调用默认的无参构造进行实例化对象,那么怎么通过公共或私有的有参构造获取实例呢?

//属性
int a;
String b ;
//公共有参构造
public ClassSource(int a) {
this.a = a;
}
//私有有参构造
private ClassSource(String b) {
this.b = b;
}
public static void main(String[] args){
Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//获取public有参构造
Constructor<?> constructor = aClass.getConstructor(int.class);
ClassSource o1 = (ClassSource) constructor.newInstance(2);
System.out.println("属性【a】的值为:"+ o1.a);
//获取private有参构造
Constructor<?> constructor1 = aClass.getDeclaredConstructor(String.class);
//这个有猫腻
constructor1.setAccessible(true);
ClassSource o2 = (ClassSource) constructor1.newInstance("abc");
System.out.println("属性【b】的值为:"+ o2.b);
}

上述代码运行的结果如下:

而获取私有构造函数最重要的是要setAccessible(true)这个方法,点进去看一下,这个方法最后是给override字段赋值的,可用于类的字段、方法以及构造函数。


public void setAccessible(boolean flag) throws SecurityException {
//这个“安全管理器”,允许应用程序在进行某些不安全或敏感操作
//之前执行安全策略,如果不允许执行,则通过抛异常阻止操作。
SecurityManager sm = System.getSecurityManager();
if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
setAccessible0(this, flag);
} private static void setAccessible0(AccessibleObject obj,
boolean flag){
if (obj instanceof Constructor && flag == true) {
Constructor<?> c = (Constructor<?>)obj;
if (c.getDeclaringClass() == Class.class) {
//如果是Class.class,则抛异常
}
}
//最终是设置此属性
obj.override = flag;
} //访问级别是否可以被覆盖,可用于字段,方法,构造函数
boolean override;

需要注意的是,在代码中用反射操作当前类私有字段、私有方法或其他私有属性时,并不需要setAccessible(true),例如在A类中用反射操作A类的私有属性。只有在A类中操作其他类的私有属性才需要设置setAccessible(true)。

不管是Class.newInstance还是Constructor.newInstance,底层调用的都是ConstructorAccessor(构造函数访问器)接口的newInstance方法。

(1)Class.newInstance

//标识所有公共属性
public static final int PUBLIC = 0;
//标识所有私有属性
public static final int DECLARED = 1;

public T newInstance(){
//省略部分代码
// 检查有没有存在已加载过的构造器
if (cachedConstructor == null) {
//省略部分代码
try {
Class<?>[] empty = {};
//获取默认私有无参构造
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
//省略部分代码
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
//省略部分代码
try {
//调用无参构造的newInstance方法
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
//省略部分代码
return null;
}
} private Constructor<T> getConstructor0(Class<?>[] parameterTypes,
int which)
{
//获取构造函数,重点看这个方法,参数是false
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
//省略匹配构造函数参数的代码
}
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
//检查是否已被初始化,如果否,则进行安全检查
checkInitted();
Constructor<T>[] res;
//反射的数据
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;
}
// 检查是否是接口
if (isInterface()) {
//代码省略
} else {
//底层调用的是本地方法
res = getDeclaredConstructors0(publicOnly);
}
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}

如上,可以看到Class.newInstance底层是调用无参构造的newInstance方法。

(2)Constructor.newInstance

public T newInstance(Object ... initargs){
//override就是是否可覆盖级别访问
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
} public interface ConstructorAccessor {
Object newInstance(Object[] var1) throws InstantiationException, IllegalArgumentException, InvocationTargetException;
}

可以看到,Class.newInstance调用的是无参构造的newInstance,而构造函数调用的是ConstructorAccessor接口类的newInstance,所以最终调用的都是ConstructorAccessor接口类的newInstance。

最终调用的是NativeConstructorAccessorImpl的newInstance方法。

【注】以下仅演示操作方法,源码不贴,防止篇幅太长。

获取实例以及构造方法之后,来看一下如何访问,修改类字段信息。

  //public属性
public int a;
//private属性
private String b;
public static void main(String[] args) throws Exception { Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//获取实例
Object instance = aClass.newInstance();
//根据字段名获取公共属性并进行赋值
Field a = aClass.getField("a");
a.set(instance,2);
System.out.println("a的值为:"+a.get(instance));
//根据字段名获取私有属性并进行赋值
Field b = aClass.getDeclaredField("b");
b.setAccessible(true);
b.set(instance,"abc");
System.out.println("b的值为"+b.get(instance));
}

上述代码运行结果为:

接下来瞧一下如何利用class访问类的成员方法:

public static void main(String[] args) throws Exception {

    Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
//获取实例
Object instance = aClass.newInstance(); //获取公共方法并调用
Method sayPublicHello = aClass.getMethod("sayPublicHello");
sayPublicHello.invoke(instance); //获取私有方法并调用
Method sayPrivateHello = aClass.getDeclaredMethod("sayPrivateHello", int.class);
sayPrivateHello.setAccessible(true);
sayPrivateHello.invoke(instance,1);
} public void sayPublicHello(){
System.out.println("sayPublicHello");
} private void sayPrivateHello(int param){
System.out.println("sayPrivateHello,参数:"+param);
}

上述代码运行结果为:

还可以使用“==”判断数据类型

Class<?> aClass = Class.forName("com.liusy.lang.ClassSource");
System.out.println(aClass == ClassSource.class);

之前遇到过用了很多反射的代码,看起来很恶心。但其实了解过后反而觉得反射是个很强大的东西,包括JDK动态代理,spring AOP,事务底层都是用的反射,研究之后有利于了解更多底层的知识。

=======================================================

我是Liusy,一个喜欢健身的程序员。

欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货。

从Class源码看反射的更多相关文章

  1. 从源码看Azkaban作业流下发过程

    上一篇零散地罗列了看源码时记录的一些类的信息,这篇完整介绍一个作业流在Azkaban中的执行过程,希望可以帮助刚刚接手Azkaban相关工作的开发.测试. 一.Azkaban简介 Azkaban作为开 ...

  2. 解密随机数生成器(二)——从java源码看线性同余算法

    Random Java中的Random类生成的是伪随机数,使用的是48-bit的种子,然后调用一个linear congruential formula线性同余方程(Donald Knuth的编程艺术 ...

  3. 从源码看Android中sqlite是怎么通过cursorwindow读DB的

    更多内容在这里查看 https://ahangchen.gitbooks.io/windy-afternoon/content/ 执行query 执行SQLiteDatabase类中query系列函数 ...

  4. 从源码看Android中sqlite是怎么读DB的(转)

    执行query 执行SQLiteDatabase类中query系列函数时,只会构造查询信息,不会执行查询. (query的源码追踪路径) 执行move(里面的fillwindow是真正打开文件句柄并分 ...

  5. 从Chrome源码看浏览器的事件机制

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  6. 从Chrome源码看JS Array的实现

    .aligncenter { clear: both; display: block; margin-left: auto; margin-right: auto } .crayon-line spa ...

  7. 从源码看JDK提供的线程池(ThreadPoolExecutor)

    一丶什么是线程池 (1)博主在听到线程池三个字的时候第一个想法就是数据库连接池,回忆一下,我们在学JavaWeb的时候怎么理解数据库连接池的,数据库创建连接和关闭连接是一个比较耗费资源的事情,对于那些 ...

  8. 从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计

    使用微信小程序开发已经很长时间了,对小程序开发已经相当熟练了:但是作为一名对技术有追求的前端开发,仅仅熟练掌握小程序的开发感觉还是不够的,我们应该更进一步的去理解其背后实现的原理以及对应的考量,这可能 ...

  9. 从微信小程序开发者工具源码看实现原理(四)- - 自适应布局

    从前面从微信小程序开发者工具源码看实现原理(一)- - 小程序架构设计可以知道,小程序大部分是通过web技术进行渲染的,也就是最终通过浏览器的dom tree + cssom来生成渲染树:既然最终是通 ...

随机推荐

  1. java十进制二进制互转

    1. 十进制转二进制 原理:给定的数循环除以2,直到商为0或者1为止.将每一步除的结果的余数记录下来,然后反过来就得到相应的二进制了. 比如8转二进制,第一次除以2等于4(余数0),第二次除以2等于2 ...

  2. CentOS7 系统基于Vim8搭建Go语言开发环境

    链接:https://pdf.us/2018/11/10/2194.html 问题1:vim-go: could not find 'gopls'. Run :GoInstallBinaries to ...

  3. ubuntu apt-mirror 同步源到本地

    1.下载 apt-mirror apt-get install apt-mirror 2.修改配置 root@wangjq:/wangjq# cat /etc/apt/mirror.list##### ...

  4. CocosCreator游戏开发(四)实现摇杆控制角色功能

    时隔3年,我又开始继续写这个系列的帖子了,也不知道是会写完全系列,还是再次夭折. 废话不多.直接开始主题了 主要实现的功能点包含这些内容:通过摇杆控制角色进行八方位移动,并按照各方位播放对应移动动画 ...

  5. 企业项目实战 .Net Core + Vue/Angular 分库分表日志系统五 | 完善业务自动创建数据库

    教程预览 01 | 前言 02 | 简单的分库分表设计 03 | 控制反转搭配简单业务 04 | 强化设计方案 05 | 完善业务自动创建数据库 说明 这节来把基础的业务部分完善一下. 因为 IQue ...

  6. Azure Logic App 入门(一)

    一,引言 前两天看一个azure相关的题,接触到一个叫 “Azure Logic App” 的服务,刚好,今天抽空学习以下,顺便结合它做一篇入门的分析文章. 首先,我们得对它有个大概的认识,了解以下A ...

  7. Alink漫谈(二十) :卡方检验源码解析

    Alink漫谈(二十) :卡方检验源码解析 目录 Alink漫谈(二十) :卡方检验源码解析 0x00 摘要 0x01 背景概念 1.1 假设检验 1.2 H0和H1是什么? 1.3 P值 (P-va ...

  8. 用aop去解决事物问题(tx)记录学习之aop1.2

    上一个文章我们了解了什么事aop,以及aop的使用方法,主要是把自己想要加入的通知(advice)加入到我们的方法里, 比如上一章我们说的事把myadvice类中的before方法织入到userser ...

  9. for...in、for...of和forEach

    let arr = [1,2,3,4,5,6]; arr.name="AAA"; for(var i in arr){ //遍历的实际是对象的属性名臣,每一个元素的索引被视为一个属 ...

  10. 漏洞重温之sql注入(六)

    漏洞重温之sql注入(六) sqli-labs通关之旅 Less-26 进入第26关,首先我们可以从网页的提示看出本关是get型注入. 我们给页面添加上id参数后直接去查看源码. 需要关注的东西我已经 ...