java反射机制是java基础重要的一部分,也是后面所学的框架的灵魂。我们先从反射概念开始说吧。
一、java反射的概述
(1)java反射的概念
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取类或对象的信息以及动态调用对象的方法的功能称为java语言的反射机制。
(2)反射的作用
从概念中不难看出,反射就是用来动态获取类或对象的信息以及调用对象的方法。所谓动态,就是运行时。利用反射可以将类的组成部分取出来当做一个对象看待,就是将类的组成部分映射成对象,这就是类信息的获取;操作映射出的对象就是动态调用类的方法。
在下面的总结中会需要定义一个类
首先我写了一个Person类
@MyAnnotation(value = "atguigu")
public class Person extends Creature<String> implements Comparable,MyInterface{
public String name;
private int age;
int id;
public Person() {
super();
}
public Person(String name) {
super();
this.name = name;
}
private Person(String name, int age) {
super();
this.name = name;
this.age = 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;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@MyAnnotation(value = "abc123")//此注解为本人自定义注解
public void show() {
System.out.println("我是一个人!");
}
private Integer display(String nation,Integer i) throws Exception{
System.out.println("我的国籍是:"+nation);
return i;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
@Override
public int compareTo(Object o) {
return 0;
}
public static void info() {
System.out.println("中国人!");
}
class Bird{//内部类
}
}
还有在Person类中用到的自定义注解
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value();
}
二、类的加载和获取字节码引用
从反射的概念可以看出,类的字节码(.class文件里的信息就是类的字节码)是java反射机制的关键点。类的字节码包含类的所有信息。想要在程序中动态的获取类的信息及调用对象的方法,就必须将类的字节码加载到JVM内存中,并获取字节码的引用(引用的数据类型为Class)。
(1)什么是加载类?如何加载类?
加载类就是将类的字节码内容加载到JVM的方法区中,且一个类在JVM中只能存在一份。
加载类的方法有三种
①通过new一个类的对象加载类——静态加载(加载类、连接类、初始化类)
Person p = new Person();//虚拟机在创建对象前,必须先将类加载到JVM内存中,然后调用构造函数,实例化对象
②使用Class静态方法 Class.forName(全类名)——动态加载(加载类、连接类、初始化类)
String className = "com.atguigu.java.Person";//需要加载的类的相对字符串表达方式,必须是类的全称
Class.forName(className);//通过类名获取
③使用类加载器,加载类
ClassLoader classLoader = this.getClass().getClassLoader();//获取当前类的类加载器
classLoader.loadClass("com.atguigu.java.Person");//通过当前类的类加载器获取,传入的字符串也是类的全称
(2)如何获取加载到JVM内存中的类的引用
有四种获取类引用的方法
①通过运行时类的对象获取
Class clazz = p.getClass();//通过对象.getClass方法获取
②通过Class.forName(全类名)方法获取,此方法在加载类时,返回类的引用
Class clazz = Class.forName(className);//加载类并获取类的引用
③通过类加载器获取,类加载器在加载类时也会返回类的引用
Class clazz = classLoader.loadClass("com.atguigu.java.Person");//加载类,获取类的引用
④通过类的属性获得,每个都有class属性
Class clazz = Person.class;//调用Person类本身.class属性
加载类,并获取类的引用,就可以使用反射机制了
三、反射机制总结
反射机制就是通过类的引用获取类的信息,并对类进行操作
1、获取运行时类的成员变量及其相关信息并对其操作
①获取对应运行时类的成员变量
@Test
public void test1() {
Class clazz = Person.class;
//1.获取运行时类本身及其父类中声明为public的属性
Field[] fields = clazz.getFields();//获取Person类的public修饰的变量,返回的是数组
for (Field field : fields) {//遍历属性数组
System.out.println(field);//打印获取的变量名称,调用field的toString方法输出属性的全称,修饰符+数据类型+类名+属性名
}
//2.获取运行时类本身声明了的属性
Field[] field1 = clazz.getDeclaredFields();//只要声明了就能获取,只限本身
for (Field field : field1) {
System.out.println(field.getName());//调用getName方法,只输出属性名,当然也可以用前者一样输出方式
}
}
运行结果
前者是public修饰的所有public属性;后者是类自身的所有属性
②获取属性的修饰符,变量类型,变量名
@Test
public void test2() {
Class clazz = Person.class ;//加载运行时类
Field[] field1 = clazz.getDeclaredFields();//获取类本身所有的属性
for (Field field : field1) {
//1.获取每个属性的权限修饰符
int i = field.getModifiers();//每一种权限对应一个值
String str1 = Modifier.toString(i);//获取值对应的权限
System.out.print(str1+" ");
//2.获取属性的变量类型
Class type = field.getType();//获取属性类型
System.out.print(type.getName()+" ");//打印属性类型的名字
//3.获取属性名并打印
System.out.println(field.getName());
}
}
运行结果
id为默认修饰符,所有int前面只有一个空格
③赋值或修改运行时类指定的属性(这个操作时要熟练掌握的),即使是final修饰的对象也可被修改
@Test
public void test3() throws Exception {
Class<Person> clazz = Person.class;//加载运行时类
Field name = clazz.getField("name");//获取运行时类的属性“name”,此声明必须为public
Person p = clazz.newInstance();//运用反射创建对象(调用的是空参构造)
name.set(p, "Jerry");//给指定p对象的name属性赋值
System.out.println(p);
Field age = clazz.getDeclaredField("age");//获取运行时类的属性“age”,此属性为任意类型类型
age.setAccessible(true);//把指定对p象的不可访问的属性设置为可访问的,因为age时private修饰的属性
age.set(p, 10);//给指定对象的age属性赋值
System.out.println(p);
Field id = clazz.getDeclaredField("id");//获取属性protect int id
id.set(p, 8);//修改属性
System.out.println(p);
}
运行结果
2、通过反射获取类的方法及其信息,并调用方法
①获取运行时类的方法
@Test
public void test1() {
Class clazz = Person.class;//加载类
//1.获取运行时类声明为public的方法
Method[] m1 = clazz.getMethods();//能获取运行时类及其父类中所有声明为public的方法
for (Method method : m1) {
System.out.println(method);//自动调用toString方法,输出此方法的所有信息
}
System.out.println();
//2.获取运行时类本身所有的方法
Method[] m2 = clazz.getDeclaredMethods();//获取运行时类本身所有方法
for (Method method : m2) {
System.out.println(method);//同前者
}
}
运行结果
②获取运行时类方法的信息(注解 权限修饰符 返回值类型 方法名 形参类表 声明异常)
@Test
public void Test2() {
Class clazz = Person.class;//加载类
//2.获取运行时类本身声明的所有的方法
Method[] m2 = clazz.getDeclaredMethods();//获取运行时类本身所有方法
for (Method method : m2) {
//1.获取注解
Annotation[] ann = method.getAnnotations();
for (Annotation annotation : ann) {//遍历并输出该方法的注解,如果无注解则不进入循环
System.out.println(annotation+" ");
}
//2.获取权限修饰符
String str = Modifier.toString(method.getModifiers());
System.out.print(str+" ");
//3.获取返回值类型
Class<?> returnType = method.getReturnType();//获取返回值类型的类引用
System.out.print(returnType.getSimpleName()+" ");//输出返回值类型的类引用的简称(SimpleName)
//4.获取方法名
System.out.print(method.getName());//获取方法名简称
//5.获取性参列表
System.out.print("(");
Class[] params = method.getParameterTypes();//获取方法的参数数组
for (Class class1 : params) {//遍历参数
System.out.print(class1.getSimpleName()+" ");
}
System.out.print(") ");
//6.获取异常类型
Class[] exceptionTypes = method.getExceptionTypes();//获取该方法异常类型数组
if(exceptionTypes.length!=0) {//如果有异常则...输出throws
System.out.print("throws ");
}
for (Class class1 : exceptionTypes) {//如果有异常则进入循环遍历异常数组
System.out.print(class1.getSimpleName());
}
System.out.println();
}
}
运行结果
③调用运行时类的方法(和成员变量同理,此操作需要熟练掌握)
@Test
public void test3() throws Exception {
Class<Person> clazz = Person.class;//加载类
//public方法的调用
//getMethod(String methodName,Class...params):获取运行时类中声明为public的指定的方法
Method m1 = clazz.getMethod("show");//获取方法show()
Person p = clazz.newInstance();//通过反射创建对象
//调用指定的方法:Object invoke(Object obj,Object...objs)
Object returnVal = m1.invoke(p);//调用p对象的m1获取的方法并用returnVal接收运行方法的返回值,void返回null
System.out.println("returnVal:"+returnVal);//null
//方法有返回值
Method m2 = clazz.getMethod("toString");//获得方法toString()
Object returnVal2 = m2.invoke(p);//调用p对象的m2获取的方法并用returnVal2接收运行方法的返回值
System.out.println("returnVal2:"+returnVal2);//打印返回值
//静态方法的调用
Method m3 = clazz.getMethod("info");//获得静态方法info()
m3.invoke(Person.class);//调用静态方法,参数可以是p对象,也可以是类的字节码引用
//getDeclaredMethod(String methodName,Class...params):获取运行时类中任何声明的指定的方法
Method m4 = clazz.getDeclaredMethod("display",String.class,Integer.class);//获取运行时类的私有方法,此方法的方法名为"display",参数为String.class,Integer.class(有多少写多少)
m4.setAccessible(true);//设置不可访问的权限为可访问的
Object value = m4.invoke(p,"CHN",10);//运行方法,传入参数,并将接收返回值
System.out.println(value);//输出10
}
运行结果
3、通过反射获取类的构造方法,并创建对象
①通过用字节码引用调用newInstance()方法创建对象
@Test
public void test1() throws Exception {
String className = "com.atguigu.java.Person";
Class clazz = Class.forName(className);//运行时加载Person类,在此处可使用泛型,后面就无需类型转化
//创建运行时类的对象,使用newInstance(),实际上就是调用了运行时类的空参构造器
/*
* 要想能够创建成功:1.要求对应的运行时类要有空参构造器。2.构造器的权限修饰符的权限要足够
*/
Object obj = clazz.newInstance();//动态创建对象,内部其实调用空参构造器,类没有无参构造时不能使用
Person p = (Person)obj;
System.out.println(p);
}
运行结果
②获取运行时类构造器
@Test
public void test2() throws Exception {
String className = "com.atguigu.java.Person";
Class clazz = Class.forName(className);//运行时加载类
Constructor[] cons = clazz.getDeclaredConstructors();//获取所有构造器
for (Constructor constructor : cons) {//遍历数组
System.out.println(constructor);//自动调用toString方法
}
}
运行结果
③调用指定的构造器,创建运行时类对象(需熟练掌握)
@Test
public void test4() throws Exception {
String className = "com.atguigu.java.Person";
Class clazz = Class.forName(className);//运行时加载类
Constructor cons = clazz.getDeclaredConstructor(String.class,int.class);
cons.setAccessible(true);//设置不可访问的方法为可访问的,防止构造函数被private修饰,而无法使用
Person p = (Person)cons.newInstance("罗伟",20);//用有参构造函数构造对象
System.out.println(p);
}
运行结果
4、通过反射获取类其他信息
以上三种就是类的主要组成部分,重点不在于获取信息,而在于调用运行,这才是反射的灵活之处。当然还有类的其他信息同样可以获取到,就不显得那么重要了
public class TestOthers {
//1、获取运行时类的父类
@Test
public void test1() {
Class clazz = Person.class;//加载运行时类
Class superClass = clazz.getSuperclass();//获取运行时类的父类
System.out.println(superClass);
}
//2、获取带泛型的父类
@Test
public void test2() {
Class clazz = Person.class;//加载运行时类
Type type = clazz.getGenericSuperclass();//获取带泛型的父类
System.out.println(type);
}
//3、获取父类的泛型(关注一下,有点绕)
@Test
public void test3() {
Class clazz = Person.class;//加载运行时类
Type type = clazz.getGenericSuperclass();//获取带泛型的父类
ParameterizedType param = (ParameterizedType)type;//强转,参数化
Type[] types = param.getActualTypeArguments();//获取泛型,返回Type类型数组
System.out.println(((Class)types[0]).getName());//将Type类型转化为Class类型
}
//4、获取实现的接口
@Test
public void test4() {
Class clazz = Person.class;//加载运行时类
Class[] interfaces = clazz.getInterfaces();//获取当前类的接口数组
for (Class class1 : interfaces) {
System.out.println(class1);
}
}
//5、获取所在的包
@Test
public void test5() {
Class clazz = Person.class;//加载运行时类
Package pack = clazz.getPackage();
System.out.println(pack);
}
//6、获取关于类的注解
//和类的生命周期有关(注解上去的),生命周期为CLASS获取不到,RUNTIME能获取到
@Test
public void test6() {
Class clazz = Person.class;//加载运行时类
Annotation[] anns = clazz.getAnnotations();//获取关于类的注解数组
for (Annotation annotation : anns) {
System.out.println(annotation);
}
}
//7、获取内部类
@Test
public void test7() {
Class clazz = Person.class;//加载运行时类
Class[] class1 = clazz.getDeclaredClasses();//获取运行时类的所有的内部类数组
for (Class class2 : class1) {
System.out.println(class2.getName());
}
}
}
以上就是反射的基本操作,而平日里用到的大概也就是创建对象,调用方法,赋值或更改变量。对于反射机制还有一个重要的应用,就是动态代理。