java 反射获取class代码_看懂Java的Class与反射机制原理,让你写出更灵活的代码...

前言

最近一些朋友怎么是说自己的代码太复杂、太臃肿、灵活性太差,也不知道问题出在哪里。

首先表扬一下你的精神,可以时刻关注着自己代码的问题。

作为一个优秀的码农,总是希望用最少的代码来实现某一项功能,我也会经常翻看自己写的旧代码,看看有没有可以提升的空间。

44ccce93cf4def88bf998f02fb8c61bc.png

代码太复杂要考虑的是有没有“杀鸡用牛刀”,架构过度的设计,代码层级过度设计等等。

代码臃肿一般都是为了一些简单的功能快速上线,一直往里面堆功能性或兼容性的代码,这就是一直在做加法,但优秀的程序员应该学会做减法,时不时检查一下自己的代码是否存在冗余和无用的功能性代码。

而关于代码的灵活性那就更有的说了,今天就来带大家来聊聊关于这方面的技术点:反射机制。

a53be06057701dfa4bbe12a57affab4b.png

反射机制

反射是框架设计的灵魂,java的反射机制就是增加程序的灵活性,避免将代码写死在程序里

反射的概述

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法。

所以先要获取到每一个字节码文件对应的Class类型的对象。

906a9d490888b3665ac3c33260116e13.png

Class

在Java编译运行过程中,当程序new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。

需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。

213e22b18742a474dd545316a61e17e6.png

类加载

实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类。下面通过一个简单例子来说明Class对象被加载的时间节点:

package first.test;class Student {static { System.out.println("study"); }}class Teacher{static { System.out.println("education"); }}public class watch{public static void print(Object obj) {System.out.println(obj);}public static void main(String[] args) {print("1");new Student();print("2");new Teacher();print("3");}}

在上述代码中,每个类Student、Teacher都存在一个static语句:

运行结果:

1study2education3

Java程序在它们开始运行之前并非都被加载到内存的,其各个部分是按需加载,所以在程序使用该类时,类加载器首先会检查这个类的Class对象是否已被加载,如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。

81c2e8d14cd3352eabe57f6386ff62f8.png

Class.forName方法

Class clazz=Class.forName("com.wk.new.Student");

forName方法是Class类的一个static成员方法,程序中所创建的所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。通过forName方法,我们可以获取到类对应的Class对象引用。因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象。

306bff4e80d63fbc555801e6b29b402d.png

Class字面常量

在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:

Class clazz = Student.class;

这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。

72d83f924597de13f6246581b58f0d11.png

理解Java的反射技术

如前言的反射机制所说,反射即是在程序动态运行的时候,对于任意一个类,可以获得其所有的方法以及变量。

在反射包中,我们常用的类主要有:

Constructor类:表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象;Field类:Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private);Method类:Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。下面将对这几个重要类进行分别说明(类中的方法其实很好区分,每个方法后面+s,表示获取到相应类的所有对象;每个方法前面+declared,表示获取到不仅仅是public对象,还包括private对象)。

b7ed949b29214c88bf3076561a1e6d47.png

Constructor类

获取构造方法们//返回指定参数类型、具有public访问权限的构造函数对象* Constructor>[] getConstructors()* Constructor getConstructor(类>... parameterTypes)//返回指定参数类型、所有声明的(包括private)构造函数对象* Constructor getDeclaredConstructor(类>... parameterTypes)* Constructor>[] getDeclaredConstructors()

示例

Class> clazz = null;//获取Class对象的引用clazz = Class.forName("com.wk.new.Student");//此时必须无参构造函数,否则将抛异常Student student = (Student) clazz.newInstance();student.setAge(3);student.setName("hali");

获取带String参数的public构造函数

Constructor cs =clazz.getConstructor(String.class);//创建StudentStudent student= (Student) cs.newInstance("lixiang");student.setAge(18);

取得指定带int和String参数构造函数,该方法是私有构造

Constructor cs=clazz.getDeclaredConstructor(int.class,String.class);//由于是private必须设置可访问,否则创建示例没有权限(底层)cs.setAccessible(true);//创建Dog对象Dog dog= (Dog) cs.newInstance(3,"hali");

Method类

获取成员方法们:* Method[] getMethods()* Method getMethod(String name, 类>... parameterTypes)* Method[] getDeclaredMethods()* Method getDeclaredMethod(String name, 类>... parameterTypes)

示例

Class clazz = Class.forName("com.wk.new.Student");//根据参数获取public的Method,包含继承自父类的方法Method method = clazz.getMethod("study",int.class,String.class);//获取所有public的方法:Method[] methods =clazz.getMethods();//获取当前类的方法包含private,该方法无法获取继承自父类的methodMethod methodSelf = clazz.getDeclaredMethod("studySelf");//获取当前类的所有方法包含private,该方法无法获取继承自父类的methodMethod[] methodsSelf=clazz.getDeclaredMethods();

Field类

获取成员变量们

* Field[] getFields() :获取所有public修饰的成员变量* Field getField(String name) 获取指定名称的 public修饰的成员变量* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符* Field getDeclaredField(String name)

示例

Class> clazz = Class.forName("com.wk.new.Student");//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段Field field = clazz.getField("age");//获取所有修饰符为public的字段,包含父类字段Field fields[] = clazz.getFields();//获取当前类所字段(包含private字段),不包含父类的字段Field fields2[] = clazz.getDeclaredFields();

注意:Field其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,相信这个不难理解。另外,final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。

185b2ddc64abf9984da6d22c9080de82.png

简单的框架实现

一直说反射是框架的核心,接下来就简单写一个框架,来帮助我们创建任何类的对象(以Student为例)。编写一个Student类

package com.wk.new;public class Student {public void study(){System.out.println("study");}}

编写配置文件 pro.properties

className=com.wk.new.StudentmethodName=study

编写框架类

/*** 框架类*/public class ReflectTest {public static void main(String[] args) throws Exception {//可以创建任意类的对象,可以执行任意方法//Properties对象Properties pro = new Properties();//获取class目录下的配置文件ClassLoader classLoader = ReflectTest.class.getClassLoader();InputStream is = classLoader.getResourceAsStream("pro.properties");pro.load(is);//获取配置文件中定义的数据String className = pro.getProperty("className");String methodName = pro.getProperty("methodName");Class cls = Class.forName(className);//创建对象Object obj = cls.newInstance();Method method = cls.getMethod(methodName);//执行获取的方法method.invoke(obj);}}

好了,写到这里一个简单的框架就实现了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值