JVM简述
了解Java的反射,首先需要知道虚拟机(JVM)是如何处理Java程序的。附图如下:
假定程序中有一句源码如下:
Object o = new Object();
详细流程如下:
- 你的代码(.java文件)会编译成(.class文件);
- JVM启动,调用main方法,.class文件会被类加载器加载进JVM的内存中,类(Object)的元数据信息会被加载到方法区,创建了该类的class对象(类型对象,每个类只有一个)到堆中;
- jvm识别到new指令,准备创建对象(引用o指向的对象)前,先检查类是否加载,若加载好,JVM会为新生对象分配内存(内存空间会初始化为零值);
- 这句话已经执行完毕。当所有程序(如果还有其他代码)跑完,jvm关闭,程序停止。
结论:类似这种创建对象的方式(new),对象的类型在编译时就已经确定下来了。
反射的功能
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
Java反射框架提供的功能如下:
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
- 在运行时调用任意一个对象的方法
反射的基本运用
我对反射的理解:类加载时生成的class对象,包含了类的所有信息。在创建该类对应的具体实例之前,可以直接通过操作class对象来调用方法或获取属性。例如:学校准备招聘一个数学老师(class模板就是一个可以教数学的老师)。现在需要对所有老师明年的教学任务进行排班,这个时候我们还不知道缺的这个数学老师具体是谁,但我们可以现在就可以对他/她安排任务如:明年数学老师(数学:class对象获取的属性)在每周二上午授课(授课:class对象调用方法)。
具体用法如下:
- 获取Class对象
// 方式一:path为student对应的包路径(com.netopstec.reflectionlearning.entity.Student)
Class student = Class.forName(String path);
// 方式二:所有数据类型包括基本数据类型(.class)获取,包装类型(.TYPE)获取
Class intClass = int.class;
Class integerClass = Integer.TYPE;
// 方式三:具体对象的getClass方法
StringBuilder str = new StringBuilder("123");
Class strClass = str.getClass();
- 判断是否为某个类的实例
// 这里涉及到java的多态,即判断某个实例是否为某种类型
public native boolean isInstance(Object obj);
- 创建实例
// 方式一:class对象的newInstance()方法
Object str = String.class.newInstance();
// 方式二:获取到构造器后调用newInstance()方法
Class c = String.class;
Constructor constructor = c.getConstructor(String.class);
Object obj = constructor.newInstance("123");
- 获取方法
// 获取该类中声明的方法(任何权限修饰符均会返回),但不会返回继承的方法
public Method[] getDeclaredMethods() throws SecurityException
// 获取该类中的所有公有方法(public修饰的),包括继承了的方法
public Method[] getMethods() throws SecurityException
// 返回某个特定公有方法(第一个参数为方法的名称,随后的参数是该方法的参数类型)
public Method getMethod(String name, Class<?>... parameterTypes)
测试代码如下:
package com.netops.java8_test1.demo17;
import java.lang.reflect.Method;
/**
* @author zhenye 2018/2/7
*/
public class Reflection {
public static void main(String[] args) throws NoSuchMethodException {
Class clazz = MethodClass.class;
System.out.println("getMethods方法返回该类实际拥有所有的公有方法:");
Method[] totalPubMethods = clazz.getMethods();
for (Method method : totalPubMethods) {
System.out.println(method);
}
System.out.println("分割线--------------------------------------------------------------------------------------------------分割线");
System.out.println("getDeclaredMethods方法返回该类显示声明的所有方法");
Method[] totalDeclaredMethods= clazz.getDeclaredMethods();
for (Method method : totalDeclaredMethods) {
System.out.println(method);
}
System.out.println("分割线--------------------------------------------------------------------------------------------------分割线");
System.out.println("getMethod方法获取某个特定公有方法,如果想要获取方法的访问修饰符权限不够,会有NoSuchMethodException,结果如下:");
Method method = clazz.getMethod("add", int.class, int.class);
System.out.println(method);
}
class MethodClass {
public int add(int a, int b){
return a+b;
}
private int sub(int a, int b){
return a-b;
}
}
}
执行结果如下:
- 获取类的成员变量
- getFields() 获取该类拥有的所有公有属性
- getDeclaredFields() 获取该类中显示声明的所有属性
- getField(String name) 获取该类的指定属性
用法参考Method
- 方法的调用
public class Reflection {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> klass = MethodClass.class;
Object obj = klass.newInstance();
Method[] methods = klass.getDeclaredMethods();
for (Method method : methods){
// method.setAccessible(true);
System.out.println();
System.out.println(method.getName() + ":" + method.invoke(obj,2,3));
}
}
}
class MethodClass {
public int add(int a,int b) {
return a+b;
}
private int sub(int a,int b) {
return a-b;
}
}
总结: invoke方法的第一个参数是被调用方法的实例(需要知道是具体哪个实例的方法,如果调用invoke方法的是静态方法可以传入null);此时的invoke方法只能调用公有方法,想要调用私有方法需要method.setAccessible(true),确保有权限调用该方法。
- 创建数组
public static void testArray() throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls,25);
//往数组里添加内容
Array.set(array,0,"hello");
Array.set(array,1,"Java");
Array.set(array,2,"fuck");
Array.set(array,3,"Scala");
Array.set(array,4,"Clojure");
//获取某一项的内容
System.out.println(Array.get(array,3));
}
注意事项:
- 反射会额外消耗一定的系统资源。如果不需要动态创建对象,就不需要用反射。
- 反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题。