反射机制允许程序在运行态借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
一、Class类和获取Class的实例的几种方式
1、Class类
程序经过javac.exe命令之后,会生成一个或者多个字节码文件(.class)文件。接着就需要用java.exe命令对某个字节码进行解释运行,所谓解释运行,就是把这个字节码文件(.class)加载到内存中。这个过程就叫类的加载。而对于加载到内存中的类,我们称其为运行时类,运行时类都是Class类的实例。
JRE通过保留Class类型对象, 用来保存某个类的属性、方法和构造器、某个类到底实现了哪些接口(即类的结构)。也就是说可以通过Class类型实例,获得当前类全部结构信息。
总结:
- Class本身也是一个类;
- Class 对象只能由系统建立对象;
- 一个加载的类在 JVM 中只会有一个Class实例,每个类的实例都会记得自己是由哪个 Class 实例所生成; 通过Class可以完整地得到一个类中的所有被加载的结构;
- Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象;
2、class类常用方法
3、获取class实例方法
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
方法1: 调用运行时类属性 .class
方法2:通过运行时类的对象 getClass
方法3:Class类的静态方法,Class.forName
方法4:使用类加载器
/**
* Class对象创建方式
*/
@Test
public void test02() throws ClassNotFoundException {
/**
* 运行时类
*/
Class clazz1 = Person.class;
/**
* 运行时类对象
*/
Person person = new Person();
Class clazz2 = person.getClass();
/**
* Class的静态方法 forName, ***用的最多****
*/
Class clazz3 = Class.forName("com.lucky.Person");
/**
* 通过类加载器
*/
ClassLoader classLoader = ReflectTest.class.getClassLoader();
Class clazz4 = classLoader.loadClass("com.lucky.Person");
}
通过几种方式获得的Class实例都是地址都相同。
System.out.println(clazz1 == clazz2);
System.out.println(clazz1 == clazz3);
System.out.println(clazz1 == clazz4);
4、可以有class的类型
a、 class:
外部类, 成员(成员内部类, 静态内部类), 局部内部类, 匿名内部类
b、 interface: 接口
c、 数组
d、 enum:枚举
e、 annotation:注解@interface
f、 primitive type:基本数据类型
g、void
@Test
public void test03() {
Class c1 = Object.class;
Class c2 = Comparable.class;
Class c3 = String[].class;
Class c4 = int[][].class;
Class c5 = ElementType.class;
Class c6 = Override.class;
Class c7 = int.class;
Class c8 = void.class;
Class c9 = Class.class;
int[] a = new int[10];
int[] b = new int[100];
Class c10 = a.getClass();
Class c11 = b.getClass();
// 只要数组的元素类型与维度一样,就是同一个Class, 即使长度不一样
System.out.println(c10 == c11);
}
二、ClassLoader
类加载的作用: 将class文件字节码内容加载到内存中, 并将这些静态数据转换成方法区的运行时数据结构, 然后在堆中生成一个代表这个类java.lang.Class对象, 作为方法区中类数据的访问入口。
类缓存: 标准的JavaSE类加载器可以按要求查找类, 但一旦某个类被加载到类加载器中, 它将维持加载(缓存) 一段时间。 不过JVM垃圾回收机制可以回收这些Class对象。
三、创建运行时类的对象
通过Class的newInstance() 方法。
/**
* 通过反射创建运行时类的对象,比如通过反射创建一个Person类的对象
*/
@Test
public void test04() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("com.lucky.Person");
clazz.newInstance();
}
注意:
使用newInstance创建对应运行时类的对象时,内部默认调用了无参构造器。因此想要正确使用此方法,必须提供无参构造,并且属性为public。
默认无参构造器:还可以在子类继承父类,默认调用super时,保证此构造函数存在。
四、获取运行时类的完整结构
实现的全部接口
@Test
public void test09(){
Class clazz = Person.class;
Class[] interfaces = clazz.getInterfaces();
for(Class c : interfaces){
System.out.println(c);
}
System.out.println();
//获取运行时类的父类实现的接口
Class[] interfaces1 = clazz.getSuperclass().getInterfaces();
for(Class c : interfaces1){
System.out.println(c);
}
}
所继承的父类
@Test
public void test07(){
Class clazz = Person.class;
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
}
全部的构造器
@Test
public void test08(){
Class clazz = Person.class;
//getConstructors():获取当前运行时类中声明为public的构造器
Constructor[] constructors = clazz.getConstructors();
for(Constructor c : constructors){
System.out.println(c);
}
System.out.println();
//getDeclaredConstructors():获取当前运行时类中声明的所有的构造器
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for(Constructor c : declaredConstructors){
System.out.println(c);
}
全部的方法
@Test
public void test06() throws ClassNotFoundException {
Class clazz = Class.forName("com.lucky.Person");
// 获取类方法
//getMethods():获取当前运行时类及其所有父类中声明为public权限的方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
System.out.println();
//getDeclaredMethods():获取当前运行时类中声明的所有方法。(不包含父类中声明的方法)
// 方法权限 getModifiers
// 返回值 m.getReturnType().getName()
// 方法名 getName
// 参数列表 m.getParameterTypes()
// 异常 m.getExceptionTypes()
Method[] declaredMethods = clazz.getDeclaredMethods();
for(Method m : declaredMethods){
System.out.println(m);
}
}
全部的Field
@Test
public void test05() throws ClassNotFoundException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("com.lucky.Person");
// 获取属性
// 当前类属性, 也可以通过Field获取这个修饰符 类型 名称
Field[] fields1 = clazz.getDeclaredFields();
for (Field field : fields1) {
System.out.println(Modifier.toString(field.getModifiers()));
System.out.println(field.getType());
System.out.println(field.getName());
System.out.println();
}
// 当前类和父类public属性
Field[] fields2 = clazz.getFields();
for (Field field : fields2) {
System.out.println(Modifier.toString(field.getModifiers()));
System.out.println(field.getType());
System.out.println(field.getName());
System.out.println();
}
}
获取注解,获取运行类所在包
五、调用运行时类的指定结构
1、操作运行类某一指定属性
@Test
public void test10() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Class clazz = Class.forName("com.lucky.Person");
// 创建对象
Person person = (Person) clazz.newInstance();
// 获取指定属性
Field field = clazz.getDeclaredField("name");
// 保证当前属性值可以访问
field.setAccessible(true);
// 赋值
field.set(person, "tom");
System.out.println(field.get(person));
}
2、运行某个运行时类方法
@Test
public void test11() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clazz = Class.forName("com.lucky.Person");
// 创建对象
Person person = (Person) clazz.newInstance();
Method method = clazz.getDeclaredMethod("show", String.class);
method.setAccessible(true);
// 又不是静态方法,那么就要用实例来访问
// 用 invoke来执行方法
Object retV = method.invoke(person, "chain");
System.out.println(retV);
// 如果是静态方法直接用
Method showDesc = clazz.getDeclaredMethod("showDesc");
showDesc.setAccessible(true);
//如果调用的运行时类中的方法没有返回值,则此invoke()返回null
// Object retV1 = showDesc.invoke(null);
// 不传入对象也可以,静态方法本来就不用new对象
Object retV1 = showDesc.invoke(Person.class);
System.out.println(retV1);//null
}
3、获取运行时类的带泛型的父类的泛型
@Test
public void test12() throws ClassNotFoundException {
Class clazz = Class.forName("com.lucky.Person");
// 获取泛型类
Type genericSuperclass = clazz.getGenericSuperclass();
ParameterizedType paramType = (ParameterizedType) genericSuperclass;
//获取泛型类型
Type[] actualTypeArguments = paramType.getActualTypeArguments();
System.out.println(actualTypeArguments[0].getTypeName());
System.out.println(((Class) actualTypeArguments[0]).getName());
}