Java中的反射

反射的概念

反射就是把一个类的包、属性、方法、构造函数等信息都分别封装成一个个对象,这些对象构成每一个class对象。

class对象是什么呢?

Java的源文件是.java,源文件是给人看的语言,但是只能读懂01二进制数的底层无法读懂它。只有源文件通过编译生成这个类的.class二进制字节码才能加载进JVM中,此时,它里面的指令才能被机器识别并执行。.class文件加载进JVM之后生成的就是一个Class类的对象。

Class类是final类型的,所以它没有子类,只有一个父类Type类。Class类描述了类的名字,类的访问属性,类所在的包,字段名称的列表,方法名称的列表等等。

Class类代表了Java类,他的实例对象对应的是类在内存中的字节码。拿到一个类的Class类的对象,我们可以通过它内部描述的信息做很多事,比如:创建一个该类的对象,直接执行这个对象的方法,或是修改它的属性等等。

每个类的class对象有且只有一个

Java中,每一个类的Class对象有且只有一个。.class文件通过类加载器加载进JVM中,由JVM完成class对象的创建和初始化,并存放在JVM的方法区。因此,Class类 没有公共构造方法,Class 对象是在加载类时由 Java 虚拟机通过调用类加载器中的 defineClass 方法自动构造的。

如果类的字节码已经存入内存中了,那么就直接获取,如果内存中还没有这个类的字节码,那么,就用类加载器加载进jvm的缓存中,以备以后使用。

Java中的9个预定义字节码

基本数据类型字节码备注
bytebyte.class
charchar.class
booleanboolean.class
intint.class
floatfloat.class
longlong.class
doubledouble.class
voidvoid.class不是基础数据类型
PS:void不是基础数据类型

数组的字节码

每一种数组都有对应的Class字节码,比如:int[].classString[].class

Class类的结构

下图是Class类的结构图,每一个Class文件就是由这些内容组成,它们描述了一个类的组成,并把java代码中的语句翻译成机器可以执行的栈指令集放在Class字节码中。
请添加图片描述

使用javap查看类的.class文件

javap -c -v file.class

查看Dog类的类结构信息

给一个Dog类

package com.teresa.reflectdemo;

public class Dog {
	public Dog(){}
	public String bank(){
		return "woof";
	}
}

通过javap -c -v Dog.class对class文件反汇编得到Gog类的类文件:

请添加图片描述
请添加图片描述

关于class文件的详细解读,限于篇幅原因,这里就仅仅抛砖引玉介绍这么多啦,如果你对Class的结构感兴趣,那我推荐你看《深入理解Java虚拟机–JVM高级特性与最佳实践》一书,此书结构清晰,内容深入浅出,是了解JVM的不二选择。

如果你有英文阅读能力,还可以去看Java官方给出的虚拟机规范:https://docs.oracle.com/javase/specs/index.html

反射的入门

先来个类一会用于它来进行反射实践:

package com.teresa.reflectdemo;

public class Person {
	public Person(){}
	public Person(String name,int age){
		this.name = name;
		this.age = age;
	}
	private String name;
	private int age;

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
}

获取类字节码对象的方法

  • 类简单名.class

  • object对象中的getClass()方法。这个方法是Object类中的,每个对象都可以使用这个方法获取class字对象

  • Class.forName(类全名)

//Class.forName(类全名)
Class<Person> claz1 = (Class<Person>) Class.forName("com.teresa.reflectdemo.Person"); 

//类简单名.class
Class<Person> claz2 = Person.class;

//object对象中的getClass()方法
Class<Person> claz3 = (Class<Person>) new Person().getClass();

System.out.println("claz1 == claz2:" + (claz1 == claz2));
System.out.println("claz1 == claz3:" + (claz1 == claz3));

结果:
claz1 == claz2:true
claz1 == claz3:true

每个类加载器加载的class对象只会在JVM中保留一个,所以 claz1 == claz2==claz3 ==true。

Class类中的几个常用方法

Integer.Type:代表这个类所代表的基本类型的字节码==于int.class。

isPrimitive():判断字节码是否是基本类型的字节码。

isArray():判断字节码是否是数组的字节码。

static Class<?> forName(String className) :返回给定字符串名表示的类或接口的 Class 对象(字节码)。

String getName() :以 String 的形式返回此 Class 对象所表示的类全名。

System.out.println("Integer.TYPE == int.class: "+(Integer.TYPE == int.class));
System.out.println("int.class.isPrimitive(): "+ int.class.isPrimitive());
System.out.println("String[].class.isArray(): "+ String[].class.isArray());

