Java反射机制

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类型的对象。所有的类都有一个自己的类对象,用于提供类本身的信息,比如方法属性,构造方法等。这个对象像是一个镜子,通过这个对象可以看到类的所有结构,所以,我们形象的称之为:反射。

-w589

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.forname获取
Class c2 = Class.forName("reflection.Student");
System.out.println(c2.hashCode());

//方式三:通过类名.class获取
Class c3 = Student.class;
System.out.println(c3.hashCode());

//方式四:基本内置数据类型的包装类有一个TYPE属性
Class c4 = Integer.TYPE;
System.out.println(c4);

//获取父类的class对象
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内存图分析反射

  1. 类的加载(Load):类加载器完成。从.class文件中加载类,创建java.lang.Class对象。
  2. 类的链接(Link):确保类信息符合JVM规范,将类的类变量分配内存,存放到相应的堆栈内存中,符号引用替换为引用地址。
  3. 类的初始化(Initialize):JVM调用类构造器的<clinit>()方法对类进行初始化,初始化过程将可合并的语句进行合并,并触发父类的初始化。

0x02 Java反射有哪些作用

2.1 Java反射的作用

  1. 通过反射获取运行时,类的完整结构,包括属性和方法
  2. 对类的一些属性和方法进行修改,包括可以修改私有属性
  3. 动态创建对象,执行实例化对象的方法

下面举几个例子

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(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());

//2.1 获取所有 public 访问权限的变量,包含父类中的public变量
// 包括本类声明的和从父类继承的
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());
}

//2.2 获取所有本类声明的变量(不问访问权限)但是这个只有本类的,不包含父类变量
fields = mClass.getDeclaredFields();
System.out.println("全部变量:");
//3. 遍历变量并输出变量信息
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(){
//1.获取并输出类的名称
Class mClass = SonClass.class;
System.out.println("类的名称:" + mClass.getName());

//2.1 获取所有 public 访问权限的方法
//包括自己声明和从父类继承的
Method[] mMethods = mClass.getMethods();

//2.2 获取所有本类的的方法(不问访问权限)
//Method[] mMethods = mClass.getDeclaredMethods();

//3.遍历所有方法
for (Method method :
mMethods) {
//获取并输出方法的访问权限(Modifiers:修饰符)
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{
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();

//2. 获取私有方法
//第一个参数为要获取的私有方法的名称
//第二个为要获取方法的参数的类型,参数为 Class...,没有参数就是null
//方法参数也可这么写 :new Class[]{String.class , int.class}
Method privateMethod =
mClass.getDeclaredMethod("privateMethod", String.class, int.class);

//3. 开始操作方法
if (privateMethod != null) {
//获取私有方法的访问权
//只是获取访问权,并不是修改实际权限
privateMethod.setAccessible(true);

//使用 invoke 反射调用私有方法
//privateMethod 是获取到的私有方法
//testClass 要操作的对象
//后面两个参数传实参
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"

正常运行后,打印如下,调用私有方法成功:

Java Reflect 666

2.6 修改私有变量

以修改 TestClass 类中的私有变量 MSG 为例,其初始值为 “Original” ,我们要修改为 “Modified”。

/**
* 修改对象私有变量的值
* 为简洁代码,在方法上抛出总的异常
*/
private static void modifyPrivateFiled() throws Exception {
//1. 获取 Class 类实例
TestClass testClass = new TestClass();
Class mClass = testClass.getClass();

//2. 获取私有变量
Field privateField = mClass.getDeclaredField("MSG");

//3. 操作私有变量
if (privateField != null) {
//获取私有变量的访问权
privateField.setAccessible(true);

//修改私有变量,并输出以测试
System.out.println("Before Modify:MSG = " + testClass.getMsg());

//调用 set(object , value) 修改变量的值
//privateField 是获取到的私有变量
//testClass 要操作的对象
//"Modified" 为要修改成的值
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对象
Class c1 = Class.forName("reflection.User");

//利用Class对象来构造对象
User user1 = (User)c1.newInstance();
System.out.println(user1);
// User{name='null', age=0, no=0}

// 利用class对象的构造器,进行有参构造
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user2 = (User)declaredConstructor.newInstance("V0W",18,123);
System.out.println(user2);
//User{name='V0W', age=18, no=123}

// 通过反射调用对象的方法
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"); // 由于name是private,直接赋值,权限不够,can not access
System.out.println(user4.getName());
}
}

0xFF 参考链接

文章作者: V0WKeep3r
文章链接: http://v0w.top/2020/10/03/javareflection/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 V0W's Blog
支付宝打赏
微信打赏