Java技术栈——Java的Class与反射机制原理详解
深入理解java的Class与反射机制
前言
- Class
- 反射机制
深入理解Class对象机制
- Java代码运行阶段
- 类加载
- Class.forName方法
- Class字面常量
理解Java的反射技术
- Constructor类
- Method类
- Field类
简单的框架实现
前言
下面所有的示例学习内容中的所有的类和对象,均以Dog或者cat为例,进行学习。
Class
在Java编译运行过程中,当程序new一个新对象或者引用静态成员变量时,Java虚拟机(JVM)中的类加载器子系统会将对应Class对象加载到JVM中,然后JVM再根据这个类型信息相关的Class对象创建我们需要实例对象或者提供静态变量的引用值。需要特别注意的是,手动编写的每个class类,无论创建多少个实例对象,在JVM中都只有一个Class对象,即在内存中每个类有且只有一个相对应的Class对象。
反射机制
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。一直以来反射技术都是Java中的闪亮点,这也是目前大部分框架(如Spring/Mybatis等)得以实现的支柱。在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。
深入理解Class对象机制
Java代码运行阶段
先通过下图了解下java代码在计算机中运行的三个阶段:
类加载
实际上所有的类都是在对其第一次使用时动态加载到JVM中的,当程序创建第一个对类的静态成员引用时,就会加载这个被使用的类。下面通过一个简单例子来说明Class对象被加载的时间节点:
package first.test;
class Dog {
static { System.out.println("wang!"); }
}
class Cat{
static { System.out.println("miao~"); }
}
public class watch{
public static void print(Object obj) {
System.out.println(obj);
}
public static void main(String[] args) {
print("1");
new Dog();
print("2");
new Cat();
print("3");
}
}
在上述代码中,每个类Dog、Cat都存在一个static语句:
运行结果:
1
wang!
2
miao~
3
Java程序在它们开始运行之前并非都被加载到内存的,其各个部分是按需加载,所以在程序使用该类时,类加载器首先会检查这个类的Class对象是否已被加载,如果还没有加载,默认的类加载器就会先根据类名查找.class文件(编译后Class对象被保存在同名的.class文件中),在这个类的字节码文件被加载时,它们必须接受相关验证,以确保其没有被破坏并且不包含不良Java代码(这是java的安全机制检测),完全没有问题后就会被动态加载到内存中,此时相当于Class对象也就被载入内存了(毕竟.class字节码文件保存的就是Class对象),同时也就可以被用来创建这个类的所有实例对象。
Class.forName方法
Class clazz=Class.forName("first.test.Dog");
forName方法是Class类的一个static成员方法,程序中所创建的所有的Class对象都源于这个Class类,因此Class类中定义的方法将适应所有Class对象。通过forName方法,我们可以获取到类对应的Class对象引用。因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用Class.forName()方法获取Class对象的引用,这样做的好处是无需通过持有该类的实例对象引用而去获取Class对象。
Class字面常量
在Java中存在另一种方式来生成Class对象的引用,它就是Class字面常量,如下:
Class clazz = Dog.class;
这种方式相对前面两种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类。
理解Java的反射技术
如前言的反射机制所说,反射即是在程序动态运行的时候,对于任意一个类,可以获得其所有的方法以及变量。在反射包中,我们常用的类主要有:
1.Constructor类:表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象;
2.Field类:Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private);
3.Method类:Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)。
下面将对这几个重要类进行分别说明(类中的方法其实很好区分,每个方法后面+s,表示获取到相应类的所有对象;每个方法前面+declared,表示获取到不仅仅是public对象,还包括private对象)。
Constructor类
获取构造方法们
//返回指定参数类型、具有public访问权限的构造函数对象
* Constructor<?>[] getConstructors()
* Constructor<T> getConstructor(类<?>... parameterTypes)
//返回指定参数类型、所有声明的(包括private)构造函数对象
* Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
* Constructor<?>[] getDeclaredConstructors()
示例
Class<?> clazz = null;
//获取Class对象的引用
clazz = Class.forName("first.test.Dog");
//此时必须无参构造函数,否则将抛异常
Dog dog = (Dog) clazz.newInstance();
dog.setAge(3);
dog.setName("hali");
获取带String参数的public构造函数
Constructor cs =clazz.getConstructor(String.class);
//创建Dog
Dog dog= (Dog) cs.newInstance("hali");
dog.setAge(3);
取得指定带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("first.test.Dog");
//根据参数获取public的Method,包含继承自父类的方法
Method method = clazz.getMethod("wang",int.class,String.class);
//获取所有public的方法:
Method[] methods =clazz.getMethods();
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method methodSelf = clazz.getDeclaredMethod("wangSelf");
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method[] methodsSelf=clazz.getDeclaredMethods();
Field类
获取成员变量们
* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
示例,getDeclaredFields()方法获取的对象不包括父类,但是getFields()包括。
Class<?> clazz = Class.forName("first.test.Dog");
//获取指定字段名称的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字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
简单的框架实现
一直说反射是框架的核心,接下来就简单写一个框架,来帮助我们创建任何类的对象(以Dog为例)。
编写一个Dog类
package first.test;
public class Dog {
public void wang(){
System.out.println("wang..wang.");
}
}
编写配置文件 pro.properties
className=first.test.Dog
methodName=wang
编写框架类
/**
* 框架类
*/
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);
}
}
至此,一个简单的框架就实现了。
作者:菜C++鸡java
来源:CSDN
嗯嗯,学习了,哈哈哈