Java反射机制 0x01 一些概念&类对象 1.0 静态语言VS动态语言 所谓动态语言 就是在运行过程中可以改变其自身结构的语言。比如我们在python中运行一段代码,定义一个变量,可以不定义它到底是一个什么数据结构的变量,而在代码运行期间,其他代码或者不同条件对这个变量进行赋值,赋什么值,它就是什么结构。
而静态语言 则不同,在定义一个变量的时候,必须同时定义它的类型结构等等,并且一般情况下更改不了。
常见的动态语言有:python,php,JavaScript,C#等。
常见的静态语言有:C,C++,Java等。
1.1 反射
Java 反射机制在程序运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种 动态的获取信息 以及 动态调用对象的方法 的功能称为 java 的反射机制。
反射机制很重要的一点就是“运行时”,其使得我们可以在程序运行时加载、探索以及使用编译期间完全未知的 .class 文件。换句话说,Java 程序可以加载一个运行时才得知名称的 .class 文件,然后获悉其完整构造,并生成其对象实体、或对其 fields(变量)设值、或调用其 methods(方法)。
Reflection反射是Java被称为动态语言的关键,反射允许程序执行期间借助Reflection API取得任何类的内部信息,并直接操作任何对象的内部属性和方法。
这些概念性的东西太抽象了。简单点说,发射给Java这个静态语言增加了动态语言的特征,使Java变成了准动态语言。我们可以利用反射机制,获取一个类的变量方法,甚至包括private的变量方法,并且可以更改其原有的结构。
1.2 类对象 类对象:这个对象就是在加载完class,之后在堆内存方法区产生的Class类型的对象。所有的类都有一个自己的类对象,用于提供类本身的信息,比如方法属性,构造方法等。这个对象像是一个镜子,通过这个对象可以看到类的所有结构,所以,我们形象的称之为:反射。
1.3 Reflection API
java.lang.Class //代表一个类
java.lang.reflect.Method //代表类的方法
java.lang.reflect.Field //代表类的成员变量
java.lang.reflect.Constructor //代表类的构造器
1.4 获取类对象的方法 获取类对象的三种主要方式:
1. Class.forName("com.charactor.Hero"); 2. Hero.class; 3. Hero hero = new Hero(); hero.getClass() //以下并非主要方式 4. 基本数据类型的包装类,如Integer有一个TYPE属性可以用于获取class 5. 子类对象可以通过getSuperclass获取父类class对象
具体举个例子:
package reflection;import static java.lang.Class.forName;public class Test01 { public static void main (String[] args) throws ClassNotFoundException { Person person = new Student(); System.out.println("this person is: " + person.name); Class c1 = person.getClass(); System.out.println(c1.hashCode()); Class c2 = Class.forName("reflection.Student" ); System.out.println(c2.hashCode()); Class c3 = Student.class; System.out.println(c3.hashCode()); Class c4 = Integer.TYPE; System.out.println(c4); Class c5 = c1.getSuperclass(); System.out.println(c5); } } class Person { public String name="nobody" ; public Person (String name) { this .name = name; } public Person () { } @Override public String toString () { return "Person{" + "name='" + name + '\'' + '}' ; } } class Student extends Person { public int no; public int age; @Override public String toString () { return "Student{" + "name='" + name + '\'' + ", no=" + no + ", age=" + age + '}' ; } }
1.5 哪些类型有类对象
class
interface 接口
[]数组
enum 枚举
annotation 注解
primitive type 基本数据类型
void
1.6 Java内存图分析反射
类的加载(Load):类加载器完成。从.class文件中加载类,创建java.lang.Class对象。
类的链接(Link):确保类信息符合JVM规范,将类的类变量分配内存,存放到相应的堆栈内存中,符号引用替换为引用地址。
类的初始化(Initialize):JVM调用类构造器的<clinit>()
方法对类进行初始化,初始化过程将可合并的语句进行合并,并触发父类的初始化。
0x02 Java反射有哪些作用 2.1 Java反射的作用
通过反射获取运行时,类的完整结构,包括属性和方法
对类的一些属性和方法进行修改,包括可以修改私有属性
动态创建对象,执行实例化对象的方法
下面举几个例子
2.2 获取类的所有变量信息 FatherClass
package reflection;public class FatherClass { public String mFatherName; public int mFatherAge; public void printFatherMsg () {} }
SonClass
package reflection;public class SonClass extends FatherClass { private String mSonName; protected int mSonAge; public String mSonBirthday; public void printSonMsg () { System.out.println("Son Msg - name : " + mSonName + "; age : " + mSonAge); } private void setSonName (String name) { mSonName = name; } private void setSonAge (int age) { mSonAge = age; } private int getSonAge () { return mSonAge; } private String getSonName () { return mSonName; } }
MainTest
package reflection;import java.lang.reflect.Field;import java.lang.reflect.Modifier;public class MainTest { private static void printFields () { Class mClass = SonClass.class; System.out.println("类的名称:" + mClass.getName()); Field[] fields = mClass.getFields(); System.out.println("public 变量:" ); for (Field field : fields) { int modifiers = field.getModifiers(); System.out.print(Modifier.toString(modifiers) + " " ); System.out.println(field.getType().getName() + " " + field.getName()); } fields = mClass.getDeclaredFields(); System.out.println("全部变量:" ); for (Field field : fields) { int modifiers = field.getModifiers(); System.out.print(Modifier.toString(modifiers) + " " ); System.out.println(field.getType().getName() + " " + field.getName()); } } public static void main (String[] args) { printFields(); } }
输出:
类的名称:reflection.SonClass public 变量-getFields(): public java.lang.String mSonBirthday public java.lang.String mFatherName public int mFatherAge 全部变量-getDeclaredFields(): private java.lang.String mSonName protected int mSonAge public java.lang.String mSonBirthday
可以发现:我们可以通过反射来获取类的变量,注意两个函数的区别:
调用 getFields()
方法,输出 SonClass 类以及其所继承的父类( 包括 FatherClass 和 Object ) 的 public 方法。注:Object 类中没有成员变量,所以没有输出。
调用 getDeclaredFields()
, 输出 SonClass 类的所有成员变量,不问访问权限。
2.3 获取类的方法信息 private static void printMethods () { Class mClass = SonClass.class; System.out.println("类的名称:" + mClass.getName()); Method[] mMethods = mClass.getMethods(); for (Method method : mMethods) { int modifiers = method.getModifiers(); System.out.print(Modifier.toString(modifiers) + " " ); Class returnType = method.getReturnType(); System.out.print(returnType.getName() + " " + method.getName() + "( " ); Parameter[] parameters = method.getParameters(); for (Parameter parameter: parameters) { System.out.print(parameter.getType().getName() + " " + parameter.getName() + "," ); } Class[] exceptionTypes = method.getExceptionTypes(); if (exceptionTypes.length == 0 ){ System.out.println(" )" ); } else { for (Class c : exceptionTypes) { System.out.println(" ) throws " + c.getName()); } } } }
调用 getMethods() 方法 获取 SonClass 类所有 public 访问权限的方法,包括从父类继承的。打印信息中,printSonMsg() 方法来自 SonClass 类, printFatherMsg() 来自 FatherClass 类,其余方法来自 Object 类。
类的名称:obj.SonClass public void printSonMsg( ) public void printFatherMsg( ) public final void wait( ) throws java.lang.InterruptedException public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException public final native void wait( long arg0, ) throws java.lang.InterruptedException public boolean equals( java.lang.Object arg0, ) public java.lang.String toString( ) public native int hashCode( ) public final native java.lang.Class getClass( ) public final native void notify( ) public final native void notifyAll( )
调用 getDeclaredMethods() 方法 打印信息中,输出的都是 SonClass 类的方法,不问访问权限。 类的名称:obj.SonClass
private int getSonAge ( ) private void setSonAge ( int arg0, ) public void printSonMsg ( ) private void setSonName ( java.lang.String arg0, ) private java.lang.String getSonName ( )
2.4 利用反射直接调用私有变量和私有方法 在上面,我们成功获取了类的变量和方法信息,验证了在运行时 动态的获取信息 的观点。那么,仅仅是获取信息吗?我们接着往后看。 都知道,对象是无法访问或操作类的私有变量和方法的,但是,通过反射,我们就可以做到。没错,反射可以做到!下面,让我们一起探讨如何利用反射访问 类对象的私有方法 以及修改 私有变量或常量。
TestClass.java
public class TestClass { private String MSG = "Original" ; private void privateMethod (String head , int tail) { System.out.print(head + tail); } public String getMsg () { return MSG; } }
2.5 访问私有方法 以访问 TestClass 类中的私有方法 privateMethod(…) 为例,方法加参数是为了考虑最全的情况。
private static void getPrivateMethod () throws Exception { TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); Method privateMethod = mClass.getDeclaredMethod("privateMethod" , String.class, int .class); if (privateMethod != null ) { privateMethod.setAccessible(true ); privateMethod.invoke(testClass, "Java Reflect " , 666 ); } }
需要注意的是,第3步中的 setAccessible(true) 方法,是获取私有方法的访问权限,如果不加会报异常 IllegalAccessException,因为当前方法访问权限是private 的,如下:
java.lang.IllegalAccessException: Class MainClass can not access a member of class obj.TestClass with modifiers "private"
正常运行后,打印如下,调用私有方法成功:
2.6 修改私有变量 以修改 TestClass 类中的私有变量 MSG 为例,其初始值为 “Original” ,我们要修改为 “Modified”。
private static void modifyPrivateFiled () throws Exception { TestClass testClass = new TestClass(); Class mClass = testClass.getClass(); Field privateField = mClass.getDeclaredField("MSG" ); if (privateField != null ) { privateField.setAccessible(true ); System.out.println("Before Modify:MSG = " + testClass.getMsg()); privateField.set(testClass, "Modified" ); System.out.println("After Modify:MSG = " + testClass.getMsg()); } }
此处代码和访问私有方法的逻辑差不多,从输出信息看出 修改私有变量 成功:
Before Modify:MSG = Original After Modify:MSG = Modified
2.7 利用反射创建对象调用方法 package reflection;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Test02 { public static void main (String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException { Class c1 = Class.forName("reflection.User" ); User user1 = (User)c1.newInstance(); System.out.println(user1); Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int .class, int .class); User user2 = (User)declaredConstructor.newInstance("V0W" ,18 ,123 ); System.out.println(user2); User user3 = (User) c1.newInstance(); Method setName = c1.getDeclaredMethod("setName" , String.class); setName.invoke(user3,"V0Wtest" ); System.out.println(user3.getName()); System.out.println("===============================" ); User user4 = (User) c1.newInstance(); Field name = c1.getDeclaredField("name" ); name.setAccessible(true ); name.set(user4,"V0W4" ); System.out.println(user4.getName()); } }
0xFF 参考链接