System.out.println("String[].class.getName(): "+String[].class.getName());
System.out.println("String.class.getName(): "+String.class.getName());
System.out.println("Annotation.class.getName(): "+ Annotation.class.getName());
System.out.println("void.class.getName(): "+ void.class.getName());
System.out.println("int.class.getName(): "+ int.class.getName());
System.out.println("Person.class.getName(): "+ Person.class.getName());

结果:
Integer.TYPE == int.class: true
int.class.isPrimitive(): true
String[].class.isArray(): true

String[].class.getName(): [Ljava.lang.String;
String.class.getName(): java.lang.String
Annotation.class.getName(): java.lang.annotation.Annotation
void.class.getName(): void
int.class.getName(): int
Person.class.getName(): com.teresa.reflectdemo.Person

构造函数的反射:Contructor 类

获取构造函数

  • Constructor<?>[] getConstructors() 返回类的所有公共构造方法
//getConstructors:获取public的构造函数
Constructor[] cons = claz1.getConstructors();
for (Constructor con : cons) {
			System.out.println("Constructor name=" + con.getName() + " ,ParameterType = " + con.getParameterTypes());
			Class[] clazs = con.getParameterTypes();
			for (Class claz : clazs) {
				System.out.println("ParameterType class=" + claz.getName());
			}
}
结果:

  • Constructor<?>[] getDeclaredConstructors() 返回类声明的所有构造方法,包括非public限定的。
		Constructor[] conss = claz1.getDeclaredConstructors();
		for (Constructor con : conss) {
			if (!con.isAccessible()) con.setAccessible(true);
			System.out.println("Constructor name=" + con.getName() + " ,ParameterType = " + con.getParameterTypes());
			Class[] clazs = con.getParameterTypes();
			for (Class claz : clazs) {
				System.out.println("ParameterType class=" + claz.getName());

			}
			System.out.println("");
		}
  • Constructor<T> getConstructor(Class<?>... parameterTypes) 返回一个此 Class 对象所表示的类的指定的带参数的公共构造方法

  • Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回一个此 Class 对象所表示的类或接口的指定的带参数的构造方法,包括非public限定的。

		Constructor con = claz1.getDeclaredConstructor(String.class,int.class);
		Person lilei = (Person) con.newInstance("lilei",20);
		System.out.println("name = "+lilei.getName());

使用构造函数

Contructor 类代表某个类中的一个构造方法的,它包含此构造函数所有的描述。

T newInstance(Object... initargs) : 使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,并用指定的初始化参数初始化该实例。

		Constructor con = claz1.getDeclaredConstructor(String.class,int.class);
		Person lilei = (Person) con.newInstance("lilei",20);
		System.out.println("name = "+lilei.getName());

字段的反射:Field类

获取类中的字段

  • Field getField(String name) :返回一个此 Class 对象所表示的类或接口的指定公共成员字段。

  • Field[] getFields() :返回此 Class 对象所表示的类或接口的所有可访问公共字段。

    Field[] fields = claz1.getFields();
    for (Field field : fields) {
    	System.out.println("Field name=" + field.getName() + " ,type = " + field.getType());
    }
    结果:
    空(因为Person中没有public修饰的字段)
    
  • Field getDeclaredField(String name): 返回一个此 Class 对象所表示的类或接口的指定已声明字段。

    Person hanmeimei = new Person("hanmeimei",20);
    Field nameFiled = claz1.getDeclaredField("name");
    
  • Field[] getDeclaredFields() :返回此 Class 对象所表示的类或接口所声明的所有字段。

Field[] fields1 = claz1.getDeclaredFields();
for (Field field : fields1) {
	if (!field.isAccessible()) field.setAccessible(true);
	System.out.println("Field name=" + field.getName() + " ,type = " +field.getType());
}
结果:
Field name=name ,type = class java.lang.String
Field name=age ,type = int

使用类中的字段

Field类代表某个类中的成员变量的描述,一个Field对象是一个成员变量的描述,通过它可以操作修改这个成员变量的具体内容。

不同的对象的某一字段的值是不同的,所以,想要获取到具体对象的某一个成员变量的值就必须指明具体的对象。

  • Object get(Object obj) :返回指定对象上此 Field 表示的字段的具体值。

  • void setAccessible(boolean flag) :将此对象的 accessible 标志设置为指示的布尔值。遇到私有字段时通过第二个函数对私有成员进行设置显示,这样才能获取到具体的值

  • Class<?> getType() :返回此 Field 对象所表示字段的声明类型。

Person hanmeimei = new Person("hanmeimei",20);
Field nameFiled = claz1.getDeclaredField("name");
if (!nameFiled.isAccessible()) {
	nameFiled.setAccessible(true);
}
String name = (String)nameFiled.get(hanmeimei);
System.out.println("name:" + name);

结果:
name:hanmeimei

方法的反射:Method类

获取类中方法

  • Method getMethod(String name, Class<?>... parameterTypes) :返回一个此 Class 对象所表示的类或接口的指定公共成员方法。

    • name 参数:是一个 String,用于指定所需方法的简称。
    • parameterTypes 参数:是按声明顺序标识该方法形参类型的 Class 对象的一个数组。如果 parameterTypes 为 null,则按空数组处理。
  • Method[] getMethods() :返回此 Class 对象所表示的类或接口。包括由该类或接口自己声明的以方法及从超类和超接口继承的公共方法。

    Method[] methods = claz1.getMethods();
    for (Method method : methods) {
    			System.out.println("Method name=" + method.getName() + " ,returnType = " + method.getReturnType());
    }
    
    结果:
    Method name=getName ,returnType = class java.lang.String
    Method name=setName ,returnType = void
    Method name=getAge ,returnType = int
    Method name=setAge ,returnType = void
    Method name=equals ,returnType = boolean
    Method name=toString ,returnType = class java.lang.String
    Method name=hashCode ,returnType = int
    Method name=getClass ,returnType = class java.lang.Class
    Method name=notify ,returnType = void
    Method name=notifyAll ,returnType = void
    Method name=wait ,returnType = void
    Method name=wait ,returnType = void
    Method name=wait ,returnType = void
    
  • Method getDeclaredMethod(String name, Class<?>... parameterTypes): 返回一个此 Class 对象所表示的类或接口的指定已声明方法。

    //创建一个person对象 ("may", 25),然后把它的name改成("teresa May")	
    Field nameField = claz1.getDeclaredField("name");
    
  • Method[] getDeclaredMethods() :返回此 Class 对象表示的类或接口自己声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

    Method[] methodss = claz1.getDeclaredMethods();
    for (Method method : methodss) {
    		if (!method.isAccessible()) method.setAccessible(true);
    		System.out.println("Method_name=" + method.getName() + " ,returnType = " + method.getReturnType());
    }
    
    结果:
    Method_name=getName ,returnType = class java.lang.String
    Method_name=setName ,returnType = void
    Method_name=getAge ,returnType = int
    Method_name=setAge ,returnType = void
    
  • Method getEnclosingMethod() :如果此 Class 对象表示某一方法中的一个本地或匿名类,则返回 Method 对象,它表示底层类的立即封闭方法。

使用类中的方法

Method类代表某个类中的某个方法的描述。

  • Object invoke(Object obj, Object... args) :让指定的对象obj以arg中封装的参数调用我们获取到的那个obj的方法。如果第一个参数是null时,表示我们要调用的方法是静态方法。因为静态方法不需要对象。

反射的路径:先创建类中方法或者任何成员的对象,然后把这个对象作用到对应的目标类身上获取我们想要的效果

//创建一个person对象 ("may", 25),然后把它的name改成("teresa May")
		Constructor<Person> constructor = claz1.getDeclaredConstructor(String.class, int.class);
		Person person = constructor.newInstance("may", 25);
		
		Field nameField = claz1.getDeclaredField("name");
		if (!nameField.isAccessible()) nameField.setAccessible(true);
		System.out.println("name is : "+nameField.get(person));
		nameField.set(person, "teresa May");
		System.out.println("name_changed as :" + nameField.get(person));


		Method getAgeMethod = claz1.getDeclaredMethod("getAge");
		if (!getAgeMethod.isAccessible()) getAgeMethod.setAccessible(true);
		int getAge = (int) getAgeMethod.invoke(person);
		System.out.println("getAgeMethod: "+getAge);

结果:
name is : may
name_changed as :teresa May
getAgeMethod: 25

数组的反射

JDK1.5以前,当我们在invoke(Object obj, Object[] args)中传入第二个参数是一个数组时,JVM会自动把这个数组里面的内容分别取出来当做方法的参数列表。

  • 如果这个方法的参数本身就是一个数组时就需要对这个参数用Object[]数组进行再包装,然后再当参数传入,比如:Object[]{String[]{"a","b"}}
  • 对要传进去作参数的数组用Object强制转换,表示这个数组就只表示一个参数。从而避免了这个数组被拆开,里面的数据被拿出来做参数列表。比如:(Object)String[]{"a","b"}

JDK1.5开始,可以使用数组也可以使用可变参数列表invoke(Object obj, Object... args),编译器认为第二个参数开始的所有参数是方法的参数列表。

数组的反射:java.lang.reflect.Array类

static int getLength(Object array) : 以 int 形式返回指定数组对象的长度。

static Object get(Object array, int index) :返回指定数组对象中索引映射的值。

获取类加载器ClassLoader

ClassLoader getClassLoader(): 返回该类的类加载器

本文就是反射API的使用,虽然知识不难,但是时间长了好记性不如烂笔头,所以还是做个记录。有任何问题可以评论留言讨论。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值